Repository navigation
[TST-E4] test: the declared-error door table, with a one-way pinned-spec guard and its pilot row - #446
Merged
Conversation
… 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.
This was referenced Oct 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 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 ownCommon/Support/(testing-style.md: a one-slice helper lives in its slice'sSupport/), runs one row source on two spines:Throws<OpenCodeApiException>, the exact status and the exact error type;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 inspec/openapi.json. It checks that:_tagis the row's tag.The spec is linked into the test assembly as an embedded resource (
Fixtures/Pinned/openapi.json) and read through the existingFixtureLoader, 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, replacesWorktreesClientContractTests.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 cannedBadRequestwithWireBodyData.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:
DeclaredErrorCaseis areadonly 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.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
🎯 Protocol & API Surface
external/,spec/orsrc/was touched (the spec is only linked into the test project)🧪 Testing
Test mapping (mechanical:
e4_test_map.pyovergit diff 3b235436 850477cd)tests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.csCreateWorktreeAsync_Should_Throw_The_Other_Declared_400_Errorworktree.create:400:InvalidRequestErrorUnmapped: 0
tests/OpenCode.Sdk.Tests/Common/Errors/DeclaredErrorTests.cstests/OpenCode.Sdk.Tests/Common/Support/DeclaredErrorCase.cstests/OpenCode.Sdk.Tests/Common/Support/DeclaredErrorDoor.cstests/OpenCode.Sdk.Tests/Worktrees/WorktreesClientContractTests.csThe
WorktreesClientContractTestsdelta 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)
WorktreeCreateResponseAdapterStatus400TagswithoutInvalidRequestErrorUnknownOpenCodeError); head: the row red on both spinesBadRequest→Unauthorized)anyOf/0(WorktreeError); to another operation; a pointer that does not resolve; a wrong operationId; a wrong tagEvidence
nameof, the single status field, public visibility, and the stated-guarantee comment.MemberNamewas never read, so a door labelled with the wrong member would still pass. It is resolved in part: the name is now a compile-checkednameofand 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 rootSupport/to the slice) is applied. Its comment note agrees that the comment is the!'s stated guarantee.✅ Completion Gate
Local check (
quality-gates.md) onfcbbaa6f, base3b235436. Every step's own exit code was read. The final head850477cdonly moves the two record files intoCommon/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.dotnet build --configuration Release: 0 warnings, 0 errorsdotnet format whitespace/dotnet format style --severity warn: no changesverify-clones: passed, 0 new (tests 5.42%); baseline −2 entries onlyOpenCode.Sdk.Tools.Tests(all but NativeAot): 2033, 0 failedOpenCode.Sdk.Tests(all but ServiceElection/NativeAot): 8396, 0 failedThe 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
Need help on this PR? Tag
@codesmith-botwith what you need.