test: consolidate the test suite (table-driven endpoints and enumerators, shared fakes) #189
No reviewers
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET!189
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "debloat-e2-tests"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #180 (pass E, part 2: test consolidation).
Part 1 (#186) built the table-driven
EndpointRequestTests, the query-parameter drift check and theshared 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, noCHANGELOG 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
requiredmember present) deserializes": the drift tests alreadycheck 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, buckettests/ATProtoNet.Tests(the only area touched):e7f9e61)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)EndpointRequestTestsgained compactGet(nsid, response, call, query, proxy)/Post(nsid, response, call, body, proxy)helpers; the 53 existing rows were rewritten onto themand 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/ChatClientTestsandLexicon/App/Bsky/BskyUnionTests.site.standard (
StandardSiteClientTests367 → 24 lines)publication/document), exact body/query; typed records deserialize from the get/list responses.
ListRecommendationsAsyncgained a row.ListSubscriptionsAsync_RecordOfTheWrongShape→RecordCollectionErrorTests.ListAsync_WhenARecordIsNotTheRecordType_*(sameRepo.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.CreateActivityAsynckept (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.GetAccountHistoryAsynckept (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→ theEventVariantstheory (plus a recordEvent-without-cid row).QueryEventsAsync_MuteReporterWithoutDuration(regression) → anEventVariantsrow 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_ParsesTheTypedEventViewkept (typed identifiers through a real response).OzoneClientTests:EmitEvent_SendsTakedownmerged 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)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 unwrappedConvoView'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.GetJoinLinkPreviewsAsync(kept, union) → 17 rows.GetConvosAsynckept (union; now also asserts what the droppedGetConvoAsyncchecked);Calls_WithoutAClientWideProxykept. The unused privateAssertPosthelper (claude-bot note on #186) went with the file.ChatActorClientTests' one record test →ConvoModelsTests.ConvoModelsTests:Deserialize_SystemMessageDataMemberJoin_ReadsWhoApprovedandDeserialize_LogAddReaction_*→ their rows in the existing variant theories (which round-trip every field);Deserialize_GetMessagesPage_*→ theGroupConvoView,DirectConvoWithoutKindandMessageViewfacts (sameConvoMessageunion).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_ParsesTheTypedEventViewandTypedRepoClientTests.CreateRecordAsync(one per identifier type through a real response) +TypedIdentifierGuardTests+ drift. The threeEnumerate*walks → the enumerator theory.PostAsync_WritesACanonicalCreatedAt→ merged intoBlueskyHelpersTests.PostAsync_PlainString.FollowAsync→BlueskyHelpersTests.SubjectHelpers_*.PostRecord_RoundTrip→BskyRecordTests' round-trip theory.CursoredResponses_AllImplementICursorPage→ item 4.PostEntry/ThreadNodefacts →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 exactAssertGet+ 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→ theUploadVideoAsync_*tests, which assert those same requests (UploadVideoAsynccalls 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;CreateRecordAsyncnow 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:HasMoretrue/false → theory; twoGetCollectionfacts merged.LexiconDifferTests(577 → 157): 10Compare_*facts + 6 space facts → one theory (kind, severity and the description/path text the originals checked); 3ToReportfacts → one theory.LexiconLinterTests: the 3 RPC permission facts → rows ofLint_MalformedPermission_IsAnError.Enumerators.
Http/PaginationTeststests the paginator once (aWalktheory: until-null, empty cursor, repeated cursor, cycle, empty page) and every dedicatedEnumerate*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, includingSpaceClientTests.EnumerateRecordsAsync_FollowsPaginationAndStops,Enumerators_HostRepeatingItsCursor_StopAfterTheRepeat(its two non-enumerator rows use the paginator directly, covered byWalk) and the archive's two enumerate tests (their field checks kept asListSegmentsAsync_ReadsEachSegmentsFields;JetstreamSegmentInfois not a Lexicon model, so drift does not cover it).SpaceRecordView.Pathkept a one-line fact.2. T3: identifier tests
IdentifierContractTestsalready covers, per type: string and spanParse/TryParse,Parse(null),TryParse(null), invalid input,CompareTo/Equals, null conversions.SyntaxInteropTestsruns every line of the vendored atproto-interop-tests syntax fixtures (incl. over-long values and handle lower-casing). The identifiers are records, so==/GetHashCodeare compiler-generated.Equality_*(11),ImplicitCast_ToString(4),Parse_NullValue/Parse_Null(5),TryParse_ReturnsExpected/TryParse_Valid*/TryParse_Invalid(8),Parse_Valid*theories (6), the genericParse_Invalid*rows,Parse_ExceedsMaxLength(3),CidTests.CompareTo_IsOrdinal.Tid,Cid,AtUri→ added toIdentifierContractTests.ExplicitConversionsFromString_ParseOrThrow, which lacked them.did:method:val%(regression), DID method/id, handle normalization, NSID parts + the 63-char regression, the record-key character-set regression rows,NewTid/Tid.Nextordering, 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 andCreate, 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 twoBuildfacts → 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 inChatClientTests.ProxyHeader_DoesNotAffectOtherXrpcCalls.4. Other duplicates
TypedBskyClientTests.EnumerateTimelineAsync_ServerRepeatsItsCursorandPaginationTests.EnumerateRecordsAsync_ServerRepeatsItsCursor→Walk's repeated-cursor row + the enumerator theory. The 6-row theory inTypedOzoneClientTestsno longer existed on main (part 1 removed it).CursoredResponses_AllImplementICursorPage→Conventions/CursorPageTests, now over everyATProtoNet.Lexicon.*namespace (wasApp.Bskyonly; no violations).5. T1: one HTTP stub, one DID resolver, FakeTimeProvider
HttpStubgained async responders (On/Fallbackwith aCancellationToken),Uris, the response helpersText/Status/TxtAnswer/Never, and setsresponse.RequestMessagelike a real handler. It replaces: Identity's sharedScriptedHandler(used across Identity, Auth/OAuth, Lexicon and Streaming tests), Tap'sCapturingHandler, Jetstream's privateScriptedHandler,CapturingHandlerandFakeArchive, Spaces'StubHostandFakeSpaceNetwork, and the Bluesky helpers'FakePds. The stateful ones (FakeArchive,StubHost,FakeSpaceNetwork) keep their domain state but script anHttpStubinstead of overridingSendAsync.TestSupport/StubDidResolverreplaces Server/Spaces'FakeDidDocumentResolver(used across 18 files), Streaming'sStubDidResolver, and the privateStaticResolver,DelegateResolver,CountingResolverandCancellingResolver: it publishes documents (or runs a script), counts resolves, refreshes, invalidations and calls per DID, and models a pre-rotation cache.FakeTimeProvider: addsMicrosoft.Extensions.TimeProvider.Testing10.10.0 (pinned, test project only). It replaces Identity'sManualClock(17 files),TidGeneratorTests.ManualTimeProvider,EfCoreSpaceStoreTests.FakeClock,DPoPInteropTests.FixedClock, and the metadata tests'RacingClock(nowAutoAdvanceAmount).TidGeneratorTests.Next_ClockStepsBackwards_*uses an NSubstituteTimeProvider, becauseFakeTimeProviderrefuses 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'sDueTimeand fire timers one by one, whichFakeTimeProviderhas no API for.VideoUploadTests.ManualTime(records requested delays and fires them at once): #187's branch already turns it intoTestSupport/InstantTimeProvider.Auth/SessionTestKit.StubServer(the shared auth kit),OAuthCallbackHandleVerificationTests.FlowStub,SpaceServerEndpointTests.OutboundHandler, andAtProtoBuilderTests' delegating handlers (they test the handler pipeline itself).SpaceClientTests' per-endpoint request tests (Spaces is not in item 1's folders).Notes for review
Identity/identifier-test files before Pass D merged; Pass D did not touch them, so the rebase was clean.issue-187-client-clock) also editsVideoUploadTests.cs, in a different hunk (UploadVideoAsync_BacksOffExponentiallyandManualTime); 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.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
⏳ 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.
@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:
EndpointRequestTests(161 rows). Does each row assert at least what the per-endpoint test it replaced asserted? Spot-check rows against the removed files.Walktheory. EveryEnumerate*must still be pinned for its first query, the cursor it sends, the items it reads, and stopping on a repeated cursor.OzoneUnionTests,ChatClientTests,BskyUnionTests). Is at least one test left per union shape?HttpStub(with its new async responders),TestSupport/StubDidResolverandFakeTimeProviderweaken any assertion the private fakes made? That includes an unscripted request that used to fail loudly.🤖 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.
✅ 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-drivenEndpointRequestTests, drops union-of-effort duplicate assertions in favor of the drift check, and unifies HTTP/DID/clock test doubles behindHttpStub,StubDidResolver, andFakeTimeProvider.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 theBlueskyHelpersTests.LoggedInAsync(conflicts:)pattern (NBadRequestputRecordresponses queued, then a final success that persists), so theFakePds→HttpStubrewrite preserves the original retry-then-succeed semantics.RacingClock→new FakeTimeProvider(...) { AutoAdvanceAmount = ... }substitution inAuthorizationServerMetadataValidationTestsproduces 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→FakeTimeProviderand its.Advance(...)calls line up with the realMicrosoft.Extensions.TimeProvider.TestingAPI.PaginationTests.Enumeratorstheory's NSID list (e.g.network.bsky.jetstream.listSegments) matches the realJetstreamArchiveClientpath constant, so the consolidated enumerator theory targets a real endpoint rather than a typo'd one.ChatModerationClientTests.GetConvoAsyncfolded into the keptGetConvosAsync/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