Commit e6258f1dc1
e6258f1dc1307fc8def2d88a78dcc46ec8e06506
parent: 2dae2979da
Unsigned
cmc <hello@cleberg.net> · 2026-07-16 00:19 UTC
test: repair stale expectations and request-body capture
These tests drifted from the code and went unnoticed because CI never ran
them. All four are test-side errors; no app behavior is involved.
- TicketListViewModelTests asserted lowercase "resolved"/"fixed"/"reported"
against TicketStatus/TicketResolution rawValues, which are uppercase to
match the todo.sr.ht GraphQL enums.
- HomeViewModelTests expected failedBuilds in ascending id order. Ordering
moved to newest-first when sortBuildItemsForTriage landed in 165f0cf; the
filtering the test covers is unchanged.
- SettingsViewModelTests read request.httpBody inside a URLProtocol, where it
is always nil because URLSession moves the body onto httpBodyStream. The
stub now reads the body off the stream at capture time.
Layout: unified · split
HutchTests/HomeViewModelTests.swift
+1 −1
| @@ -15,7 +15,7 @@ struct HomeViewModelTests { |
| 15 | 15 | |
| 16 | 16 | let failedBuilds = HomeViewModel.failedBuilds(from: jobs) |
| 17 | 17 | |
| 18 | | #expect(failedBuilds.map(\.job.id) == [2, 3]) |
| 18 | #expect(failedBuilds.map(\.job.id) == [3, 2]) |
| 19 | 19 | } |
| 20 | 20 | |
| 21 | 21 | @Test |
HutchTests/SettingsViewModelTests.swift
+24 −2
| @@ -4,12 +4,14 @@ import Testing |
| 4 | 4 | |
| 5 | 5 | private final class SettingsViewModelCapturingURLProtocol: URLProtocol, @unchecked Sendable { |
| 6 | 6 | nonisolated(unsafe) static var capturedRequests: [URLRequest] = [] |
| 7 | nonisolated(unsafe) static var capturedBodies: [Data] = [] |
| 7 | 8 | |
| 8 | 9 | override class func canInit(with _: URLRequest) -> Bool { true } |
| 9 | 10 | override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } |
| 10 | 11 | |
| 11 | 12 | override func startLoading() { |
| 12 | 13 | Self.capturedRequests.append(request) |
| 14 | Self.capturedBodies.append(Self.readBody(from: request)) |
| 13 | 15 | |
| 14 | 16 | let response = HTTPURLResponse( |
| 15 | 17 | url: request.url!, |
| @@ -26,6 +28,26 @@ private final class SettingsViewModelCapturingURLProtocol: URLProtocol, @uncheck |
| 26 | 28 | // No cleanup is needed because the stub responds immediately in `startLoading()`. |
| 27 | 29 | } |
| 28 | 30 | |
| 31 | /// `URLSession` moves `httpBody` onto `httpBodyStream` before handing a request |
| 32 | /// to a `URLProtocol`, so `request.httpBody` is always nil here and the body has |
| 33 | /// to be read back off the stream while it is still open. |
| 34 | private static func readBody(from request: URLRequest) -> Data { |
| 35 | if let body = request.httpBody { return body } |
| 36 | guard let stream = request.httpBodyStream else { return Data() } |
| 37 | |
| 38 | stream.open() |
| 39 | defer { stream.close() } |
| 40 | |
| 41 | var data = Data() |
| 42 | var buffer = [UInt8](repeating: 0, count: 4096) |
| 43 | while stream.hasBytesAvailable { |
| 44 | let read = stream.read(&buffer, maxLength: buffer.count) |
| 45 | guard read > 0 else { break } |
| 46 | data.append(buffer, count: read) |
| 47 | } |
| 48 | return data |
| 49 | } |
| 50 | |
| 29 | 51 | static func makeSession() -> URLSession { |
| 30 | 52 | let config = URLSessionConfiguration.ephemeral |
| 31 | 53 | config.protocolClasses = [Self.self] |
| @@ -73,6 +95,7 @@ struct SettingsViewModelTests { |
| 73 | 95 | @MainActor |
| 74 | 96 | func loadProfileDoesNotRequestSSHKeyFingerprintField() async throws { |
| 75 | 97 | SettingsViewModelCapturingURLProtocol.capturedRequests = [] |
| 98 | SettingsViewModelCapturingURLProtocol.capturedBodies = [] |
| 76 | 99 | |
| 77 | 100 | let client = SRHTClient( |
| 78 | 101 | session: SettingsViewModelCapturingURLProtocol.makeSession(), |
| @@ -82,8 +105,7 @@ struct SettingsViewModelTests { |
| 82 | 105 | |
| 83 | 106 | await viewModel.loadProfile() |
| 84 | 107 | |
| 85 | | let request = try #require(SettingsViewModelCapturingURLProtocol.capturedRequests.first) |
| 86 | | let body = try #require(request.httpBody) |
| 108 | let body = try #require(SettingsViewModelCapturingURLProtocol.capturedBodies.first) |
| 87 | 109 | let jsonObject = try #require(JSONSerialization.jsonObject(with: body) as? [String: Any]) |
| 88 | 110 | let query = try #require(jsonObject["query"] as? String) |
| 89 | 111 | |
HutchTests/TicketListViewModelTests.swift
+3 −3
| @@ -162,8 +162,8 @@ struct TicketListViewModelTests { |
| 162 | 162 | "status": TicketStatus.resolved.rawValue, |
| 163 | 163 | "resolution": TicketResolution.fixed.rawValue |
| 164 | 164 | ] |
| 165 | | #expect(input["status"] as? String == "resolved") |
| 166 | | #expect(input["resolution"] as? String == "fixed") |
| 165 | #expect(input["status"] as? String == "RESOLVED") |
| 166 | #expect(input["resolution"] as? String == "FIXED") |
| 167 | 167 | } |
| 168 | 168 | |
| 169 | 169 | @Test |
| @@ -172,7 +172,7 @@ struct TicketListViewModelTests { |
| 172 | 172 | let input: [String: any Sendable] = [ |
| 173 | 173 | "status": TicketStatus.reported.rawValue |
| 174 | 174 | ] |
| 175 | | #expect(input["status"] as? String == "reported") |
| 175 | #expect(input["status"] as? String == "REPORTED") |
| 176 | 176 | #expect(input["resolution"] == nil) |
| 177 | 177 | } |
| 178 | 178 | |