gitbayTests/RepoMirrorDomainTests.swift

main
gitbay-ios/gitbayTests/RepoMirrorDomainTests.swift history · blame · raw

173 lines · 8111 bytes

13 symbols in this file
  1import Foundation
  2import Testing
  3@testable import gitbay
  4
  5private func makeClient() throws -> (GitbayClient, StubProtocol.Box) {
  6    let box = StubProtocol.box()
  7    let client = GitbayClient(
  8        instance: try GitbayInstance(url: "https://gitbay.org"),
  9        token: "test-token",
 10        session: box.session()
 11    )
 12    return (client, box)
 13}
 14
 15private func writes(_ stub: StubProtocol.Box) throws -> [[String: Any]] {
 16    try stub.seen.filter { $0.url.path().hasSuffix("/cmd") }.map {
 17        try #require(try JSONSerialization.jsonObject(with: $0.body) as? [String: Any])
 18    }
 19}
 20
 21private let okJSON = #"{"protocol_version":1,"data":{},"exit_code":0}"#
 22private let mirrorsJSON = """
 23    {"protocol_version":1,"data":[\
 24    {"id":1,"direction":"push","url":"https://github.com/krz/gitbay.git","username":"bot","pending":true},\
 25    {"id":2,"direction":"pull","url":"https://example.test/up.git","pending":false,\
 26    "last_sync":"2026-10-15T10:00:00.123Z","last_error":"authentication failed"}\
 27    ],"exit_code":0}
 28    """
 29private let domainsJSON = """
 30    {"protocol_version":1,"data":[\
 31    {"domain":"docs.example.test","state":"pending"},\
 32    {"domain":"www.example.test","state":"verified","verified_at":"2026-10-01T09:00:00Z"},\
 33    {"domain":"old.example.test","state":"expired"}\
 34    ],"exit_code":0}
 35    """
 36
 37@MainActor
 38struct MirrorsViewModelTests {
 39
 40    @Test func listsMirrorsWithTheirSyncState() async throws {
 41        let (client, stub) = try makeClient()
 42        stub.enqueue(.init(status: 200, json: mirrorsJSON))
 43        let model = MirrorsViewModel(client: client, repoPath: "krz/gitbay")
 44        await model.load()
 45
 46        let mirrors = try #require(model.state.value)
 47        #expect(mirrors.map(\.id) == [1, 2])
 48        #expect(mirrors[0].pending && mirrors[0].lastSync == nil && mirrors[0].username == "bot")
 49        #expect(mirrors[1].isPull && mirrors[1].lastSync != nil && mirrors[1].lastError == "authentication failed")
 50        #expect(stub.seen.first?.url.query() == "argv=repo&argv=mirror&argv=list&argv=krz/gitbay")
 51    }
 52
 53    @Test func addSendsTheTokenOnStdinOnlyWhenGiven() async throws {
 54        let (client, stub) = try makeClient()
 55        for _ in 0..<2 {
 56            stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"id":3},"exit_code":0}"#, match: "cmd"))
 57            stub.enqueue(.init(status: 200, json: mirrorsJSON, match: "argv=list"))
 58        }
 59        let model = MirrorsViewModel(client: client, repoPath: "krz/gitbay")
 60
 61        #expect(await model.add(url: " https://example.test/a.git ", pull: false, username: "", token: ""))
 62        #expect(await model.add(url: "https://example.test/b.git", pull: true, username: "bot", token: "t0k"))
 63
 64        let sent = try writes(stub)
 65        #expect(sent.map { $0["argv"] as? [String] } == [
 66            ["repo", "mirror", "add", "krz/gitbay", "https://example.test/a.git", "--direction", "push"],
 67            ["repo", "mirror", "add", "krz/gitbay", "https://example.test/b.git", "--direction", "pull",
 68             "--username", "bot", "--token-stdin"],
 69        ])
 70        #expect(sent[0]["stdin"] == nil)
 71        #expect(sent[1]["stdin"] as? String == "t0k\n")
 72    }
 73
 74    @Test func aRefusedURLSaysWhy() async throws {
 75        let (client, stub) = try makeClient()
 76        stub.enqueue(.init(status: 400, json: #"{"protocol_version":1,"error":"that mirror already exists","exit_code":2}"#))
 77        let model = MirrorsViewModel(client: client, repoPath: "krz/gitbay")
 78
 79        #expect(await model.add(url: "https://example.test/a.git", pull: false, username: "", token: "") == false)
 80        #expect(model.actionError == "that mirror already exists")
 81    }
 82
 83    @Test func syncAndRemove() async throws {
 84        let (client, stub) = try makeClient()
 85        for _ in 0..<2 {
 86            stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
 87            stub.enqueue(.init(status: 200, json: mirrorsJSON, match: "argv=list"))
 88        }
 89        let model = MirrorsViewModel(client: client, repoPath: "krz/gitbay")
 90
 91        #expect(await model.sync())
 92        await model.remove(id: 2)
 93
 94        #expect(try writes(stub).map { $0["argv"] as? [String] } == [
 95            ["repo", "mirror", "sync", "krz/gitbay"],
 96            ["repo", "mirror", "remove", "krz/gitbay", "2"],
 97        ])
 98    }
 99}
100
101@MainActor
102struct PageDomainsViewModelTests {
103
104    @Test func listsClaimsWithTheirState() async throws {
105        let (client, stub) = try makeClient()
106        stub.enqueue(.init(status: 200, json: domainsJSON))
107        let model = PageDomainsViewModel(client: client, repoPath: "krz/gitbay")
108        await model.load()
109
110        let domains = try #require(model.state.value)
111        #expect(domains.map(\.domain) == ["docs.example.test", "www.example.test", "old.example.test"])
112        #expect(domains[0].isPending && domains[0].verifiedAt == nil)
113        #expect(domains[1].isVerified && domains[1].verifiedAt != nil)
114        #expect(!domains[2].isPending && !domains[2].isVerified)
115    }
116
117    @Test func addReturnsTheDNSChallenge() async throws {
118        let (client, stub) = try makeClient()
119        stub.enqueue(.init(status: 200, json: """
120            {"protocol_version":1,"data":{"challenge_name":"_gitbay-challenge.docs.example.test",\
121            "challenge_value":"gitbay-domain-verify=abc","domain":"docs.example.test","state":"pending"},"exit_code":0}
122            """, match: "cmd"))
123        stub.enqueue(.init(status: 200, json: domainsJSON, match: "argv=list"))
124        let model = PageDomainsViewModel(client: client, repoPath: "krz/gitbay")
125
126        let challenge = try #require(await model.add(" Docs.Example.Test "))
127
128        #expect(challenge.name == "_gitbay-challenge.docs.example.test")
129        #expect(challenge.value == "gitbay-domain-verify=abc")
130        #expect(try writes(stub).first?["argv"] as? [String] == ["repo", "domain", "add", "krz/gitbay", "Docs.Example.Test"])
131    }
132
133    @Test func verifyReportsAMissingRecordVerbatim() async throws {
134        let (client, stub) = try makeClient()
135        stub.enqueue(.init(status: 403, json: #"{"protocol_version":1,"error":"_gitbay-challenge.docs.example.test does not carry the expected record \"x\"","exit_code":4}"#))
136        let model = PageDomainsViewModel(client: client, repoPath: "krz/gitbay")
137
138        #expect(await model.verify("docs.example.test") == false)
139        #expect(model.actionError == #"_gitbay-challenge.docs.example.test does not carry the expected record "x""#)
140    }
141
142    @Test func aRefusedVerifyRefreshesTheList() async throws {
143        let (client, stub) = try makeClient()
144        stub.enqueue(.init(status: 400, json: #"{"protocol_version":1,"error":"the claim on old.example.test expired; run repo domain add again","exit_code":2}"#, match: "cmd"))
145        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":[],"exit_code":0}"#, match: "argv=list"))
146        let model = PageDomainsViewModel(client: client, repoPath: "krz/gitbay")
147
148        #expect(await model.verify("old.example.test") == false)
149
150        #expect(model.actionError == "the claim on old.example.test expired; run repo domain add again")
151        guard case .empty = model.state else {
152            Issue.record("expected the removed claim gone, got \(model.state)")
153            return
154        }
155    }
156
157    @Test func verifyAndRemoveNameTheDomain() async throws {
158        let (client, stub) = try makeClient()
159        for _ in 0..<2 {
160            stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"domain":"d","state":"verified"},"exit_code":0}"#, match: "cmd"))
161            stub.enqueue(.init(status: 200, json: domainsJSON, match: "argv=list"))
162        }
163        let model = PageDomainsViewModel(client: client, repoPath: "krz/gitbay")
164
165        #expect(await model.verify("docs.example.test"))
166        await model.remove("www.example.test")
167
168        #expect(try writes(stub).map { $0["argv"] as? [String] } == [
169            ["repo", "domain", "verify", "krz/gitbay", "docs.example.test"],
170            ["repo", "domain", "remove", "krz/gitbay", "www.example.test"],
171        ])
172    }
173}