| @@ -88,3 +88,145 @@ struct MRLifecycleDecodingTests { |
| 88 | #expect(row.stackedOn == nil) |
88 | #expect(row.stackedOn == nil) |
| 89 | } |
89 | } |
| 90 | } |
90 | } |
| |
91 | |
| |
92 | @MainActor |
| |
93 | struct MRLifecycleActionTests { |
| |
94 | |
| |
95 | private func loaded() async throws -> (MRDetailViewModel, StubProtocol.Box) { |
| |
96 | let box = StubProtocol.box() |
| |
97 | let client = GitbayClient( |
| |
98 | instance: try GitbayInstance(url: "https://gitbay.org"), |
| |
99 | token: "test-token", |
| |
100 | session: box.session() |
| |
101 | ) |
| |
102 | box.enqueue(.init(status: 200, json: """ |
| |
103 | {"protocol_version":1,"data":\(plainDetail),"exit_code":0} |
| |
104 | """)) |
| |
105 | let model = MRDetailViewModel(client: client, repoPath: "krz/gitbay", number: 7) |
| |
106 | await model.load() |
| |
107 | return (model, box) |
| |
108 | } |
| |
109 | |
| |
110 | private func argvOf(_ seen: StubProtocol.Seen) throws -> [String] { |
| |
111 | let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any]) |
| |
112 | return try #require(body["argv"] as? [String]) |
| |
113 | } |
| |
114 | |
| |
115 | private func ok(_ box: StubProtocol.Box) { |
| |
116 | box.enqueue(.init(status: 200, json: """ |
| |
117 | {"protocol_version":1,"exit_code":0} |
| |
118 | """)) |
| |
119 | box.enqueue(.init(status: 200, json: """ |
| |
120 | {"protocol_version":1,"data":\(plainDetail),"exit_code":0} |
| |
121 | """)) |
| |
122 | } |
| |
123 | |
| |
124 | @Test func markingDraftAndReadyAreDifferentCommands() async throws { |
| |
125 | let (model, box) = try await loaded() |
| |
126 | ok(box) |
| |
127 | await model.setDraft(true) |
| |
128 | var write = try #require(box.seen.first { $0.method == "POST" }) |
| |
129 | #expect(try argvOf(write) == ["mr", "draft", "krz/gitbay", "7"]) |
| |
130 | |
| |
131 | let (model2, box2) = try await loaded() |
| |
132 | ok(box2) |
| |
133 | await model2.setDraft(false) |
| |
134 | write = try #require(box2.seen.first { $0.method == "POST" }) |
| |
135 | #expect(try argvOf(write) == ["mr", "ready", "krz/gitbay", "7"]) |
| |
136 | } |
| |
137 | |
| |
138 | @Test func retargetPassesTheBranchPositionally() async throws { |
| |
139 | let (model, box) = try await loaded() |
| |
140 | ok(box) |
| |
141 | await model.retarget(to: "release") |
| |
142 | let write = try #require(box.seen.first { $0.method == "POST" }) |
| |
143 | #expect(try argvOf(write) == ["mr", "retarget", "krz/gitbay", "7", "release"]) |
| |
144 | } |
| |
145 | |
| |
146 | /// `mr review request` is a THREE-word path. Two words posts a |
| |
147 | /// review instead — silently, with a 200. |
| |
148 | @Test func requestingAReviewUsesTheThreeWordPath() async throws { |
| |
149 | let (model, box) = try await loaded() |
| |
150 | ok(box) |
| |
151 | await model.requestReview(from: "rae") |
| |
152 | let write = try #require(box.seen.first { $0.method == "POST" }) |
| |
153 | #expect(try argvOf(write) |
| |
154 | == ["mr", "review", "request", "krz/gitbay", "7", "--add", "rae"]) |
| |
155 | } |
| |
156 | |
| |
157 | @Test func removingAReviewRequestUsesRemoveNotAdd() async throws { |
| |
158 | let (model, box) = try await loaded() |
| |
159 | ok(box) |
| |
160 | await model.removeReviewRequest("rae") |
| |
161 | let write = try #require(box.seen.first { $0.method == "POST" }) |
| |
162 | #expect(try argvOf(write) |
| |
163 | == ["mr", "review", "request", "krz/gitbay", "7", "--remove", "rae"]) |
| |
164 | } |
| |
165 | |
| |
166 | @Test func branchesComeFromRepoRefs() async throws { |
| |
167 | let (model, box) = try await loaded() |
| |
168 | box.enqueue(.init(status: 200, json: """ |
| |
169 | {"protocol_version":1,"data":{"branches":[{"name":"main","sha":"a"},\ |
| |
170 | {"name":"release","sha":"b"}],"tags":[]},"exit_code":0} |
| |
171 | """)) |
| |
172 | await model.loadBranches() |
| |
173 | #expect(model.branches == ["main", "release"]) |
| |
174 | } |
| |
175 | |
| |
176 | @Test func aRefusalSurfacesAndDoesNotReload() async throws { |
| |
177 | let (model, box) = try await loaded() |
| |
178 | box.enqueue(.init(status: 200, json: """ |
| |
179 | {"protocol_version":1,"error":"cannot retarget a merged merge request",\ |
| |
180 | "exit_code":1} |
| |
181 | """)) |
| |
182 | await model.retarget(to: "release") |
| |
183 | #expect(model.actionError?.isEmpty == false) |
| |
184 | #expect(model.working == false) |
| |
185 | } |
| |
186 | } |
| |
187 | |
| |
188 | @MainActor |
| |
189 | struct MRCreateDraftTests { |
| |
190 | |
| |
191 | @Test func creatingAsADraftAppendsTheFlag() async throws { |
| |
192 | let box = StubProtocol.box() |
| |
193 | let client = GitbayClient( |
| |
194 | instance: try GitbayInstance(url: "https://gitbay.org"), |
| |
195 | token: "test-token", |
| |
196 | session: box.session() |
| |
197 | ) |
| |
198 | box.enqueue(.init(status: 200, json: """ |
| |
199 | {"protocol_version":1,"data":{"number":7},"exit_code":0} |
| |
200 | """)) |
| |
201 | let model = MRCreateViewModel(client: client, repoPath: "krz/gitbay") |
| |
202 | model.draft = true |
| |
203 | _ = await model.create(source: "feat", target: "main", title: "a change", body: "") |
| |
204 | |
| |
205 | let write = try #require(box.seen.first { $0.method == "POST" }) |
| |
206 | let body = try #require( |
| |
207 | try JSONSerialization.jsonObject(with: write.body) as? [String: Any] |
| |
208 | ) |
| |
209 | let argv = try #require(body["argv"] as? [String]) |
| |
210 | #expect(argv.contains("--draft")) |
| |
211 | } |
| |
212 | |
| |
213 | @Test func creatingWithoutDraftOmitsTheFlag() async throws { |
| |
214 | let box = StubProtocol.box() |
| |
215 | let client = GitbayClient( |
| |
216 | instance: try GitbayInstance(url: "https://gitbay.org"), |
| |
217 | token: "test-token", |
| |
218 | session: box.session() |
| |
219 | ) |
| |
220 | box.enqueue(.init(status: 200, json: """ |
| |
221 | {"protocol_version":1,"data":{"number":7},"exit_code":0} |
| |
222 | """)) |
| |
223 | let model = MRCreateViewModel(client: client, repoPath: "krz/gitbay") |
| |
224 | _ = await model.create(source: "feat", target: "main", title: "a change", body: "") |
| |
225 | |
| |
226 | let write = try #require(box.seen.first { $0.method == "POST" }) |
| |
227 | let body = try #require( |
| |
228 | try JSONSerialization.jsonObject(with: write.body) as? [String: Any] |
| |
229 | ) |
| |
230 | #expect((body["argv"] as? [String])?.contains("--draft") == false) |
| |
231 | } |
| |
232 | } |