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 postedArgv(_ stub: StubProtocol.Box) throws -> [[String]] { try stub.seen.filter { $0.method == "POST" }.map { seen in let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any]) return try #require(body["argv"] as? [String]) } } private let settingsJSON = """ {"protocol_version":1,"data":{"protected_branches":["main"],"require_checks":true,\ "required_contexts":["ci/test","ext/lint"]},"exit_code":0} """ private let repoShowJSON = """ {"protocol_version":1,"data":{"path":"krz/gitbay","description":"a forge",\ "visibility":"public","default_branch":"main"},"exit_code":0} """ private let ok = #"{"protocol_version":1,"data":{},"exit_code":0}"# @MainActor struct RequiredContextsTests { private func loaded() async throws -> (RepoSettingsViewModel, StubProtocol.Box) { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: settingsJSON, match: "argv=settings")) stub.enqueue(.init(status: 200, json: repoShowJSON, match: "argv=show&argv=krz")) let model = RepoSettingsViewModel(client: client, repoPath: "krz/gitbay") await model.load() return (model, stub) } @Test func requiredContextsDecode() async throws { let (model, _) = try await loaded() #expect(model.state.value?.settings.contexts == ["ci/test", "ext/lint"]) } @Test func addingAContextSendsTheWholeListWithItAppended() async throws { let (model, stub) = try await loaded() stub.enqueue(.init(status: 200, json: ok, match: "cmd")) let cleared = await model.addRequiredContext(" ci/build ") #expect(cleared) #expect(try postedArgv(stub) == [["repo", "settings", "require-contexts", "krz/gitbay", "ci/test", "ext/lint", "ci/build"]]) } @Test func addingOneAlreadyListedSendsNothing() async throws { let (model, stub) = try await loaded() await model.addRequiredContext("ci/test") #expect(try postedArgv(stub).isEmpty) } @Test func aRefusedContextIsNotClearedFromTheField() async throws { let (model, stub) = try await loaded() stub.enqueue(.init(status: 400, json: #"{"protocol_version":1,"error":"at most 20 required contexts","exit_code":2}"#, match: "cmd")) let cleared = await model.addRequiredContext("ci/more") #expect(!cleared) // The name was typed, so the refusal is shown as the server said it. #expect(model.actionError == "at most 20 required contexts") } @Test func removingTheLastContextSendsAnEmptyList() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: """ {"protocol_version":1,"data":{"require_checks":true,"required_contexts":["ci/test"]},"exit_code":0} """, match: "argv=settings")) stub.enqueue(.init(status: 200, json: repoShowJSON, match: "argv=show&argv=krz")) let model = RepoSettingsViewModel(client: client, repoPath: "krz/gitbay") await model.load() stub.enqueue(.init(status: 200, json: ok, match: "cmd")) await model.removeRequiredContext("ci/test") #expect(try postedArgv(stub) == [["repo", "settings", "require-contexts", "krz/gitbay"]]) } }