Commit 0a5413dcd9
Verified · cmc
Layout: unified · split
gitbay/Orgs/OrgViewModels.swift +21 −3
| @@ -31,7 +31,7 @@ final class OrgViewModel { | ||
| 31 | 31 | private(set) var working = false |
| 32 | 32 | |
| 33 | 33 | private let client: GitbayClient |
| 34 | let orgName: String | |
| 34 | private(set) var orgName: String | |
| 35 | 35 | |
| 36 | 36 | init(client: GitbayClient, orgName: String) { |
| 37 | 37 | self.client = client |
| @@ -69,11 +69,29 @@ final class OrgViewModel { | ||
| 69 | 69 | |
| 70 | 70 | /// `org rename <old> <new>` — both positional, old first. Swapping |
| 71 | 71 | /// them renames the wrong way with no error at all. |
| 72 | /// | |
| 73 | /// Doesn't go through `perform`: its automatic reload would still | |
| 74 | /// query the old, now-renamed org. `orgName` only moves to the new | |
| 75 | /// name once the rename itself has actually succeeded, and the | |
| 76 | /// reload that follows targets that new name. | |
| 72 | 77 | func rename(to newName: String) async -> Bool { |
| 73 | 78 | let newName = newName.trimmingCharacters(in: .whitespacesAndNewlines) |
| 74 | 79 | guard !newName.isEmpty else { return false } |
| 75 | await perform(["org", "rename", orgName, newName]) | |
| 76 | return actionError == nil | |
| 80 | working = true | |
| 81 | actionError = nil | |
| 82 | defer { working = false } | |
| 83 | do { | |
| 84 | try await client.run(["org", "rename", orgName, newName]) | |
| 85 | orgName = newName | |
| 86 | await load() | |
| 87 | return true | |
| 88 | } catch let error as GitbayError { | |
| 89 | actionError = error.userFacingMessage | |
| 90 | return false | |
| 91 | } catch { | |
| 92 | actionError = GitbayError.transport(error).userFacingMessage | |
| 93 | return false | |
| 94 | } | |
| 77 | 95 | } |
| 78 | 96 | |
| 79 | 97 | func createTeam(_ team: String) async { |
gitbayTests/SmallActionTests.swift +27 −3
| @@ -81,15 +81,39 @@ struct OrgCreateRenameTests { | ||
| 81 | 81 | |
| 82 | 82 | /// Both names are positional, OLD first. Swapping them renames the |
| 83 | 83 | /// wrong way with no error at all. |
| 84 | @Test func renameSendsOldThenNew() async throws { | |
| 84 | /// | |
| 85 | /// A successful rename triggers `load()`, which fires two concurrent | |
| 86 | /// GETs (`org show`, `org team list`) alongside the POST — all three | |
| 87 | /// need a stub, or the reload fails and the model ends up showing an | |
| 88 | /// error despite the rename having worked. | |
| 89 | @Test func renameSendsOldThenNewAndReloadsTheNewName() async throws { | |
| 85 | 90 | let (client, stub) = try makeClient() |
| 86 | 91 | let model = OrgViewModel(client: client, orgName: "krz") |
| 87 | 92 | stub.enqueue(.init(status: 200, json: ok)) |
| 88 | stub.enqueue(.init(status: 200, json: ok)) | |
| 89 | _ = await model.rename(to: "kerouac") | |
| 93 | stub.enqueue(.init(status: 200, json: """ | |
| 94 | {"protocol_version":1,"data":{"org":"kerouac","members":[]},"exit_code":0} | |
| 95 | """, match: "argv=show")) | |
| 96 | stub.enqueue(.init(status: 200, json: """ | |
| 97 | {"protocol_version":1,"data":[],"exit_code":0} | |
| 98 | """, match: "argv=team")) | |
| 99 | let renamed = await model.rename(to: "kerouac") | |
| 90 | 100 | |
| 101 | #expect(renamed) | |
| 91 | 102 | let write = try #require(stub.seen.first { $0.method == "POST" }) |
| 92 | 103 | #expect(try argvOf(write) == ["org", "rename", "krz", "kerouac"]) |
| 104 | ||
| 105 | #expect(model.actionError == nil) | |
| 106 | guard case .loaded = model.state else { | |
| 107 | Issue.record("expected .loaded after a successful rename, got \(model.state)") | |
| 108 | return | |
| 109 | } | |
| 110 | let reloads = try stub.seen.filter { $0.method == "GET" }.map { seen in | |
| 111 | try #require(seen.url.query(percentEncoded: false)).components(separatedBy: "&") | |
| 112 | } | |
| 113 | for reload in reloads { | |
| 114 | #expect(reload.contains("argv=kerouac"), | |
| 115 | "expected the reload to target the new name, got \(reload)") | |
| 116 | } | |
| 93 | 117 | } |
| 94 | 118 | |
| 95 | 119 | @Test func aBlankRenameSendsNothing() async throws { |