Skip to main content
ThunderLang
← All articles
ai-engineering

Test AI-Generated Invitation Expiry at the Exact Boundary

2 min read · 2026-10-08 · Allen Codewell

A blank envelope rests against a wooden barrier aligned with a seam in limestone.

AI-generated illustration. The chosen expiry rule makes the boundary decisive: an invitation at the exact deadline fails the expiry check, rather than receiving an extra instant of validity.

To test AI-generated invitation expiry code, choose what should happen at the exact expiry instant. Then test the application's expiry gate with an injected clock immediately before, exactly at, and immediately after that instant.

For this example, the expiry check passes only when now < expiresAt. Equality means expired.

Decide what “until” means before asking for code

An instruction such as “make invitations expire after their deadline” allows two interpretations:

now < expiresAt   // Exclusive boundary: equality is expired.
now <= expiresAt  // Inclusive boundary: equality still passes.

Neither comparison can decide the intended behavior. The requirement must choose. Here, we choose the exclusive boundary.

Passing this check means only that the sampled time is earlier than the deadline. It does not establish that the invitation is authorized, unused, or otherwise acceptable.

A requirement-to-test worksheet

These timestamps are hypothetical fixtures, not an operational deadline.

Contract item Decision
Time representation UTC instants represented as integer milliseconds since the Unix epoch
Input assumption expiresAtMs and the clock result are validated, finite integer timestamps
Boundary rule The expiry check passes if and only if nowMs < expiresAtMs
Clock sampling Read the clock once when the expiry gate runs
Exact-boundary outcome Equality fails the expiry check
Scope Expiry only, not authorization, single-use enforcement, or clock synchronization

Use this deadline for all three cases:

expiresAt = 2030-01-01T12:00:00.000Z

Case Injected now Expected expiry result
One millisecond before 2030-01-01T11:59:59.999Z Pass
Exactly at expiry 2030-01-01T12:00:00.000Z Fail
One millisecond after 2030-01-01T12:00:00.001Z Fail

Agree on this worksheet before implementation. If the intended boundary changes, update the contract and expected outcomes deliberately, not just to make a test match generated code.

Use equality as the counterexample

Suppose a coding agent produces this condition:

const passesExpiryCheck = nowMs <= expiresAtMs;

The before-expiry and after-expiry tests both pass with this implementation. Neither distinguishes it from the chosen contract.

The exact-expiry case does:

Given: nowMs equals expiresAtMs
Required: passesExpiryCheck is false
Inclusive comparison: passesExpiryCheck is true

This counterexample pinpoints the disagreement. It is not evidence that the implementation is unreliable in every respect.

The useful review question is: Which input would make a plausible wrong implementation disagree with the contract? Here, it is equality.

A coral paper clock connects through a removable cable to a cream fixture containing one gear.

AI-generated illustration. An injected clock supplies a controlled instant to the application's expiry gate. Separate tests can supply times immediately before, exactly at and immediately after expiry without waiting for wall-clock time; this illustration does not depict executed tests.

Inject the clock into the application's expiry gate

This illustrative JavaScript uses a clock function instead of waiting for wall-clock time to cross a deadline. It has not been executed here.

// invitation-expiry.mjs
export function passesInvitationExpiry(
  invitation,
  { now = Date.now } = {}
) {
  const nowMs = now();
  return nowMs < invitation.expiresAtMs;
}

When adapting this example, test the expiry gate that the application's invitation redemption path actually uses. A separate test-only implementation could match the worksheet while leaving application behavior unchanged.

The code assumes timestamp validation has already happened. Handling malformed or missing values needs a separate requirement.

The three acceptance tests import the module rather than rewrite its comparison:

// invitation-expiry.test.mjs
import test from 'node:test';
import assert from 'node:assert/strict';
import { passesInvitationExpiry } from './invitation-expiry.mjs';

const invitation = {
  expiresAtMs: Date.parse('2030-01-01T12:00:00.000Z')
};

const cases = [
  {
    name: 'passes one millisecond before expiry',
    now: '2030-01-01T11:59:59.999Z',
    expected: true
  },
  {
    name: 'fails exactly at expiry',
    now: '2030-01-01T12:00:00.000Z',
    expected: false
  },
  {
    name: 'fails one millisecond after expiry',
    now: '2030-01-01T12:00:00.001Z',
    expected: false
  }
];

for (const fixture of cases) {
  test(fixture.name, () => {
    const result = passesInvitationExpiry(invitation, {
      now: () => Date.parse(fixture.now)
    });

    assert.equal(result, fixture.expected);
  });
}

The expected outcomes are literal values from the contract. Computing them with another timestamp comparison could reproduce the same mistake.

The injected clock supplies the intended instant regardless of when the test runs. Millisecond representation sets the test's granularity; it does not establish a machine clock's accuracy.

Check that the test rejects the intended mistake

In an isolated working copy, temporarily replace < with <= in the expiry gate. Run the tests using your project's existing test setup.

The exact-expiry case should fail, while the before-and-after cases should still pass. Restore < afterward.

This mutation exercise checks whether the suite detects this particular mistake, not every possible defect.

Keep the acceptance claim narrow

These tests check the expiry gate's decisions for three chosen inputs. They do not establish:

  • Production wiring: the redemption route actually calls the gate and rejects a failed result. Add a route-level test with a fake clock and isolated test resources to check that integration.
  • Authorization: the requester is entitled to redeem the invitation.
  • Single-use enforcement: two concurrent requests cannot redeem the same invitation.
  • Distributed-clock agreement: different machines agree on the current instant.
  • Storage fidelity: timestamps retain their precision through serialization and persistence.
  • Completion timing: redemption finishes before expiry. This contract checks time when the gate runs, not when a later operation commits.

If redemption must complete before the deadline, checking time earlier in the request is not enough to establish that behavior.

For this narrow change, the review standard is clear: name the boundary, use equality to expose the inclusive-comparison mistake, and test the implementation used for expiry decisions. Stronger claims need additional requirements and evidence.

For a next step, see ThunderLang's getting-started guide to gate your first AI change.