Commit ac81a4d179
ac81a4d179fae9ec7631b4587c5909352c532077
parent: 982ec60b32
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-07 02:22 UTC
Guard direct stub.seen indexing with #require
A raw stub.seen[n] crashes the whole test process the moment a
regression leaves fewer requests than expected, instead of failing
just that one test. Replace every unguarded index (and aliases of
stub.seen) with #require(stub.seen.dropFirst(n).first), which throws
a legible per-test failure instead.
Layout: unified · split
gitbayTests/BlameEditTests.swift
+2 −1
| @@ -64,7 +64,8 @@ struct BlameViewModelTests { |
| 64 | 64 | await model.loadMore() |
| 65 | 65 | #expect(model.hunks.count == 2) |
| 66 | 66 | #expect(!model.hasMore) |
| 67 | | #expect(stub.seen[1].url.query()?.contains("argv=--from&argv=1001") == true) |
| 67 | let second = try #require(stub.seen.dropFirst().first) |
| 68 | #expect(second.url.query()?.contains("argv=--from&argv=1001") == true) |
| 68 | 69 | |
| 69 | 70 | // Nothing further to ask for. |
| 70 | 71 | await model.loadMore() |
gitbayTests/ExploreRefLogTests.swift
+2 −1
| @@ -84,7 +84,8 @@ struct ExploreTests { |
| 84 | 84 | await list.loadMore() |
| 85 | 85 | #expect(list.state.value?.count == 3) |
| 86 | 86 | #expect(!list.hasMore) |
| 87 | | #expect(stub.seen[1].url.query()?.contains("argv=--cursor&argv=ZXhwbG9yZTpr") == true) |
| 87 | let second = try #require(stub.seen.dropFirst().first) |
| 88 | #expect(second.url.query()?.contains("argv=--cursor&argv=ZXhwbG9yZTpr") == true) |
| 88 | 89 | } |
| 89 | 90 | |
| 90 | 91 | @Test func ownerAndNameSplitForDisplay() { |
gitbayTests/GitbayClientTests.swift
+6 −3
| @@ -101,8 +101,10 @@ struct GitbayClientReadTests { |
| 101 | 101 | #expect(first[0].path == "krz/gitbay") |
| 102 | 102 | let requests = stub.seen |
| 103 | 103 | #expect(requests.count == 2) |
| 104 | | #expect(requests[0].headers["If-None-Match"] == nil) |
| 105 | | #expect(requests[1].headers["If-None-Match"] == etag) |
| 104 | let firstRequest = try #require(requests.first) |
| 105 | #expect(firstRequest.headers["If-None-Match"] == nil) |
| 106 | let secondRequest = try #require(requests.dropFirst().first) |
| 107 | #expect(secondRequest.headers["If-None-Match"] == etag) |
| 106 | 108 | } |
| 107 | 109 | |
| 108 | 110 | @Test func changedBodyReplacesTheCachedValidator() async throws { |
| @@ -117,7 +119,8 @@ struct GitbayClientReadTests { |
| 117 | 119 | |
| 118 | 120 | #expect(refreshed.isEmpty) |
| 119 | 121 | #expect(revalidated.isEmpty) |
| 120 | | #expect(stub.seen[2].headers["If-None-Match"] == "\"bb\"") |
| 122 | let third = try #require(stub.seen.dropFirst(2).first) |
| 123 | #expect(third.headers["If-None-Match"] == "\"bb\"") |
| 121 | 124 | } |
| 122 | 125 | |
| 123 | 126 | @Test func absentDataDecodesAsEmptyList() async throws { |
gitbayTests/MRViewModelTests.swift
+6 −5
| @@ -185,7 +185,8 @@ struct MRListViewModelTests { |
| 185 | 185 | } |
| 186 | 186 | |
| 187 | 187 | #expect(stub.seen.count == 2) |
| 188 | | #expect(stub.seen[1].url.query()?.contains("argv=merged") == true) |
| 188 | let second = try #require(stub.seen.dropFirst().first) |
| 189 | #expect(second.url.query()?.contains("argv=merged") == true) |
| 189 | 190 | guard case .empty = model.state else { |
| 190 | 191 | Issue.record("expected .empty after filtering, got \(model.state)") |
| 191 | 192 | return |
| @@ -264,7 +265,7 @@ struct MRDetailViewModelTests { |
| 264 | 265 | |
| 265 | 266 | await model.review(.approve) |
| 266 | 267 | |
| 267 | | let write = stub.seen[3] |
| 268 | let write = try #require(stub.seen.dropFirst(3).first) |
| 268 | 269 | #expect(write.method == "POST") |
| 269 | 270 | #expect(write.url.path() == "/api/v1/cmd") |
| 270 | 271 | let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any]) |
| @@ -282,7 +283,7 @@ struct MRDetailViewModelTests { |
| 282 | 283 | |
| 283 | 284 | await model.comment("long review text\nwith lines") |
| 284 | 285 | |
| 285 | | let write = stub.seen[3] |
| 286 | let write = try #require(stub.seen.dropFirst(3).first) |
| 286 | 287 | let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any]) |
| 287 | 288 | #expect(body["argv"] as? [String] == ["mr", "comment", "krz/gitbay", "7", "--file", "-"]) |
| 288 | 289 | #expect(body["stdin"] as? String == "long review text\nwith lines") |
| @@ -311,7 +312,7 @@ struct MRDetailViewModelTests { |
| 311 | 312 | |
| 312 | 313 | await model.setResolved(thread, true) |
| 313 | 314 | |
| 314 | | let write = stub.seen[3] |
| 315 | let write = try #require(stub.seen.dropFirst(3).first) |
| 315 | 316 | let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any]) |
| 316 | 317 | #expect(body["argv"] as? [String] == ["mr", "resolve", "krz/gitbay", "7", "3"]) |
| 317 | 318 | } |
| @@ -326,7 +327,7 @@ struct MRDetailViewModelTests { |
| 326 | 327 | |
| 327 | 328 | await model.reply(to: thread, "because 5xx is transient") |
| 328 | 329 | |
| 329 | | let write = stub.seen[3] |
| 330 | let write = try #require(stub.seen.dropFirst(3).first) |
| 330 | 331 | let body = try #require(try JSONSerialization.jsonObject(with: write.body) as? [String: Any]) |
| 331 | 332 | #expect(body["argv"] as? [String] == |
| 332 | 333 | ["mr", "diff-comment", "krz/gitbay", "7", "--reply", "3", "--file", "-"]) |
gitbayTests/PagedListTests.swift
+8 −4
| @@ -38,9 +38,11 @@ struct ClientPagingTests { |
| 38 | 38 | #expect(first.next == "abc") |
| 39 | 39 | #expect(second.items == [Row(number: 3)]) |
| 40 | 40 | #expect(second.next == nil) |
| 41 | | #expect(stub.seen[0].url.query() == |
| 41 | let firstSeen = try #require(stub.seen.first) |
| 42 | #expect(firstSeen.url.query() == |
| 42 | 43 | "argv=issue&argv=list&argv=krz/gitbay&argv=--limit&argv=2") |
| 43 | | #expect(stub.seen[1].url.query() == |
| 44 | let secondSeen = try #require(stub.seen.dropFirst().first) |
| 45 | #expect(secondSeen.url.query() == |
| 44 | 46 | "argv=issue&argv=list&argv=krz/gitbay&argv=--limit&argv=2&argv=--cursor&argv=abc") |
| 45 | 47 | } |
| 46 | 48 | } |
| @@ -77,8 +79,10 @@ struct PagedListModelTests { |
| 77 | 79 | // Further calls are no-ops, not requests. |
| 78 | 80 | await list.loadMore() |
| 79 | 81 | #expect(stub.seen.count == 3) |
| 80 | | #expect(stub.seen[1].url.query()?.contains("argv=--cursor&argv=c1") == true) |
| 81 | | #expect(stub.seen[2].url.query()?.contains("argv=--cursor&argv=c2") == true) |
| 82 | let second = try #require(stub.seen.dropFirst().first) |
| 83 | #expect(second.url.query()?.contains("argv=--cursor&argv=c1") == true) |
| 84 | let third = try #require(stub.seen.dropFirst(2).first) |
| 85 | #expect(third.url.query()?.contains("argv=--cursor&argv=c2") == true) |
| 82 | 86 | } |
| 83 | 87 | |
| 84 | 88 | @Test func anEmptyFirstPageIsAnEmptyState() async throws { |
gitbayTests/ProfileCommitTests.swift
+2 −1
| @@ -59,7 +59,8 @@ struct ProfileAggregateTests { |
| 59 | 59 | #expect(profile.activityTotal == 15) |
| 60 | 60 | // One read builds the whole page. |
| 61 | 61 | #expect(stub.seen.count == 1) |
| 62 | | #expect(stub.seen[0].url.query() == "argv=profile&argv=show&argv=cmc") |
| 62 | let first = try #require(stub.seen.first) |
| 63 | #expect(first.url.query() == "argv=profile&argv=show&argv=cmc") |
| 63 | 64 | } |
| 64 | 65 | |
| 65 | 66 | @Test func anOrgProfileCarriesMembersInsteadOfOrgs() async throws { |
gitbayTests/RefsMilestoneTests.swift
+2 −1
| @@ -120,7 +120,8 @@ struct MilestoneListViewModelTests { |
| 120 | 120 | } |
| 121 | 121 | |
| 122 | 122 | #expect(stub.seen.count == 2) |
| 123 | | #expect(stub.seen[1].url.query()?.contains("argv=closed") == true) |
| 123 | let second = try #require(stub.seen.dropFirst().first) |
| 124 | #expect(second.url.query()?.contains("argv=closed") == true) |
| 124 | 125 | } |
| 125 | 126 | } |
| 126 | 127 | |