Skip to content

feat(collections): deep collection testing, approved fixtures and test maintenance - #14

Merged
testomut merged 6 commits into
mainfrom
feat/collection-testing
Nov 9, 2026
Merged

testomut merged 6 commits into
mainfrom
feat/collection-testing

Conversation

@testomut

@testomut testomut commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Why

The goal is catching API defects, not adding more tests. On the demo API the current generator's 65 tests miss all six injected list defects. This PR adds collection tests that catch all six, with the same results in TypeScript and Python.

Injected defect (DEMO_FAULT) Before (65 tests) After
filter ignored missed detected
sort reversed missed detected
created record hidden from the list missed detected
every record listed twice missed detected
wrong field value in the list missed detected
pages overlap (off-by-one offset) missed detected

What

Collection Testing Engine. A list operation gets deep tests only from a reviewed deps collection mapping. Every pointer, parameter, sort value and paging parameter in it is checked against the spec. A mismatch becomes an INVALID_REVIEWED_CONFIG gap, never a guess.

  • Strategy A, create & verify.
    • Each test creates its own records with a unique marker in an isolating filter, reads only them, and removes them in finally.
    • The datasets are non-trivial: a match plus a non-match for filters, 3 distinct values created out of order for sorts, page size + 1 records for paging.
  • Strategy B, approved fixtures.
    • generate --fixtures <dir> compares the list with a fixture a person approved. Fields are compared by policy: exact, tolerance:<n>, schema, ignore, plus an exhaustive option.
    • fixtures capture writes candidates only.
    • fixtures refresh --draft-pr proposes a changed candidate in one Draft PR per operation. Approved files are never written by the agent.
  • Strategy C, read-only. The same properties are checked on existing data; a test without enough data is skipped with the reason.
  • Strategy record. The chosen strategy and its reasons are written per list operation in coverage-gaps.json.
  • Assertions, in TS and Python:
    • contains / not contains;
    • fields by policy;
    • every result matches the filter;
    • unique by a field;
    • sorted by a field;
    • page size, and no duplicates across pages.
  • Paging and lifecycle. Offset and page/size paging; bounded polling only when eventual consistency is documented; create → list → update → list → delete → list.
  • Case IDs. Rule vocabulary v3 adds collection-contains, filter, sort, pagination, unique and fixture.

maintain. Spec diff → deterministic regeneration → registry and manifest validation → one Draft PR per suite. An open PR is updated rather than duplicated, and nothing is merged. The summary lists:

  • changed operations;
  • tests and cases added, updated, removed or retired;
  • files whose only change is the spec hash in the header, listed separately;
  • expectations old → new;
  • fixtures used;
  • reviewed config that no longer matches the spec;
  • gaps before → after;
  • the validation result.

Fault injection. The demo API gets offset / tag / sort on GET /items, six DEMO_FAULT modes, and two catalog reference items, which the approved demo fixture describes. scripts/mutation-check.ts runs a generated suite once per fault, on its own port.

CI.

  • The demo suites are generated with --deps and --fixtures.
  • Fault injection must report 6/6 detected for TypeScript and for Python.
  • A maintenance run on a changed spec must flag the stale mapping.

Fixes found along the way:

  • A GET of a single resource with an array field (for example Item.tags) was reported as an unconfigured collection.
  • Hand-written _support/api/*Api.ts dropped out of the generation manifest on regeneration, so maintain would have counted them as removed.

Verification (local, CI-equivalent)

  • tsc, build, and unit tests: 206 passed, 2 skipped. That includes 12 new collection, fixture and strategy tests.
  • Demo suite, generated and run once, in TypeScript and Python:
    • 86 tests, each with its own registered case ID;
    • only the deliberate stats/get/contract defect fails;
    • TypeScript and Python have the same active cases;
    • registry and manifest are valid;
    • coverage gaps match demo-api/expected-gaps.json.
  • Fault injection: no fault → 29 tests pass, 0 fail. Each of the 6 faults → between 1 and 7 tests fail. The counts are identical in TypeScript and Python.
  • Kitchen sink: compiles, and TS/Python parity holds at 112 / 112 cases.
  • maintain, checked twice:
    • on a demo spec with a changed limit maximum and a removed sort value: 3 files really updated, 20 header-only, the stale -price mapping reported, validation passed;
    • CI runs the same check with only the sort-value change.
  • fixtures capture / refresh against the local demo: the candidate is written, and with no difference under the policies it reports "nothing to refresh".

Limitations

  • No cursor pagination yet. Sorting is checked by plain comparison (code points or numbers), so the API's documented order has to match that.
  • One invalid part of a collection mapping drops all its collection tests for that operation. They come back once the mapping is fixed, and the summary says why they're missing.
  • The PR-update path of maintain --draft-pr and fixtures refresh --draft-pr (gh pr list / edit, force-with-lease on the bot branch) was not run against GitHub, to avoid opening throwaway PRs.
  • In CI, maintain runs without oasdiff and regenerates the whole suite (stated in its summary). Locally it ran with oasdiff 1.34.1.
  • --run only finds Playwright suites under tests/api (the repository's playwright.config.ts). This was already the case.
  • The approved demo fixture was captured from the local demo API and approved as part of this PR's demo setup.

…t maintenance

Collection Testing Engine for list operations, from a reviewed deps "collection"
mapping checked strictly against the spec (mismatch = INVALID_REVIEWED_CONFIG):
- strategy A (create & verify): controlled datasets with a unique marker in an
  isolating filter, cleanup in finally; filter (match + non-match), sort (3
  distinct values created out of order), pagination (page size + 1 records),
  uniqueness, persisted fields by policy, bounded polling;
- strategy B (approved fixtures): generate --fixtures compares a list with a
  fixture a person approved; fixtures capture writes candidates only; fixtures
  refresh --draft-pr proposes a changed candidate (one PR per operation);
- strategy C (read-only) with explicit skips; strategy recorded per operation;
- create -> list -> update -> list -> delete -> list scenario;
- case ID vocabulary v3; same cases in TypeScript and Python.

maintain: spec diff -> deterministic regeneration -> registry and manifest
validation -> one Draft PR per suite (updated, never duplicated, never merged)
with a summary of changed operations, tests, expectations, fixtures and gaps.

Fault injection: the demo API simulates six list defects (DEMO_FAULT);
scripts/mutation-check.ts requires each to be caught. The previous generator's
65 demo tests catch 0/6; the collection tests catch 6/6 in both languages.

Fixes: a single resource with an array field is not an unconfigured collection;
hand-written _support/api/*Api.ts stay in the generation manifest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 809f79e4-c12d-4510-ba19-9907e3098807
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

testomut and others added 5 commits November 9, 2026 23:10
- "strategy": "read-only" in a collection mapping: no records are created for
  the checks even when a create is confirmed;
- page-number paging with a fixed page size (no size parameter);
- read-only checks of a public list go through publicApi, never a login;
- read-only filter tests take up to 5 values from the list and require the
  filtered result to be a subset of the unfiltered list (re-read once);
- a sort case ID may carry a reviewed value the spec does not enumerate.

The subset check catches the defect of Toolshop's deliberately defective build
(by_brand=1 returns rentals 27-29 the list never shows) and passes on the
regular build, in TypeScript and Python. Demo fault injection stays 6/6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en the .env one is missing or refused

The .env account is used while it works. When it is missing or its login is refused (401/403/422, e.g. the test environment reset its data), the agent registers one account before --run and the generated tests reuse it; a worker of a standalone run registers at most once. A working account never causes a registration; .env is never changed. Verified on the demo API: wrong password -> 1 registration in an agent run, 1 per worker standalone; right password -> none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wright globalSetup

Playwright starts a fresh worker after a failed test, so per-worker registration in auto mode registered an account per failure (4 for 3 failures on Toolshop). _support/global-setup.ts now settles the account once in the main process and hands the credentials to every worker through the environment (memory only). init adds it to playwright.config.ts when present. Verified on Toolshop: one registration for a run with failures and restarted workers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s globalSetup when it has no register settings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generated conftest registers at most one account per pytest session: new always, auto only when the .env account is missing or refused (401/403/422). Credentials stay in memory; .env is never changed. The CLI no longer refuses new accounts for Python. Verified on the demo API: auto with a wrong password -> 1 registration, with the right one -> none, new -> 1; an agent-driven Python run in auto mode registers once and the suite reuses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@testomut
testomut marked this pull request as ready for review November 9, 2026 23:05
@testomut
testomut merged commit 09150e4 into main Nov 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant