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 okJSON = #"{"protocol_version":1,"data":{},"exit_code":0}"# private let orgLabelsJSON = """ {"protocol_version":1,"data":[\ {"name":"bug","color":"#cf222e","org":true,"issues":18},\ {"name":"ci","org":true,"issues":2}],"exit_code":0} """ private let orgMilestonesJSON = """ {"protocol_version":1,"data":[\ {"title":"v2.0.0","state":"open","org":true,"open":3,"closed":9}],"exit_code":0} """ struct LabelOwnerTests { /// A repository's set is `label `; an org's is /// `org label `. Milestones follow the same shape. @Test func eachOwnerSpellsItsOwnCommand() { #expect(LabelOwner.repo("krz/gitbay").argv("label", "set") == ["label", "set", "krz/gitbay"]) #expect(LabelOwner.org("krz").argv("label", "set") == ["org", "label", "set", "krz"]) #expect(LabelOwner.org("krz").argv("milestone", "list") == ["org", "milestone", "list", "krz"]) #expect(LabelOwner.repo("krz/gitbay").isOrg == false) #expect(LabelOwner.org("krz").isOrg) } } @MainActor struct OrgLabelListTests { @Test func anOrgListsItsLabelsThroughOrgLabel() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: orgLabelsJSON)) let model = LabelListViewModel(client: client, owner: .org("krz")) await model.load() #expect(stub.seen.first?.url.query() == "argv=org&argv=label&argv=list&argv=krz") #expect(model.state.value?.map(\.name) == ["bug", "ci"]) #expect(model.state.value?.allSatisfy(\.org) == true) } @Test func setAndRemoveUseTheOrgCommands() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: orgLabelsJSON)) let model = LabelListViewModel(client: client, owner: .org("krz")) await model.load() for _ in 0..<2 { stub.enqueue(.init(status: 200, json: okJSON, match: "cmd")) stub.enqueue(.init(status: 200, json: orgLabelsJSON, match: "argv=list")) } await model.setLabel("bug", color: "cf222e") await model.remove("ci") let writes = try stub.seen.filter { $0.method == "POST" }.map(argvOf) #expect(writes == [ ["org", "label", "set", "krz", "bug", "--color", "cf222e"], ["org", "label", "remove", "krz", "ci"], ]) } } @MainActor struct OrgMilestoneListTests { @Test func anOrgListsItsMilestonesWithTheState() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: orgMilestonesJSON)) let model = MilestoneListViewModel(client: client, owner: .org("krz")) await model.load() #expect(stub.seen.first?.url.query() == "argv=org&argv=milestone&argv=list&argv=krz&argv=--state&argv=open") #expect(model.state.value?.map(\.title) == ["v2.0.0"]) } @Test func createCloseAndReopenUseTheOrgCommands() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: orgMilestonesJSON)) let model = MilestoneListViewModel(client: client, owner: .org("krz")) await model.load() for _ in 0..<3 { stub.enqueue(.init(status: 200, json: okJSON, match: "cmd")) stub.enqueue(.init(status: 200, json: orgMilestonesJSON, match: "argv=list")) } let ok = await model.create(title: "v2.1.0", description: "next", due: "2026-10-01") await model.close("v2.0.0") await model.reopen("v2.0.0") #expect(ok) let writes = try stub.seen.filter { $0.method == "POST" }.map(argvOf) #expect(writes == [ ["org", "milestone", "create", "krz", "v2.1.0", "--description", "next", "--due", "2026-10-01"], ["org", "milestone", "close", "krz", "v2.0.0"], ["org", "milestone", "reopen", "krz", "v2.0.0"], ]) } }