skip to content

What must a policy rule's test suite assert beyond denying the obviously bad input?

level: middleimportance: should knowfreq 62%

answer

  1. deny-only suites pass a deny-everything rule
  2. test what must be allowed
  3. the property is simply absent
  4. value resolved only at deploy time
  5. assert the message, not just the verdict

basics

~20 s

It must assert what the rule allows, not only what it denies — otherwise a rule that blocks everything passes. It also needs the awkward cases: the property missing entirely, and a value that is only resolved at deploy time.

solid answer

~50 s

A suite made only of violations is close to worthless: a rule that denies unconditionally passes all of it. So every rule needs allow fixtures — compliant documents that must come back clean — because over-blocking is the failure that gets the gate switched off. Then the cases nobody writes first. If the backup-retention property is absent from the template altogether, does the rule deny, or does it simply reach no conclusion and let the resource through? That has to be a decision the tests pin down, not an accident of how the comparison was written. Same for a value supplied indirectly and only known at deploy time: the rule sees a reference, not a number. Finally, assert the denial message and the rule id, since that text is the product for the person you just blocked, and add a regression fixture for every false positive ever reported.

code

yaml · 20 lines
yaml
Parameters:
  RetentionDays: { Type: Number }
Resources:
  TooShort:                      # must DENY
    Type: AWS::RDS::DBInstance
    Properties:
      BackupRetentionPeriod: 1
  Compliant:                     # must ALLOW, cleanly
    Type: AWS::RDS::DBInstance
    Properties:
      BackupRetentionPeriod: 14
  Unspecified:                   # property absent - deny, or no verdict at all?
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: postgres
  Indirect:                      # value only known at deploy time
    Type: AWS::RDS::DBInstance
    Properties:
      BackupRetentionPeriod: !Ref RetentionDays
  ...

go deeper

for a junior

Know that a rule needs examples it should allow as well as examples it should deny, and that a rule blocking everything would pass a suite made only of bad examples.

for a middle

Be ready to enumerate the fixture shapes and explain the absent-property case: a comparison against a field that is not there often yields no verdict rather than a denial, so the resource passes.

for a senior

Demonstrate that you test coverage as well as logic — which resource types the rule matches, how it handles values resolved after review, and how a corpus of real templates shows a change's true impact before it merges.

for a principal

Argue for the discipline that makes rules trusted: allow fixtures as a hard requirement, every reported false positive turned into a permanent test, and impact reports attached to rule changes as the price of shipping one.

## Why deny-only suites pass bad rules The instinct when testing a rule is to feed it the thing it is supposed to catch. For "managed database instances must keep at least seven days of backups", that is a template with a one-day retention window, and the test asserts a denial. Green. The problem is that a rule which denies *everything* also passes that suite. So does a rule that denies on the wrong grounds — matching the resource type and ignoring the property entirely. Deny-only fixtures test that the rule is *loud*, not that it is *right*. The missing half is allow fixtures: documents that are compliant and must come back with no findings. Over-blocking is the failure mode that kills a gate politically. A rule that misses a violation produces a risk somebody may never notice; a rule that blocks a compliant deploy produces an angry team at 5pm and a standing argument about whether the gate should be advisory. Allow fixtures are the only cheap defence against it. ## The three shapes every fixture set needs 1. **Violating** — the property is present and wrong (retention of one day). Must deny, with the expected rule id and message. 2. **Compliant** — the property is present and right (retention of fourteen days). Must produce no finding. 3. **Absent** — the property is not in the document at all. This is the case that separates a tested rule from an untested one, and it is where most rules quietly fail. A rule written as a numeric comparison against a field that does not exist frequently produces no result at all rather than a denial. No result is not a denial. The template sails through, and the resource is created with whatever the platform's default happens to be. Whether absence should deny is a judgment — often it should, because you cannot demonstrate compliance from a document that does not mention it — but it must be a decision the suite records, not an emergent property of the expression. A fourth shape is worth adding wherever the surface allows indirection: the value is supplied by a parameter, an intrinsic reference, or anything resolved after the document is reviewed. The rule sees a reference object where it expected a number. Comparing it numerically may throw, may silently produce nothing, or may compare against a type it does not understand. Decide what you want — commonly, deny with a message saying the value could not be determined at review time — and fixture it. ## Assert the output, not just the verdict The verdict is a boolean; the *message* is what a blocked engineer reads. Assert it. A test that pins the rule id and the denial text catches a rename that would orphan every reference to that rule, and it stops the message decaying into "policy violation" as the rule is edited over time. If the repository requires each rule to name its owning team, assert that field too, so it cannot go missing. ## Where fixtures should come from Hand-written fixtures encode what the rule author imagined. Real templates contain shapes nobody imagines: nested modules, resources declared through indirection, generated documents, the same logical resource expressed through a different resource type. The strongest suites are seeded from the estate — a corpus of documents already merged across the organisation — and each new rule is evaluated against that corpus before it merges, so the reviewer sees the true impact rather than a guess. Also: **every false positive that ever reached a team becomes a permanent fixture**. That is how a rule stops making the same mistake twice, and it is the cheapest trust-building move available to a rule owner. ## What a green suite does not prove It proves the rule matches the inputs you wrote down. It does not prove: - **Coverage of the resource types that matter.** The requirement is about databases; the rule may only match single instances while the estate's production data lives in cluster resources that carry their own retention property. Every document you fixtured is a document you thought of. - **That the rule's premise is still true.** A rule encoding a requirement that has since changed passes its own tests forever. - **Anything about the templates teams actually write.** Only evaluating the rule against real documents tells you that. The useful mental model: the suite is a contract with the rule's future editors, saying "these are the decisions we made on purpose". It is not a claim about the world.

  • Your rule's suite is green, but a database with a one-day window still reached production. Where do you look first?
    At what the rule matches. The most common cause is a resource type outside its match — the estate uses a cluster resource that carries its own retention property, and the rule only matches single instances. After that, check whether the value arrived indirectly and the rule skipped it, and whether that pipeline treated the finding as advisory rather than blocking.
  • Where should the fixtures come from?
    Two sources. Hand-written cases for the boundaries — at the threshold, one below it, absent, indirect. And a corpus harvested from documents already merged across the estate, evaluated on every rule change so the reviewer sees how many real templates a change would newly deny. Add every reported false positive as a permanent fixture.
  • Why assert the denial message and not just that a denial happened?
    Because the message is what the blocked engineer acts on, and it decays silently. Pinning the text and the rule id catches a rename that would orphan every waiver and dashboard referring to that rule, and stops a specific, actionable message eroding into a generic one over a year of edits.

saying these in an interview costs you the question

  • A suite of violating examples is enough
  • If the property is missing the rule obviously fails
  • Green tests mean the rule is enforced somewhere
  • Fixtures can be invented; real templates add nothing
  • The message text is cosmetic and not worth asserting

context