gitbayTests/RepoCredentialTests.swift

36835f99e6d463f3e18c5c3fc3562bb92513f52b
gitbay-ios/gitbayTests/RepoCredentialTests.swift history · blame · raw

160 lines · 7550 bytes

14 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 body(_ seen: StubProtocol.Seen) throws -> [String: Any] {
 16    try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any])
 17}
 18
 19private let okJSON = #"{"protocol_version":1,"data":{},"exit_code":0}"#
 20private let secretsJSON = #"{"protocol_version":1,"data":["DEPLOY_TOKEN","NPM_TOKEN"],"exit_code":0}"#
 21private let deployKeysJSON = """
 22    {"protocol_version":1,"data":[\
 23    {"fingerprint":"SHA256:abc","algo":"ssh-ed25519","mode":"rw","label":"ci@box","last_used_at":"2026-10-10T08:00:00.000Z"},\
 24    {"fingerprint":"SHA256:def","algo":"ssh-ed25519","mode":"ro","label":"","expires_at":"2026-11-10T08:00:00Z"}\
 25    ],"exit_code":0}
 26    """
 27
 28@MainActor
 29struct BuildSecretsViewModelTests {
 30
 31    @Test func listsSecretNames() async throws {
 32        let (client, stub) = try makeClient()
 33        stub.enqueue(.init(status: 200, json: secretsJSON))
 34        let model = BuildSecretsViewModel(client: client, repoPath: "krz/gitbay")
 35        await model.load()
 36        #expect(model.state.value == ["DEPLOY_TOKEN", "NPM_TOKEN"])
 37        #expect(stub.seen.first?.url.query() == "argv=repo&argv=secret&argv=list&argv=krz/gitbay")
 38    }
 39
 40    @Test func noSecretsIsAnEmptyState() async throws {
 41        let (client, stub) = try makeClient()
 42        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":null,"exit_code":0}"#))
 43        let model = BuildSecretsViewModel(client: client, repoPath: "krz/gitbay")
 44        await model.load()
 45        guard case .empty = model.state else {
 46            Issue.record("expected .empty, got \(model.state)")
 47            return
 48        }
 49    }
 50
 51    @Test func setSendsTheValueOnStdinNeverInArgv() async throws {
 52        let (client, stub) = try makeClient()
 53        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"secret":"DEPLOY_TOKEN"},"exit_code":0}"#, match: "cmd"))
 54        stub.enqueue(.init(status: 200, json: secretsJSON, match: "argv=list"))
 55        let model = BuildSecretsViewModel(client: client, repoPath: "krz/gitbay")
 56
 57        #expect(await model.set(name: " DEPLOY_TOKEN ", value: "s3cret"))
 58
 59        let sent = try body(try #require(stub.seen.first))
 60        #expect(sent["argv"] as? [String] == ["repo", "secret", "set", "krz/gitbay", "DEPLOY_TOKEN"])
 61        #expect(sent["stdin"] as? String == "s3cret")
 62        #expect(model.state.value == ["DEPLOY_TOKEN", "NPM_TOKEN"])
 63    }
 64
 65    @Test func aRefusedNameSaysWhy() async throws {
 66        let (client, stub) = try makeClient()
 67        stub.enqueue(.init(status: 400, json: #"{"protocol_version":1,"error":"secret names are env-var shaped: uppercase letters, digits, _","exit_code":2}"#))
 68        let model = BuildSecretsViewModel(client: client, repoPath: "krz/gitbay")
 69
 70        #expect(await model.set(name: "lower", value: "x") == false)
 71        #expect(model.actionError == "secret names are env-var shaped: uppercase letters, digits, _")
 72    }
 73
 74    @Test func aFailedWriteStillRefreshesTheList() async throws {
 75        let (client, stub) = try makeClient()
 76        stub.enqueue(.init(status: 404, json: #"{"protocol_version":1,"error":"no secret NPM_TOKEN on krz/gitbay","exit_code":3}"#, match: "cmd"))
 77        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":["DEPLOY_TOKEN"],"exit_code":0}"#, match: "argv=list"))
 78        let model = BuildSecretsViewModel(client: client, repoPath: "krz/gitbay")
 79
 80        await model.remove("NPM_TOKEN")
 81
 82        #expect(model.actionError == "no secret NPM_TOKEN on krz/gitbay")
 83        #expect(model.state.value == ["DEPLOY_TOKEN"])
 84    }
 85
 86    @Test func aMultiLineValueKeepsItsNewlines() async throws {
 87        let (client, stub) = try makeClient()
 88        stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
 89        stub.enqueue(.init(status: 200, json: secretsJSON, match: "argv=list"))
 90        let model = BuildSecretsViewModel(client: client, repoPath: "krz/gitbay")
 91        let key = "-----BEGIN KEY-----\nabc\n-----END KEY-----\n"
 92
 93        #expect(await model.set(name: "BOT_SSH_KEY", value: key))
 94
 95        #expect(try body(try #require(stub.seen.first))["stdin"] as? String == key)
 96    }
 97
 98    @Test func removeNamesTheSecret() async throws {
 99        let (client, stub) = try makeClient()
100        stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
101        stub.enqueue(.init(status: 200, json: secretsJSON, match: "argv=list"))
102        let model = BuildSecretsViewModel(client: client, repoPath: "krz/gitbay")
103
104        await model.remove("NPM_TOKEN")
105
106        #expect(try body(try #require(stub.seen.first))["argv"] as? [String]
107            == ["repo", "secret", "remove", "krz/gitbay", "NPM_TOKEN"])
108    }
109}
110
111@MainActor
112struct DeployKeysViewModelTests {
113
114    @Test func listsKeysWithModeAndDates() async throws {
115        let (client, stub) = try makeClient()
116        stub.enqueue(.init(status: 200, json: deployKeysJSON))
117        let model = DeployKeysViewModel(client: client, repoPath: "krz/gitbay")
118        await model.load()
119
120        let keys = try #require(model.state.value)
121        #expect(keys.map(\.fingerprint) == ["SHA256:abc", "SHA256:def"])
122        #expect(keys[0].canPush && !keys[1].canPush)
123        #expect(keys[0].lastUsedAt != nil && keys[0].expiresAt == nil)
124        #expect(keys[1].lastUsedAt == nil && keys[1].expiresAt != nil)
125        #expect(stub.seen.first?.url.query() == "argv=repo&argv=deploy-key&argv=list&argv=krz/gitbay")
126    }
127
128    @Test func addSendsTheKeyOnStdinWithModeAndLifetime() async throws {
129        let (client, stub) = try makeClient()
130        for _ in 0..<2 {
131            stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"fingerprint":"SHA256:x","mode":"ro"},"exit_code":0}"#, match: "cmd"))
132            stub.enqueue(.init(status: 200, json: deployKeysJSON, match: "argv=list"))
133        }
134        let model = DeployKeysViewModel(client: client, repoPath: "krz/gitbay")
135
136        #expect(await model.add(publicKey: "ssh-ed25519 AAAA ci\n", canPush: false, ttl: nil))
137        #expect(await model.add(publicKey: "ssh-ed25519 AAAA ci", canPush: true, ttl: "30d"))
138
139        let writes = try stub.seen.filter { $0.url.path().hasSuffix("/cmd") }.map(body)
140        #expect(writes.map { $0["argv"] as? [String] } == [
141            ["repo", "deploy-key", "add", "krz/gitbay"],
142            ["repo", "deploy-key", "add", "krz/gitbay", "--rw", "--ttl", "30d"],
143        ])
144        #expect(writes.allSatisfy { $0["stdin"] as? String == "ssh-ed25519 AAAA ci" })
145    }
146
147    @Test func removeNamesTheFingerprint() async throws {
148        let (client, stub) = try makeClient()
149        stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
150        stub.enqueue(.init(status: 200, json: deployKeysJSON, match: "argv=list"))
151        stub.enqueue(.init(status: 200, json: deployKeysJSON, match: "argv=list"))
152        let model = DeployKeysViewModel(client: client, repoPath: "krz/gitbay")
153        await model.load()
154
155        await model.remove(try #require(model.state.value?.first))
156
157        let writes = try stub.seen.filter { $0.url.path().hasSuffix("/cmd") }.map(body)
158        #expect(writes.first?["argv"] as? [String] == ["repo", "deploy-key", "remove", "krz/gitbay", "SHA256:abc"])
159    }
160}