Commit 0a5413dcd9
Verified · cmc
Layout: unified · split
gitbay/Orgs/OrgViewModels.swift +21 −3
| @@ -31,7 +31,7 @@ final class OrgViewModel { | |||
| 31 | private(set) var working = false | 31 | private(set) var working = false |
| 32 | 32 | ||
| 33 | private let client: GitbayClient | 33 | private let client: GitbayClient |
| 34 | let orgName: String | 34 | private(set) var orgName: String |
| 35 | 35 | ||
| 36 | init(client: GitbayClient, orgName: String) { | 36 | init(client: GitbayClient, orgName: String) { |
| 37 | self.client = client | 37 | self.client = client |
| @@ -69,11 +69,29 @@ final class OrgViewModel { | |||
| 69 | 69 | ||
| 70 | /// `org rename <old> <new>` — both positional, old first. Swapping | 70 | /// `org rename <old> <new>` — both positional, old first. Swapping |
| 71 | /// them renames the wrong way with no error at all. | 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 | func rename(to newName: String) async -> Bool { | 77 | func rename(to newName: String) async -> Bool { |
| 73 | let newName = newName.trimmingCharacters(in: .whitespacesAndNewlines) | 78 | let newName = newName.trimmingCharacters(in: .whitespacesAndNewlines) |
| 74 | guard !newName.isEmpty else { return false } | 79 | guard !newName.isEmpty else { return false } |
| 75 | await perform(["org", "rename", orgName, newName]) | 80 | working = true |
| 76 | return actionError == nil | 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 | func createTeam(_ team: String) async { | 97 | func createTeam(_ team: String) async { |
gitbayTests/SmallActionTests.swift +27 −3
| @@ -81,15 +81,39 @@ struct OrgCreateRenameTests { | |||
| 81 | 81 | ||
| 82 | /// Both names are positional, OLD first. Swapping them renames the | 82 | /// Both names are positional, OLD first. Swapping them renames the |
| 83 | /// wrong way with no error at all. | 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 | let (client, stub) = try makeClient() | 90 | let (client, stub) = try makeClient() |
| 86 | let model = OrgViewModel(client: client, orgName: "krz") | 91 | let model = OrgViewModel(client: client, orgName: "krz") |
| 87 | stub.enqueue(.init(status: 200, json: ok)) | 92 | stub.enqueue(.init(status: 200, json: ok)) |
| 88 | stub.enqueue(.init(status: 200, json: ok)) | 93 | stub.enqueue(.init(status: 200, json: """ |
| 89 | _ = await model.rename(to: "kerouac") | 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 | let write = try #require(stub.seen.first { $0.method == "POST" }) | 102 | let write = try #require(stub.seen.first { $0.method == "POST" }) |
| 92 | #expect(try argvOf(write) == ["org", "rename", "krz", "kerouac"]) | 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 | @Test func aBlankRenameSendsNothing() async throws { | 119 | @Test func aBlankRenameSendsNothing() async throws { |