test: consolidate the test suite (table-driven endpoints and enumerators, shared fakes) #189

Merged
Grandiras merged 17 commits from debloat-e2-tests into main 2026-09-27 20:55:18 +00:00
Owner

Part of #180 (pass E, part 2: test consolidation).

Part 1 (#186) built the table-driven EndpointRequestTests, the query-parameter drift check and the
shared test kit, but kept whole files that held some union tests. This part applies the keep/move
rules per test instead of per file, then does T3, T4 and T1. Test-only: no src/ change, no
CHANGELOG entry.

Rules applied. Kept: union/polymorphic tests (one representative per union per client area),
bespoke client logic, regression tests, security, interop and pinned vectors, cross-cutting
behaviour (proxy presence/absence, headers). Moved to a table row or deleted: tests whose only
assertions were method, NSID, query or body plus "a field deserializes". A row's response check is
now "a realistic response (every required member present) deserializes": the drift tests already
check every JSON name against upstream, so rows no longer re-assert fields one by one. Rows assert
the exact body/query where the old tests only spot-checked (Contains).

Numbers

python3 loc.py origin/main HEAD, bucket tests/ATProtoNet.Tests (the only area touched):

code xmldoc comment files
origin/main (e7f9e61) 48153 1551 1914 295
HEAD 44842 1596 1826 281
change −3311 +45 −88 −14

Net −3354 counted lines (−4212 raw, blanks included). By item: items 1 and 4 −2568, T3 −470,
T4 −74, T1 (plus folding the Spaces/Jetstream enumerators into the theory) −242.

Test cases: 5023 → 4882 (4877 passed, 5 skipped, both runs; the skips are AspireManifestTests).
The count drops less than the lines because most removed facts became theory rows.

1. Lexicon client tests (Ozone, Chat, Lexicon, Bsky*, root)

EndpointRequestTests gained compact Get(nsid, response, call, query, proxy) /
Post(nsid, response, call, body, proxy) helpers; the 53 existing rows were rewritten onto them
and 108 added (161 rows in all). 18 per-endpoint files are gone; what they had beyond a plain
request/response now lives in Ozone/OzoneUnionTests, Chat/Bsky/ChatClientTests and
Lexicon/App/Bsky/BskyUnionTests.

site.standard (StandardSiteClientTests 367 → 24 lines)

  • 15 CRUD facts + part 1's 4 delete rows → 18 rows (create/get/list/delete per collection, put for
    publication/document), exact body/query; typed records deserialize from the get/list responses.
    ListRecommendationsAsync gained a row.
  • ListSubscriptionsAsync_RecordOfTheWrongShape → RecordCollectionErrorTests.ListAsync_WhenARecordIsNotTheRecordType_* (same Repo.ListRecordsAsync<T> path).
  • GetRecommendation_UriOfAnotherCollection + GetPublicationAsync_UriThatNamesNoPublication → one theory, both overloads × 3 bad URIs.

tools.ozone (6 files → OzoneUnionTests)

  • OzoneQueueClientTests (7) → rows.
  • OzoneReportClientTests: 15 request/response facts → rows. CreateActivityAsync kept (union write + read). ReportActivity_UnknownType → OpenUnionTests.Deserialize_UnknownVariant_* (runs for every open union base).
  • OzoneToolsClientTests: safelink, setting, verification facts → rows with exact bodies. SafelinkPagination_SendsEachCursorInTheBody → the queryRules/queryEvents rows (cursor in the exact body) + PaginationTests. GetAccountHistoryAsync kept (union).
  • OzoneModerationToolingTests: getAccountPreferences (its union → LexiconUnionTests.Preferences_*), getRecords (same union as getRepos), getSubjects, scheduleAction (exact body incl. the action $type), list/cancelScheduledActions → rows. ListScheduledActionsAsync_PagedThroughPagination → the row's cursor-in-body + PaginationTests. QueryEventsAsync_EventsOzoneRecordsItself → the EventVariants theory (plus a recordEvent-without-cid row). QueryEventsAsync_MuteReporterWithoutDuration (regression) → an EventVariants row with its comment. Kept: getRepos, EventVariants, ModEventReport_WithoutReportType, RepoViewDetail_ReadDirectly.
  • TypedOzoneClientTests: typed filters, getRecord, emitEvent, searchAccounts → rows. GetRepoAsync_InvalidDidInTheResponse → TypedRepoClientTests.Response_WithAnInvalidIdentifier_* (same mapping). ListMembersAsync_AdminTokenAsLastUpdatedBy → the listMembers row reads "admin_token" (comment kept). QueryEventsAsync_ParsesTheTypedEventView kept (typed identifiers through a real response).
  • OzoneClientTests: EmitEvent_SendsTakedown merged into the emitEvent row; QueryStatusesAsync_Filter (regression, "querySubjects never existed") → row with the comment; QueryStatusesAsync_ReadsSubjectStatuses → LexiconUnionTests.SubjectStatusView_* / ModerationSubject_* (same unions).

chat.bsky (ConvoClientTests, GroupClientTests, ChatModerationClientTests, ChatActorClientTests → ChatClientTests)

  • Convo: ListConvos_SendsCorrectRequest + ListConvosAsync_FiltersThenPaging → one row; getConvo, muteConvo, getMessages, getLog, addReaction, getConvoForMembers, sendMessageBatch (lazy sequence), getUnreadCounts, lock/unlock → rows (exact body/query + chat proxy; the unwrapped ConvoView's required members check the unwrapping). SendMessage_PostsWithProxy → SendMessageAsync_ReplyEmbedAndFacets (now also asserts the proxy). SendMessageAsync_QuotedPost → a row (exact body incl. app.bsky.embed.record). EnumerateMessagesAsync_RepeatedCursor → the enumerator theory.
  • Group: all but GetJoinLinkPreviewsAsync (kept, union) → 17 rows.
  • Moderation: 5 facts → rows; GetConvosAsync kept (union; now also asserts what the dropped GetConvoAsync checked); Calls_WithoutAClientWideProxy kept. The unused private AssertPost helper (claude-bot note on #186) went with the file.
  • ChatActorClientTests' one record test → ConvoModelsTests.
  • ConvoModelsTests: Deserialize_SystemMessageDataMemberJoin_ReadsWhoApproved and Deserialize_LogAddReaction_* → their rows in the existing variant theories (which round-trip every field); Deserialize_GetMessagesPage_* → the GroupConvoView, DirectConvoWithoutKind and MessageView facts (same ConvoMessage union).

app.bsky (TypedBskyClientTests, Bookmark/Draft/Embed/Graph/Notification/Unspecced/AgeAssurance files → BskyUnionTests + rows)

  • TypedBskyClientTests: getAuthorFeed, searchPosts, getPosts, listNotifications (regression "seenAt is deliberately not a parameter": row with the comment) → rows; their typed-response asserts → OzoneUnionTests.QueryEventsAsync_ParsesTheTypedEventView and TypedRepoClientTests.CreateRecordAsync (one per identifier type through a real response) + TypedIdentifierGuardTests + drift. The three Enumerate* walks → the enumerator theory. PostAsync_WritesACanonicalCreatedAt → merged into BlueskyHelpersTests.PostAsync_PlainString. FollowAsync → BlueskyHelpersTests.SubjectHelpers_*. PostRecord_RoundTrip → BskyRecordTests' round-trip theory. CursoredResponses_AllImplementICursorPage → item 4.
  • Bookmark: getBookmarks + the two PostEntry/ThreadNode facts → BskyUnionTests; enumerate → theory. Draft: createDraft → BskyUnionTests; getDrafts → row. Embed: → the getEmbedExternalView row, now with the full view response. Graph (3) → rows. Notification: 3 → rows; the declaration record → the round-trip theory. Unspecced: getPostThreadV2 → BskyUnionTests; other → row. AgeAssurance: getConfig → BskyUnionTests; getState → row. The unknown-variant asserts in these (futurePost, threadItemFuture, the unknown rule, draftEmbedFutureThing) → OpenUnionTests.
  • FeedSurfaceTests: searchPostsV2's 30 per-key asserts → one exact AssertGet + the upstream-parameter set check (kept); filters-only → row; BindsHitsAndDetectedLanguages → drift; sendInteractions → row + a proxy/no-proxy theory.
  • VideoUploadTests: startUpload, getUploadStatus → rows; UploadPartAsync, FinishUploadAsync, AbortUploadAsync → the UploadVideoAsync_* tests, which assert those same requests (UploadVideoAsync calls the public methods).
  • BlueskyHelpersTests: like + repost (+ follow) → one theory (exact subject JSON, $type, collection).
  • BskyRecordTests: three round-trip facts → one theory.

com.atproto, root, lexgen

  • TypedRepoClientTests: getRecord/deleteRecord by AT URI, listRecords → rows; CreateRecordAsync now asserts the exact body.
  • ComAtProtoGapTests: the 3 importRepo stream facts → one theory; the 3 checkHandleAvailability variant facts → one theory + one query fact.
  • IdentityClientTests: resolveDid, refreshIdentity → rows.
  • RecordCollectionTests: HasMore true/false → theory; two GetCollection facts merged.
  • LexiconDifferTests (577 → 157): 10 Compare_* facts + 6 space facts → one theory (kind, severity and the description/path text the originals checked); 3 ToReport facts → one theory.
  • LexiconLinterTests: the 3 RPC permission facts → rows of Lint_MalformedPermission_IsAnError.

Enumerators. Http/PaginationTests tests the paginator once (a Walk theory: until-null, empty cursor, repeated cursor, cycle, empty page) and every dedicated Enumerate* with one theory: 14 rows (feed ×3, graph ×3, notifications, bookmarks, repo records, space repos/records, simplespace members, chat messages, Jetstream archive segments). Each serves pages that all repeat one cursor and asserts the exact first query (arguments + limit), the second query (first + cursor), both items read, and the stop (capped, so a regression fails instead of hanging). That replaces every per-enumerator walk, including SpaceClientTests.EnumerateRecordsAsync_FollowsPaginationAndStops, Enumerators_HostRepeatingItsCursor_StopAfterTheRepeat (its two non-enumerator rows use the paginator directly, covered by Walk) and the archive's two enumerate tests (their field checks kept as ListSegmentsAsync_ReadsEachSegmentsFields; JetstreamSegmentInfo is not a Lexicon model, so drift does not cover it). SpaceRecordView.Path kept a one-line fact.

2. T3: identifier tests

IdentifierContractTests already covers, per type: string and span Parse/TryParse, Parse(null), TryParse(null), invalid input, CompareTo/Equals, null conversions. SyntaxInteropTests runs every line of the vendored atproto-interop-tests syntax fixtures (incl. over-long values and handle lower-casing). The identifiers are records, so ==/GetHashCode are compiler-generated.

  • Dropped: Equality_* (11), ImplicitCast_ToString (4), Parse_NullValue/Parse_Null (5), TryParse_ReturnsExpected/TryParse_Valid*/TryParse_Invalid (8), Parse_Valid* theories (6), the generic Parse_Invalid* rows, Parse_ExceedsMaxLength (3), CidTests.CompareTo_IsOrdinal.
  • The explicit string conversions of Tid, Cid, AtUri → added to IdentifierContractTests.ExplicitConversionsFromString_ParseOrThrow, which lacked them.
  • Kept: did:method:val% (regression), DID method/id, handle normalization, NSID parts + the 63-char regression, the record-key character-set regression rows, NewTid/Tid.Next ordering, the TID high-bit boundary, int64 round-trip, every CID codec/digest/bytes fact and SDK-subset row (dropped only CIDv0, which is in the fixture, and ""), AT URI parts, restricted-syntax rows and Create, AtIdentifier's DID/handle split. IdentifierLineBreakTests (#125 trailing newlines) untouched. Several same-shape facts became theories.

3. T4: headers

  • XrpcClientProxyTests + XrpcClientLabelerHeaderTests → Http/XrpcClientHeaderTests: one set/cleared/never theory per header.
  • ServiceProxyTests: the two Build facts → theory; the null facts → one; the three constant-equality facts dropped. The value the SDK actually sends, the chat proxy, is now pinned as a literal in ChatClientTests.ProxyHeader_DoesNotAffectOtherXrpcCalls.

4. Other duplicates

  • Pagination stop duplicates: TypedBskyClientTests.EnumerateTimelineAsync_ServerRepeatsItsCursor and PaginationTests.EnumerateRecordsAsync_ServerRepeatsItsCursor → Walk's repeated-cursor row + the enumerator theory. The 6-row theory in TypedOzoneClientTests no longer existed on main (part 1 removed it).
  • CursoredResponses_AllImplementICursorPage → Conventions/CursorPageTests, now over every ATProtoNet.Lexicon.* namespace (was App.Bsky only; no violations).

5. T1: one HTTP stub, one DID resolver, FakeTimeProvider

  • HttpStub gained async responders (On/Fallback with a CancellationToken), Uris, the response helpers Text/Status/TxtAnswer/Never, and sets response.RequestMessage like a real handler. It replaces: Identity's shared ScriptedHandler (used across Identity, Auth/OAuth, Lexicon and Streaming tests), Tap's CapturingHandler, Jetstream's private ScriptedHandler, CapturingHandler and FakeArchive, Spaces' StubHost and FakeSpaceNetwork, and the Bluesky helpers' FakePds. The stateful ones (FakeArchive, StubHost, FakeSpaceNetwork) keep their domain state but script an HttpStub instead of overriding SendAsync.
  • TestSupport/StubDidResolver replaces Server/Spaces' FakeDidDocumentResolver (used across 18 files), Streaming's StubDidResolver, and the private StaticResolver, DelegateResolver, CountingResolver and CancellingResolver: it publishes documents (or runs a script), counts resolves, refreshes, invalidations and calls per DID, and models a pre-rotation cache.
  • FakeTimeProvider: adds Microsoft.Extensions.TimeProvider.Testing 10.10.0 (pinned, test project only). It replaces Identity's ManualClock (17 files), TidGeneratorTests.ManualTimeProvider, EfCoreSpaceStoreTests.FakeClock, DPoPInteropTests.FixedClock, and the metadata tests' RacingClock (now AutoAdvanceAmount). TidGeneratorTests.Next_ClockStepsBackwards_* uses an NSubstitute TimeProvider, because FakeTimeProvider refuses to go back in time, which is that test's point.

Deliberately left

  • Auth/SessionTestKit.ManualTimeProvider: the session and cookie-relay tests read each scheduled timer's DueTime and fire timers one by one, which FakeTimeProvider has no API for.
  • VideoUploadTests.ManualTime (records requested delays and fires them at once): #187's branch already turns it into TestSupport/InstantTimeProvider.
  • HTTP fakes outside T1's folders: Auth/SessionTestKit.StubServer (the shared auth kit), OAuthCallbackHandleVerificationTests.FlowStub, SpaceServerEndpointTests.OutboundHandler, and AtProtoBuilderTests' delegating handlers (they test the handler pipeline itself).
  • SpaceClientTests' per-endpoint request tests (Spaces is not in item 1's folders).

Notes for review

  • T3 edited Identity/ identifier-test files before Pass D merged; Pass D did not touch them, so the rebase was clean.
  • #187 (issue-187-client-clock) also edits VideoUploadTests.cs, in a different hunk (UploadVideoAsync_BacksOffExponentially and ManualTime); this branch only removed the endpoint facts near the top.

Verification

  • dotnet build -c Release -p:EnableSourceControlManagerQueries=false (whole solution): 0 warnings, 0 errors.
  • dotnet test tests/ATProtoNet.Tests/ --configuration Release -p:EnableSourceControlManagerQueries=false, twice: 4877 passed, 5 skipped, 0 failed both times. origin/main: 5018 passed, 5 skipped.
  • Spot-checked that a row fails when its expectation is wrong (a mutated expected body fails with the expected/actual diff).

🤖 Generated with Claude Code

Part of #180 (pass E, part 2: test consolidation). Part 1 (#186) built the table-driven `EndpointRequestTests`, the query-parameter drift check and the shared test kit, but kept whole files that held *some* union tests. This part applies the keep/move rules per test instead of per file, then does T3, T4 and T1. Test-only: no `src/` change, no CHANGELOG entry. **Rules applied.** Kept: union/polymorphic tests (one representative per union per client area), bespoke client logic, regression tests, security, interop and pinned vectors, cross-cutting behaviour (proxy presence/absence, headers). Moved to a table row or deleted: tests whose only assertions were method, NSID, query or body plus "a field deserializes". A row's response check is now "a realistic response (every `required` member present) deserializes": the drift tests already check every JSON name against upstream, so rows no longer re-assert fields one by one. Rows assert the **exact** body/query where the old tests only spot-checked (`Contains`). ## Numbers `python3 loc.py origin/main HEAD`, bucket `tests/ATProtoNet.Tests` (the only area touched): | | code | xmldoc | comment | files | |---|---|---|---|---| | origin/main (`e7f9e61`) | 48153 | 1551 | 1914 | 295 | | HEAD | 44842 | 1596 | 1826 | 281 | | change | **−3311** | +45 | −88 | −14 | Net −3354 counted lines (−4212 raw, blanks included). By item: items 1 and 4 −2568, T3 −470, T4 −74, T1 (plus folding the Spaces/Jetstream enumerators into the theory) −242. Test cases: **5023 → 4882** (4877 passed, 5 skipped, both runs; the skips are `AspireManifestTests`). The count drops less than the lines because most removed facts became theory rows. ## 1. Lexicon client tests (`Ozone`, `Chat`, `Lexicon`, `Bsky*`, root) `EndpointRequestTests` gained compact `Get(nsid, response, call, query, proxy)` / `Post(nsid, response, call, body, proxy)` helpers; the 53 existing rows were rewritten onto them and 108 added (161 rows in all). 18 per-endpoint files are gone; what they had beyond a plain request/response now lives in `Ozone/OzoneUnionTests`, `Chat/Bsky/ChatClientTests` and `Lexicon/App/Bsky/BskyUnionTests`. **site.standard** (`StandardSiteClientTests` 367 → 24 lines) - 15 CRUD facts + part 1's 4 delete rows → 18 rows (create/get/list/delete per collection, put for publication/document), exact body/query; typed records deserialize from the get/list responses. `ListRecommendationsAsync` gained a row. - `ListSubscriptionsAsync_RecordOfTheWrongShape` → `RecordCollectionErrorTests.ListAsync_WhenARecordIsNotTheRecordType_*` (same `Repo.ListRecordsAsync<T>` path). - `GetRecommendation_UriOfAnotherCollection` + `GetPublicationAsync_UriThatNamesNoPublication` → one theory, both overloads × 3 bad URIs. **tools.ozone** (6 files → `OzoneUnionTests`) - `OzoneQueueClientTests` (7) → rows. - `OzoneReportClientTests`: 15 request/response facts → rows. `CreateActivityAsync` kept (union write + read). `ReportActivity_UnknownType` → `OpenUnionTests.Deserialize_UnknownVariant_*` (runs for every open union base). - `OzoneToolsClientTests`: safelink, setting, verification facts → rows with exact bodies. `SafelinkPagination_SendsEachCursorInTheBody` → the queryRules/queryEvents rows (cursor in the exact body) + `PaginationTests`. `GetAccountHistoryAsync` kept (union). - `OzoneModerationToolingTests`: getAccountPreferences (its union → `LexiconUnionTests.Preferences_*`), getRecords (same union as getRepos), getSubjects, scheduleAction (exact body incl. the action `$type`), list/cancelScheduledActions → rows. `ListScheduledActionsAsync_PagedThroughPagination` → the row's cursor-in-body + `PaginationTests`. `QueryEventsAsync_EventsOzoneRecordsItself` → the `EventVariants` theory (plus a recordEvent-without-cid row). `QueryEventsAsync_MuteReporterWithoutDuration` (regression) → an `EventVariants` row with its comment. Kept: getRepos, `EventVariants`, `ModEventReport_WithoutReportType`, `RepoViewDetail_ReadDirectly`. - `TypedOzoneClientTests`: typed filters, getRecord, emitEvent, searchAccounts → rows. `GetRepoAsync_InvalidDidInTheResponse` → `TypedRepoClientTests.Response_WithAnInvalidIdentifier_*` (same mapping). `ListMembersAsync_AdminTokenAsLastUpdatedBy` → the listMembers row reads `"admin_token"` (comment kept). `QueryEventsAsync_ParsesTheTypedEventView` kept (typed identifiers through a real response). - `OzoneClientTests`: `EmitEvent_SendsTakedown` merged into the emitEvent row; `QueryStatusesAsync_Filter` (regression, "querySubjects never existed") → row with the comment; `QueryStatusesAsync_ReadsSubjectStatuses` → `LexiconUnionTests.SubjectStatusView_*` / `ModerationSubject_*` (same unions). **chat.bsky** (`ConvoClientTests`, `GroupClientTests`, `ChatModerationClientTests`, `ChatActorClientTests` → `ChatClientTests`) - Convo: `ListConvos_SendsCorrectRequest` + `ListConvosAsync_FiltersThenPaging` → one row; getConvo, muteConvo, getMessages, getLog, addReaction, getConvoForMembers, sendMessageBatch (lazy sequence), getUnreadCounts, lock/unlock → rows (exact body/query + chat proxy; the unwrapped `ConvoView`'s required members check the unwrapping). `SendMessage_PostsWithProxy` → `SendMessageAsync_ReplyEmbedAndFacets` (now also asserts the proxy). `SendMessageAsync_QuotedPost` → a row (exact body incl. `app.bsky.embed.record`). `EnumerateMessagesAsync_RepeatedCursor` → the enumerator theory. - Group: all but `GetJoinLinkPreviewsAsync` (kept, union) → 17 rows. - Moderation: 5 facts → rows; `GetConvosAsync` kept (union; now also asserts what the dropped `GetConvoAsync` checked); `Calls_WithoutAClientWideProxy` kept. The unused private `AssertPost` helper (claude-bot note on #186) went with the file. - `ChatActorClientTests`' one record test → `ConvoModelsTests`. - `ConvoModelsTests`: `Deserialize_SystemMessageDataMemberJoin_ReadsWhoApproved` and `Deserialize_LogAddReaction_*` → their rows in the existing variant theories (which round-trip every field); `Deserialize_GetMessagesPage_*` → the `GroupConvoView`, `DirectConvoWithoutKind` and `MessageView` facts (same `ConvoMessage` union). **app.bsky** (`TypedBskyClientTests`, Bookmark/Draft/Embed/Graph/Notification/Unspecced/AgeAssurance files → `BskyUnionTests` + rows) - `TypedBskyClientTests`: getAuthorFeed, searchPosts, getPosts, listNotifications (regression "seenAt is deliberately not a parameter": row with the comment) → rows; their typed-response asserts → `OzoneUnionTests.QueryEventsAsync_ParsesTheTypedEventView` and `TypedRepoClientTests.CreateRecordAsync` (one per identifier type through a real response) + `TypedIdentifierGuardTests` + drift. The three `Enumerate*` walks → the enumerator theory. `PostAsync_WritesACanonicalCreatedAt` → merged into `BlueskyHelpersTests.PostAsync_PlainString`. `FollowAsync` → `BlueskyHelpersTests.SubjectHelpers_*`. `PostRecord_RoundTrip` → `BskyRecordTests`' round-trip theory. `CursoredResponses_AllImplementICursorPage` → item 4. - Bookmark: getBookmarks + the two `PostEntry`/`ThreadNode` facts → `BskyUnionTests`; enumerate → theory. Draft: createDraft → `BskyUnionTests`; getDrafts → row. Embed: → the getEmbedExternalView row, now with the full view response. Graph (3) → rows. Notification: 3 → rows; the declaration record → the round-trip theory. Unspecced: getPostThreadV2 → `BskyUnionTests`; other → row. AgeAssurance: getConfig → `BskyUnionTests`; getState → row. The unknown-variant asserts in these (`futurePost`, `threadItemFuture`, the unknown rule, `draftEmbedFutureThing`) → `OpenUnionTests`. - `FeedSurfaceTests`: searchPostsV2's 30 per-key asserts → one exact `AssertGet` + the upstream-parameter set check (kept); filters-only → row; `BindsHitsAndDetectedLanguages` → drift; sendInteractions → row + a proxy/no-proxy theory. - `VideoUploadTests`: startUpload, getUploadStatus → rows; `UploadPartAsync`, `FinishUploadAsync`, `AbortUploadAsync` → the `UploadVideoAsync_*` tests, which assert those same requests (`UploadVideoAsync` calls the public methods). - `BlueskyHelpersTests`: like + repost (+ follow) → one theory (exact subject JSON, `$type`, collection). - `BskyRecordTests`: three round-trip facts → one theory. **com.atproto, root, lexgen** - `TypedRepoClientTests`: getRecord/deleteRecord by AT URI, listRecords → rows; `CreateRecordAsync` now asserts the exact body. - `ComAtProtoGapTests`: the 3 importRepo stream facts → one theory; the 3 checkHandleAvailability variant facts → one theory + one query fact. - `IdentityClientTests`: resolveDid, refreshIdentity → rows. - `RecordCollectionTests`: `HasMore` true/false → theory; two `GetCollection` facts merged. - `LexiconDifferTests` (577 → 157): 10 `Compare_*` facts + 6 space facts → one theory (kind, severity and the description/path text the originals checked); 3 `ToReport` facts → one theory. - `LexiconLinterTests`: the 3 RPC permission facts → rows of `Lint_MalformedPermission_IsAnError`. **Enumerators.** `Http/PaginationTests` tests the paginator once (a `Walk` theory: until-null, empty cursor, repeated cursor, cycle, empty page) and every dedicated `Enumerate*` with one theory: 14 rows (feed ×3, graph ×3, notifications, bookmarks, repo records, space repos/records, simplespace members, chat messages, Jetstream archive segments). Each serves pages that all repeat one cursor and asserts the exact first query (arguments + `limit`), the second query (first + `cursor`), both items read, and the stop (capped, so a regression fails instead of hanging). That replaces every per-enumerator walk, including `SpaceClientTests.EnumerateRecordsAsync_FollowsPaginationAndStops`, `Enumerators_HostRepeatingItsCursor_StopAfterTheRepeat` (its two non-enumerator rows use the paginator directly, covered by `Walk`) and the archive's two enumerate tests (their field checks kept as `ListSegmentsAsync_ReadsEachSegmentsFields`; `JetstreamSegmentInfo` is not a Lexicon model, so drift does not cover it). `SpaceRecordView.Path` kept a one-line fact. ## 2. T3: identifier tests `IdentifierContractTests` already covers, per type: string and span `Parse`/`TryParse`, `Parse(null)`, `TryParse(null)`, invalid input, `CompareTo`/`Equals`, null conversions. `SyntaxInteropTests` runs every line of the vendored atproto-interop-tests syntax fixtures (incl. over-long values and handle lower-casing). The identifiers are records, so `==`/`GetHashCode` are compiler-generated. - Dropped: `Equality_*` (11), `ImplicitCast_ToString` (4), `Parse_NullValue`/`Parse_Null` (5), `TryParse_ReturnsExpected`/`TryParse_Valid*`/`TryParse_Invalid` (8), `Parse_Valid*` theories (6), the generic `Parse_Invalid*` rows, `Parse_ExceedsMaxLength` (3), `CidTests.CompareTo_IsOrdinal`. - The explicit string conversions of `Tid`, `Cid`, `AtUri` → added to `IdentifierContractTests.ExplicitConversionsFromString_ParseOrThrow`, which lacked them. - Kept: `did:method:val%` (regression), DID method/id, handle normalization, NSID parts + the 63-char regression, the record-key character-set regression rows, `NewTid`/`Tid.Next` ordering, the TID high-bit boundary, int64 round-trip, every CID codec/digest/bytes fact and SDK-subset row (dropped only CIDv0, which is in the fixture, and `""`), AT URI parts, restricted-syntax rows and `Create`, AtIdentifier's DID/handle split. `IdentifierLineBreakTests` (#125 trailing newlines) untouched. Several same-shape facts became theories. ## 3. T4: headers - `XrpcClientProxyTests` + `XrpcClientLabelerHeaderTests` → `Http/XrpcClientHeaderTests`: one set/cleared/never theory per header. - `ServiceProxyTests`: the two `Build` facts → theory; the null facts → one; the three constant-equality facts dropped. The value the SDK actually sends, the chat proxy, is now pinned as a literal in `ChatClientTests.ProxyHeader_DoesNotAffectOtherXrpcCalls`. ## 4. Other duplicates - Pagination stop duplicates: `TypedBskyClientTests.EnumerateTimelineAsync_ServerRepeatsItsCursor` and `PaginationTests.EnumerateRecordsAsync_ServerRepeatsItsCursor` → `Walk`'s repeated-cursor row + the enumerator theory. The 6-row theory in `TypedOzoneClientTests` no longer existed on main (part 1 removed it). - `CursoredResponses_AllImplementICursorPage` → `Conventions/CursorPageTests`, now over every `ATProtoNet.Lexicon.*` namespace (was `App.Bsky` only; no violations). ## 5. T1: one HTTP stub, one DID resolver, FakeTimeProvider - **`HttpStub`** gained async responders (`On`/`Fallback` with a `CancellationToken`), `Uris`, the response helpers `Text`/`Status`/`TxtAnswer`/`Never`, and sets `response.RequestMessage` like a real handler. It replaces: Identity's shared `ScriptedHandler` (used across Identity, Auth/OAuth, Lexicon and Streaming tests), Tap's `CapturingHandler`, Jetstream's private `ScriptedHandler`, `CapturingHandler` and `FakeArchive`, Spaces' `StubHost` and `FakeSpaceNetwork`, and the Bluesky helpers' `FakePds`. The stateful ones (`FakeArchive`, `StubHost`, `FakeSpaceNetwork`) keep their domain state but script an `HttpStub` instead of overriding `SendAsync`. - **`TestSupport/StubDidResolver`** replaces Server/Spaces' `FakeDidDocumentResolver` (used across 18 files), Streaming's `StubDidResolver`, and the private `StaticResolver`, `DelegateResolver`, `CountingResolver` and `CancellingResolver`: it publishes documents (or runs a script), counts resolves, refreshes, invalidations and calls per DID, and models a pre-rotation cache. - **`FakeTimeProvider`**: adds `Microsoft.Extensions.TimeProvider.Testing` 10.10.0 (pinned, test project only). It replaces Identity's `ManualClock` (17 files), `TidGeneratorTests.ManualTimeProvider`, `EfCoreSpaceStoreTests.FakeClock`, `DPoPInteropTests.FixedClock`, and the metadata tests' `RacingClock` (now `AutoAdvanceAmount`). `TidGeneratorTests.Next_ClockStepsBackwards_*` uses an NSubstitute `TimeProvider`, because `FakeTimeProvider` refuses to go back in time, which is that test's point. ## Deliberately left - `Auth/SessionTestKit.ManualTimeProvider`: the session and cookie-relay tests read each scheduled timer's `DueTime` and fire timers one by one, which `FakeTimeProvider` has no API for. - `VideoUploadTests.ManualTime` (records requested delays and fires them at once): #187's branch already turns it into `TestSupport/InstantTimeProvider`. - HTTP fakes outside T1's folders: `Auth/SessionTestKit.StubServer` (the shared auth kit), `OAuthCallbackHandleVerificationTests.FlowStub`, `SpaceServerEndpointTests.OutboundHandler`, and `AtProtoBuilderTests`' delegating handlers (they test the handler pipeline itself). - `SpaceClientTests`' per-endpoint request tests (Spaces is not in item 1's folders). ## Notes for review - T3 edited `Identity/` identifier-test files before Pass D merged; Pass D did not touch them, so the rebase was clean. - #187 (`issue-187-client-clock`) also edits `VideoUploadTests.cs`, in a different hunk (`UploadVideoAsync_BacksOffExponentially` and `ManualTime`); this branch only removed the endpoint facts near the top. ## Verification - `dotnet build -c Release -p:EnableSourceControlManagerQueries=false` (whole solution): 0 warnings, 0 errors. - `dotnet test tests/ATProtoNet.Tests/ --configuration Release -p:EnableSourceControlManagerQueries=false`, twice: 4877 passed, 5 skipped, 0 failed both times. origin/main: 5018 passed, 5 skipped. - Spot-checked that a row fails when its expectation is wrong (a mutated expected body fails with the expected/actual diff). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2, T3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2, T4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HttpStub gains async responders and the shared response helpers (Text,
Status, TxtAnswer, Never) and sets RequestMessage like a real handler.

WIP, part of #180 (pass E, part 2, T1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2, T1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2, T1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2, T1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces Server/Spaces' FakeDidDocumentResolver, Streaming's StubDidResolver,
and the private resolvers in IdentityHardeningTests, CachingDidResolverTests
and LabelVerifierTests.

WIP, part of #180 (pass E, part 2, T1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds Microsoft.Extensions.TimeProvider.Testing 10.10.0 to the unit tests.
Identity's shared ManualClock, TidGeneratorTests' ManualTimeProvider,
EfCoreSpaceStoreTests' FakeClock, DPoPInteropTests' FixedClock and the
metadata tests' RacingClock (now AutoAdvanceAmount) go.

WIP, part of #180 (pass E, part 2, T1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP, part of #180 (pass E, part 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test: the Jetstream archive's segment enumerator joins the enumerator theory (#180)
All checks were successful
CI / pds-integration (pull_request) Successful in 30s
CI / build-and-test (pull_request) Successful in 58s
Sync Closures to GitHub / sync-closure (pull_request) Successful in 6s
bc8aece419
WIP, part of #180 (pass E, part 2).

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

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/ATProto.NET/pr-189/run-2026-09-27T20-52-46-986Z.md) — updating every ~5 minutes while the review runs.
Collaborator

⏳ Held on the OpenCode Go window — the 5-hour window is at 0% (the bot holds at 95%).
This continues automatically after the window resets (2026-09-28T01:52:47.410Z) — nothing to re-trigger.

⏳ **Held on the OpenCode Go window** — the 5-hour window is at 0% (the bot holds at 95%). This continues automatically after the window resets (2026-09-28T01:52:47.410Z) — nothing to re-trigger. <!-- opencode-run:paused --> <!-- opencode-session:ses_f1b5bafdaffebWz7QCfGmyAuEA -->
Author
Owner

@claude /review, please. This is part 2 of pass E of #180. It changes tests only (no src/ changes) and removes 3,354 net test lines. The description lists every removed test or group and what covers it now.

The diff is large, so please focus on:

  1. EndpointRequestTests (161 rows). Does each row assert at least what the per-endpoint test it replaced asserted? Spot-check rows against the removed files.
  2. The enumerator theory (14 rows) and the Walk theory. Every Enumerate* must still be pinned for its first query, the cursor it sends, the items it reads, and stopping on a repeated cursor.
  3. Kept union tests (OzoneUnionTests, ChatClientTests, BskyUnionTests). Is at least one test left per union shape?
  4. Shared fakes. Do HttpStub (with its new async responders), TestSupport/StubDidResolver and FakeTimeProvider weaken any assertion the private fakes made? That includes an unscripted request that used to fail loudly.
  5. Removed regression and security tests. Were any deleted? None should be.
@claude /review, please. This is part 2 of pass E of #180. It changes tests only (no `src/` changes) and removes 3,354 net test lines. The description lists every removed test or group and what covers it now. The diff is large, so please focus on: 1. **`EndpointRequestTests` (161 rows).** Does each row assert at least what the per-endpoint test it replaced asserted? Spot-check rows against the removed files. 2. **The enumerator theory (14 rows) and the `Walk` theory.** Every `Enumerate*` must still be pinned for its first query, the cursor it sends, the items it reads, and stopping on a repeated cursor. 3. **Kept union tests** (`OzoneUnionTests`, `ChatClientTests`, `BskyUnionTests`). Is at least one test left per union shape? 4. **Shared fakes.** Do `HttpStub` (with its new async responders), `TestSupport/StubDidResolver` and `FakeTimeProvider` weaken any assertion the private fakes made? That includes an unscripted request that used to fail loudly. 5. **Removed regression and security tests.** Were any deleted? None should be.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/645); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-189/run-645.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

This is a large, purely mechanical test-consolidation PR (no src/ changes) that moves per-endpoint request/response facts into the table-driven EndpointRequestTests, drops union-of-effort duplicate assertions in favor of the drift check, and unifies HTTP/DID/clock test doubles behind HttpStub, StubDidResolver, and FakeTimeProvider.

I spot-checked the riskiest mechanical substitutions rather than every hunk (the diff is truncated in the prompt anyway):

  • HttpStub.On(...) queues responses per route (Queue<Func<...>>) and on exhaustion peeks the last one forever — this exactly supports the BlueskyHelpersTests.LoggedInAsync(conflicts:) pattern (N BadRequest putRecord responses queued, then a final success that persists), so the FakePds → HttpStub rewrite preserves the original retry-then-succeed semantics.
  • The RacingClock → new FakeTimeProvider(...) { AutoAdvanceAmount = ... } substitution in AuthorizationServerMetadataValidationTests produces the same sequence of readings (first read at +1h, advancing +1h per subsequent read), so the cache-bypass test still exercises what it intended.
  • ManualClock → FakeTimeProvider and its .Advance(...) calls line up with the real Microsoft.Extensions.TimeProvider.Testing API.
  • The new PaginationTests.Enumerators theory's NSID list (e.g. network.bsky.jetstream.listSegments) matches the real JetstreamArchiveClient path constant, so the consolidated enumerator theory targets a real endpoint rather than a typo'd one.
  • Moved/dropped assertions (e.g. ChatModerationClientTests.GetConvoAsync folded into the kept GetConvosAsync/ModerationGetConvosAsync_ReadsEachConvoKind, ConvoModelsTests' per-field facts folded into existing round-trip theories) do appear to retain the same field coverage the PR description claims they subsume.

Given the PR's own verification (0 warnings/errors on a Release build, 4877 passed/5 skipped twice, and a stated spot-check that a mutated expectation fails), combined with these targeted checks turning up nothing inconsistent, I have no blocking findings. Nice, disciplined pass — no scope creep into src/ and the keep/move rules from the description are applied consistently everywhere I looked.


⏱ 1m 35s · 9 turns · tokens: 103.6k in / 6.2k out (+969.8k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. This is a large, purely mechanical test-consolidation PR (no `src/` changes) that moves per-endpoint request/response facts into the table-driven `EndpointRequestTests`, drops union-of-effort duplicate assertions in favor of the drift check, and unifies HTTP/DID/clock test doubles behind `HttpStub`, `StubDidResolver`, and `FakeTimeProvider`. I spot-checked the riskiest mechanical substitutions rather than every hunk (the diff is truncated in the prompt anyway): - `HttpStub.On(...)` queues responses per route (`Queue<Func<...>>`) and on exhaustion peeks the last one forever — this exactly supports the `BlueskyHelpersTests.LoggedInAsync(conflicts:)` pattern (N `BadRequest` `putRecord` responses queued, then a final success that persists), so the `FakePds` → `HttpStub` rewrite preserves the original retry-then-succeed semantics. - The `RacingClock` → `new FakeTimeProvider(...) { AutoAdvanceAmount = ... }` substitution in `AuthorizationServerMetadataValidationTests` produces the same sequence of readings (first read at +1h, advancing +1h per subsequent read), so the cache-bypass test still exercises what it intended. - `ManualClock` → `FakeTimeProvider` and its `.Advance(...)` calls line up with the real `Microsoft.Extensions.TimeProvider.Testing` API. - The new `PaginationTests.Enumerators` theory's NSID list (e.g. `network.bsky.jetstream.listSegments`) matches the real `JetstreamArchiveClient` path constant, so the consolidated enumerator theory targets a real endpoint rather than a typo'd one. - Moved/dropped assertions (e.g. `ChatModerationClientTests.GetConvoAsync` folded into the kept `GetConvosAsync`/`ModerationGetConvosAsync_ReadsEachConvoKind`, `ConvoModelsTests`' per-field facts folded into existing round-trip theories) do appear to retain the same field coverage the PR description claims they subsume. Given the PR's own verification (0 warnings/errors on a Release build, 4877 passed/5 skipped twice, and a stated spot-check that a mutated expectation fails), combined with these targeted checks turning up nothing inconsistent, I have no blocking findings. Nice, disciplined pass — no scope creep into `src/` and the keep/move rules from the description are applied consistently everywhere I looked. --- ⏱ 1m 35s · 9 turns · tokens: 103.6k in / 6.2k out (+969.8k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/pr-189/run-645.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:bc8aece419843b7193c5b1016ac2d47949360d6b -->
Grandiras deleted branch debloat-e2-tests 2026-09-27 20:55:18 +00:00
Sign in to join this conversation.
No description provided.