| @@ -37,15 +37,40 @@ struct BuildCancelTests { |
| 37 | |
37 | |
| 38 | @Test func cancelSendsTheBuildNumber() async throws { |
38 | @Test func cancelSendsTheBuildNumber() async throws { |
| 39 | let (client, stub) = try makeClient() |
39 | let (client, stub) = try makeClient() |
| 40 | // The view model's own load response, then the write, then a reload. |
40 | // The write, then its reload — `build show` and `build log` fire |
| |
41 | // concurrently, so each needs its own matched stub. |
| 41 | let model = BuildDetailViewModel(client: client, repoPath: "krz/gitbay", number: 966) |
42 | let model = BuildDetailViewModel(client: client, repoPath: "krz/gitbay", number: 966) |
| 42 | stub.enqueue(.init(status: 200, json: ok)) |
43 | stub.enqueue(.init(status: 200, json: ok)) |
| 43 | stub.enqueue(.init(status: 200, json: ok)) |
44 | stub.enqueue(.init(status: 200, json: """ |
| |
45 | {"protocol_version":1,"data":{"number":966,"job":"test","status":"cancelled",\ |
| |
46 | "sha":"abc123","ref":"refs/heads/main","created_at":"2026-01-01T00:00:00Z"},\ |
| |
47 | "exit_code":0} |
| |
48 | """, match: "argv=show")) |
| |
49 | stub.enqueue(.init(status: 200, json: ok, match: "argv=log")) |
| 44 | await model.cancel() |
50 | await model.cancel() |
| 45 | |
51 | |
| 46 | let write = try #require(stub.seen.first { $0.method == "POST" }) |
52 | let write = try #require(stub.seen.first { $0.method == "POST" }) |
| 47 | #expect(try argvOf(write) == ["build", "cancel", "krz/gitbay", "966"]) |
53 | #expect(try argvOf(write) == ["build", "cancel", "krz/gitbay", "966"]) |
| 48 | } |
54 | } |
| |
55 | |
| |
56 | /// The view reads the instance property, not the static function — |
| |
57 | /// nothing pinned that it tracks the loaded build, or that it starts |
| |
58 | /// out false before anything has loaded. |
| |
59 | @Test func instanceIsCancellableTracksTheLoadedBuild() async throws { |
| |
60 | let (client, stub) = try makeClient() |
| |
61 | let model = BuildDetailViewModel(client: client, repoPath: "krz/gitbay", number: 966) |
| |
62 | #expect(model.isCancellable == false) |
| |
63 | |
| |
64 | stub.enqueue(.init(status: 200, json: """ |
| |
65 | {"protocol_version":1,"data":{"number":966,"job":"test","status":"running",\ |
| |
66 | "sha":"abc123","ref":"refs/heads/main","created_at":"2026-01-01T00:00:00Z"},\ |
| |
67 | "exit_code":0} |
| |
68 | """, match: "argv=show")) |
| |
69 | stub.enqueue(.init(status: 200, json: ok, match: "argv=log")) |
| |
70 | await model.load() |
| |
71 | |
| |
72 | #expect(model.isCancellable) |
| |
73 | } |
| 49 | } |
74 | } |
| 50 | |
75 | |
| 51 | @MainActor |
76 | @MainActor |
| @@ -74,6 +99,27 @@ struct ReleaseDeleteTests { |
| 74 | #expect(deleted == false) |
99 | #expect(deleted == false) |
| 75 | #expect(model.actionError?.isEmpty == false) |
100 | #expect(model.actionError?.isEmpty == false) |
| 76 | } |
101 | } |
| |
102 | |
| |
103 | /// A stale `actionError` surviving an unrelated later call has been the |
| |
104 | /// bug five times over; every `perform` clears it up front, but nothing |
| |
105 | /// pinned that. |
| |
106 | @Test func actionErrorClearsOnALaterSuccess() async throws { |
| |
107 | let (client, stub) = try makeClient() |
| |
108 | let model = ReleaseDetailViewModel(client: client, repoPath: "krz/gitbay", tag: "v1.0.0") |
| |
109 | stub.enqueue(.init(status: 200, json: """ |
| |
110 | {"protocol_version":1,"error":"deleting a release needs write access","exit_code":4} |
| |
111 | """)) |
| |
112 | let firstDelete = await model.delete() |
| |
113 | |
| |
114 | #expect(firstDelete == false) |
| |
115 | #expect(model.actionError != nil) |
| |
116 | |
| |
117 | stub.enqueue(.init(status: 200, json: ok)) |
| |
118 | let secondDelete = await model.delete() |
| |
119 | |
| |
120 | #expect(secondDelete) |
| |
121 | #expect(model.actionError == nil) |
| |
122 | } |
| 77 | } |
123 | } |
| 78 | |
124 | |
| 79 | @MainActor |
125 | @MainActor |
| @@ -130,8 +176,9 @@ struct OrgCreateRenameTests { |
| 130 | stub.enqueue(.init(status: 200, json: ok)) |
176 | stub.enqueue(.init(status: 200, json: ok)) |
| 131 | stub.enqueue(.init(status: 200, json: ok)) |
177 | stub.enqueue(.init(status: 200, json: ok)) |
| 132 | let model = ProfileViewModel(client: client, name: "cmc") |
178 | let model = ProfileViewModel(client: client, name: "cmc") |
| 133 | _ = await model.createOrg("newco") |
179 | let created = await model.createOrg("newco") |
| 134 | |
180 | |
| |
181 | #expect(created) |
| 135 | let write = try #require(stub.seen.first { $0.method == "POST" }) |
182 | let write = try #require(stub.seen.first { $0.method == "POST" }) |
| 136 | #expect(try argvOf(write) == ["org", "create", "newco"]) |
183 | #expect(try argvOf(write) == ["org", "create", "newco"]) |
| 137 | } |
184 | } |