From 05e3050851d8c54d9ab97ea1158536605a69146e Mon Sep 17 00:00:00 2001 From: Stephen Cooper <1970249+scooper4711@users.noreply.github.com> Date: Mon, 19 Oct 2026 18:52:10 +0200 Subject: [PATCH] test: Make the stop-at-the-first-failure check deterministic c fails only after a retry, two requests, while d, e and f answered at once, so at times they finished first and g was asked for. d's lookup now waits until it is canceled, so c's failure always lands while d runs, and the test expects exactly a and b, and no request for e. FileStatusFetcher itself was right: it returns what it learned before the failure. --- .../UpdateCheckPolicyTests.swift | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Tests/ScrollkeeperKitTests/UpdateCheckPolicyTests.swift b/Tests/ScrollkeeperKitTests/UpdateCheckPolicyTests.swift index 7d3a079..5536641 100644 --- a/Tests/ScrollkeeperKitTests/UpdateCheckPolicyTests.swift +++ b/Tests/ScrollkeeperKitTests/UpdateCheckPolicyTests.swift @@ -119,10 +119,11 @@ import Testing private let paizo = FakePaizo() private let updated = "Tue Jul 21 2026 20:35:38 GMT+0000 (Coordinated Universal Time)" - private func makeClient() -> LibraryCatalogClient { + /// A signed-in client whose library requests go to `http`, or to the fake Paizo when nil. + private func makeClient(http: (any HTTPClient)? = nil) -> LibraryCatalogClient { paizo.installSignedInStore() let session = PaizoSession(http: paizo.http, credentials: MemoryCredentialStore(FakePaizo.account)) - return LibraryCatalogClient(http: paizo.http, session: session) + return LibraryCatalogClient(http: http ?? paizo.http, session: session) } @Test func fileStatusCarriesTheUpdateDateAndTheCurrentFile() async throws { @@ -160,13 +161,16 @@ import Testing @Test func fetchingStopsAtTheFirstFailure() async { paizo.installFileStatus(updated: ["a": "", "b": "", "d": "", "e": "", "f": "", "g": ""]) - let fetcher = FileStatusFetcher(client: makeClient(), concurrentLookups: 2) + // d's lookup only ends when it is canceled, so c's failure, after its retry, lands while d is still + // running, however the lookups are scheduled. Answering d at once let d, e and f finish first at times. + let client = makeClient(http: HoldingHTTPClient(inner: paizo.http, held: ["d"])) + let fetcher = FileStatusFetcher(client: client, concurrentLookups: 2) let learned = await fetcher.fetch(["a", "b", "c", "d", "e", "f", "g"]) - #expect(Set(learned.map(\.packageID)).isSuperset(of: ["a", "b"])) - #expect(!learned.map(\.packageID).contains("c")) - #expect(paizo.http.count(of: "/customer/g?") == 0) + #expect(Set(learned.map(\.packageID)) == ["a", "b"]) + #expect(paizo.http.count(of: "/customer/c?") == 2) + #expect(paizo.http.count(of: "/customer/e?") == 0) } @Test func fetchingNothingAsksNothing() async { @@ -191,3 +195,21 @@ import Testing #expect(entitlement.fileName == "v2.pdf") } } + +/// Answers like `inner`, except that requests for the `held` packages wait until they are canceled, so a test +/// decides which lookups are still running when another one fails. +private struct HoldingHTTPClient: HTTPClient { + let inner: StubHTTPClient + let held: Set + + func send(_ request: URLRequest) async throws -> HTTPResponse { + if held.contains(request.url?.lastPathComponent ?? "") { + try await Task.sleep(for: .seconds(3600)) + } + return try await inner.send(request) + } + + func download(_ request: URLRequest, to destination: URL, progress: @escaping ProgressHandler) async throws { + try await inner.download(request, to: destination, progress: progress) + } +}