Skip to content

[TST-E4] test: the declared-error door table, with a one-way pinned-spec guard and its pilot row - #446

Merged
Blind-Striker merged 1 commit into
masterfrom
test/tst-e4a-declared-error-table
Oct 17, 2026
Merged

Blind-Striker merged 1 commit into
masterfrom
test/tst-e4a-declared-error-table

Conversation

@Blind-Striker

@Blind-Striker Blind-Striker commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

📝 Description

What does this PR do?
It is TST-E4a's first PR. It adds the declared-error door table that later PRs will convert about a hundred per-door test methods into, and converts one pilot row so the mechanism can be reviewed before any batch conversion.

  • Common/Errors/DeclaredErrorTests, with its two row records in the slice's own Common/Support/ (testing-style.md: a one-slice helper lives in its slice's Support/), runs one row source on two spines:

    • throwing: Throws<OpenCodeApiException>, the exact status and the exact error type;
    • NoThrow: IsError, the exact status and the exact error type.

    A row is one door, one canned error and one expectation. The spine is the test method, so a row's data is never written twice.

  • The rows' expectations come from the pinned spec, not from generated code. A third test, Every_Row_Should_Match_The_Pinned_Declaration, resolves each row's JSON pointer in spec/openapi.json. It checks that:

    • the operationId sits at the row's method and path;
    • the pointer belongs to that operation's response for the row's status;
    • the referenced component's _tag is the row's tag.

    The spec is linked into the test assembly as an embedded resource (Fixtures/Pinned/openapi.json) and read through the existing FixtureLoader, so it reaches every OS leg and net472. The guard is one-way: it proves each row against the pin, and does not try to find every declared error.

  • The pilot row, worktree.create:400:InvalidRequestError, replaces WorktreesClientContractTests.CreateWorktreeAsync_Should_Throw_The_Other_Declared_400_Error. It keeps the same door, the same request (ProjectId "prj_1", Directory "/repo/feature") and the same canned BadRequest with WireBodyData.InvalidRequestError, and it now also runs on the NoThrow spine.

  • The row names its SDK member with nameof(WorktreesClient.CreateWorktreeAsync), so a renamed member breaks the build instead of leaving a stale string. The member is shown in the test's display name.

Two shape choices worth knowing:

  • DeclaredErrorCase is a readonly record struct. The test class and its records are public, matching every other test class. A public test method that dereferences a reference-type parameter trips CA1062, which this repository sets to error; a value-type row avoids that without a null check in every test.
  • The comment above GetString()! stays, on purpose. It states the guarantee behind the ! (SLOP-08).

One status field. At the base, every one of the 106 declared-error methods that the later PRs will convert answers with the same status it asserts, so the row has a single Status, used for both the canned response and the expectation. The wrong-status mutant below shows that a single field still guards it.

🔄 Type of Change

  • 🧪 Test improvements

🎯 Protocol & API Surface

  • No change to the public API surface
  • Nothing under external/, spec/ or src/ was touched (the spec is only linked into the test project)

🧪 Testing

Test mapping (mechanical: e4_test_map.py over git diff 3b235436 850477cd)

File Base test method At head Target
tests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.cs CreateWorktreeAsync_Should_Throw_The_Other_Declared_400_Error row worktree.create:400:InvalidRequestError

Unmapped: 0

File Assert.That at base at head delta
tests/OpenCode.Sdk.Tests/Common/Errors/DeclaredErrorTests.cs 0 12 +12
tests/OpenCode.Sdk.Tests/Common/Support/DeclaredErrorCase.cs 0 0 +0
tests/OpenCode.Sdk.Tests/Common/Support/DeclaredErrorDoor.cs 0 0 +0
tests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.cs 68 65 -3

The WorktreesClientContractTests delta is the deleted method's three assertions. The row asserts the status and the exact type on both spines; the deleted method asserted them on the throwing spine only.

Mutants (net10.0, analyzers on, each restored byte-identically)

Mutant Killed by
Equivalence: WorktreeCreateResponseAdapter Status400Tags without InvalidRequestError base: the deleted method red (UnknownOpenCodeError); head: the row red on both spines
Wrong status (the single field BadRequest → Unauthorized) both spines and the guard
Wrong error type both spines
Pointer to the 401 response; to anyOf/0 (WorktreeError); to another operation; a pointer that does not resolve; a wrong operationId; a wrong tag the guard

Evidence

  • Dispatch harness on the worker's commit: build, Sdk.Tests net10.0 2797/0 and net472 2802/0, Tools.Tests 2019/0, verify-clones 0 new, slopwatch clean.
  • After the coordinator's fix-up: the focused classes pass 22/0 on net10.0 and net472. The fix-up added the nameof, the single status field, public visibility, and the stated-guarantee comment.
  • Review: the coordinator read the diff, and a second-family reviewer (Grok, given only the requirements and the diff) reviewed it. Grok's one defect: MemberName was never read, so a door labelled with the wrong member would still pass. It is resolved in part: the name is now a compile-checked nameof and part of the display name. A label naming a different, existing member would still pass, because a lambda cannot tell the test which member it calls. Grok's placement note (move the records from the root Support/ to the slice) is applied. Its comment note agrees that the comment is the !'s stated guarantee.

✅ Completion Gate

Local check (quality-gates.md) on fcbbaa6f, base 3b235436. Every step's own exit code was read. The final head 850477cd only moves the two record files into Common/Support/ and changes their namespace. On it: the full build has 0 warnings and 0 errors; the focused classes pass 22/0 on net10.0 and net472; the whole Tools.Tests project passes 2033/0; format, slopwatch and verify-clones are clean.

  • slopwatch: zero findings
  • dotnet build --configuration Release: 0 warnings, 0 errors
  • dotnet format whitespace / dotnet format style --severity warn: no changes
  • verify-clones: passed, 0 new (tests 5.42%); baseline −2 entries only
  • OpenCode.Sdk.Tools.Tests (all but NativeAot): 2033, 0 failed
  • OpenCode.Sdk.Tests (all but ServiceElection/NativeAot): 8396, 0 failed

The host runs Bun 1.4.3, a maintainer-named off-pin host (OPENCODE_SDK_TESTS_BUN_MISMATCH=allow); the pin is 1.4.2.

✅ Code Quality Checklist

  • No new suppression
  • No tests skipped or weakened; the one removed method is mapped to its row above

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

… and its pilot row

TST-E4a P01a. DeclaredErrorTests runs one row source on both spines: the throwing door (OpenCodeApiException, exact status, exact error type) and the NoThrow door (IsError, exact status, exact error type). Every row's status and tag come from the pinned spec: the guard resolves each row's JSON pointer in spec/openapi.json, embedded into the test assembly, and checks the operation, the response status and the component's _tag. The pilot row worktree.create:400:InvalidRequestError replaces WorktreesClientContractTests.CreateWorktreeAsync_Should_Throw_The_Other_Declared_400_Error with the same door, request and canned error, now on both spines. The row's member name is a nameof, so a renamed SDK member breaks the build.
@Blind-Striker
Blind-Striker added this pull request to the merge queue Oct 17, 2026
Merged via the queue into master with commit 3f90dc7 Oct 17, 2026
12 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