import Foundation import Testing @testable import gitbay private func makeClient() throws -> (GitbayClient, StubProtocol.Box) { let box = StubProtocol.box() let client = GitbayClient( instance: try GitbayInstance(url: "https://gitbay.org"), token: "test-token", session: box.session() ) return (client, box) } private func argvOf(_ seen: StubProtocol.Seen) throws -> [String] { let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any]) return try #require(body["argv"] as? [String]) } private let orgShowJSON = """ {"protocol_version":1,"data":{"org":"krz","members":[\ {"user":"cmc","role":"admin"},{"user":"guest","role":"member"}]},"exit_code":0} """ private let teamsJSON = #"{"protocol_version":1,"data":["core"],"exit_code":0}"# private let okJSON = #"{"protocol_version":1,"data":{},"exit_code":0}"# @MainActor struct OrgViewModelTests { private func loadedModel() async throws -> (OrgViewModel, StubProtocol.Box) { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: orgShowJSON, match: "argv=show")) stub.enqueue(.init(status: 200, json: teamsJSON, match: "argv=team")) let model = OrgViewModel(client: client, orgName: "krz") await model.load() return (model, stub) } @Test func loadsMembersAndBareTeamNames() async throws { let (model, _) = try await loadedModel() let loaded = try #require(model.state.value) #expect(loaded.members.map(\.user) == ["cmc", "guest"]) #expect(loaded.teams == ["core"]) } @Test func membershipWritesUseTheRightCommands() async throws { let (model, stub) = try await loadedModel() for _ in 0..<4 { stub.enqueue(.init(status: 200, json: okJSON, match: "cmd")) stub.enqueue(.init(status: 200, json: orgShowJSON, match: "argv=show")) stub.enqueue(.init(status: 200, json: teamsJSON, match: "argv=team")) } let guest = try #require(model.state.value?.members.last) await model.addMember("newbie", role: "member") await model.setRole(guest, role: "admin") await model.removeMember(guest) await model.createTeam("infra") let writes = try stub.seen.filter { $0.method == "POST" }.map(argvOf) #expect(writes == [ ["org", "members", "add", "krz", "newbie", "--role", "member"], ["org", "members", "add", "krz", "guest", "--role", "admin"], ["org", "members", "remove", "krz", "guest"], ["org", "team", "create", "krz", "infra"], ]) } @Test func anOutsiderGetsTheRefusalAsTheScreenState() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 403, json: #"{"protocol_version":1,"error":"you are not a member of krz","exit_code":4}"#, match: "argv=show")) stub.enqueue(.init(status: 403, json: #"{"protocol_version":1,"error":"you are not a member of krz","exit_code":4}"#, match: "argv=team")) let model = OrgViewModel(client: client, orgName: "krz") await model.load() guard case .failed(let message) = model.state else { Issue.record("expected .failed, got \(model.state)") return } #expect(message == "you are not a member of krz") } } @MainActor struct TeamViewModelTests { private let teamShowJSON = """ {"protocol_version":1,"data":{"team":"core","members":["cmc"],\ "grants":[{"repo":"krz/gitbay","role":"write"}]},"exit_code":0} """ private func loadedModel() async throws -> (TeamViewModel, StubProtocol.Box) { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: teamShowJSON, match: "argv=show")) let model = TeamViewModel(client: client, orgName: "krz", teamName: "core") await model.load() return (model, stub) } @Test func loadsMembersAndGrants() async throws { let (model, _) = try await loadedModel() let loaded = try #require(model.state.value) #expect(loaded.members == ["cmc"]) #expect(loaded.grants?.first?.repo == "krz/gitbay") } @Test func teamWritesTargetOrgTeamAndSubject() async throws { let (model, stub) = try await loadedModel() for _ in 0..<4 { stub.enqueue(.init(status: 200, json: okJSON, match: "cmd")) stub.enqueue(.init(status: 200, json: teamShowJSON, match: "argv=show")) } let grant = try #require(model.state.value?.grants?.first) await model.addMember("guest") await model.removeMember("guest") await model.grant(repo: "krz/orgo", role: "admin") await model.revoke(grant) let writes = try stub.seen.filter { $0.method == "POST" }.map(argvOf) #expect(writes == [ ["org", "team", "add", "krz", "core", "guest"], ["org", "team", "remove", "krz", "core", "guest"], ["org", "team", "grant", "krz", "core", "krz/orgo", "admin"], ["org", "team", "revoke", "krz", "core", "krz/gitbay"], ]) } @Test func aMissingTeamIsAnEmptyState() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 404, json: #"{"protocol_version":1,"error":"no team \"core\" in krz","exit_code":3}"#)) let model = TeamViewModel(client: client, orgName: "krz", teamName: "core") await model.load() guard case .empty = model.state else { Issue.record("expected .empty, got \(model.state)") return } } }