a native ios client for gitbay

client ios swift

https://gitbay.org

gitbayTests/ComposeViewModelTests.swift

ui-smoke
gitbay-ios/gitbayTests/ComposeViewModelTests.swift history · blame · raw

182 lines · 8033 bytes

  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 let createdJSON = #"{"protocol_version":1,"data":{"number":7},"exit_code":0}"#
 16private let okJSON = #"{"protocol_version":1,"data":{},"exit_code":0}"#
 17
 18private func argvOf(_ seen: StubProtocol.Seen) throws -> ([String], String?) {
 19    let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any])
 20    return (try #require(body["argv"] as? [String]), body["stdin"] as? String)
 21}
 22
 23@MainActor
 24struct IssueCreateViewModelTests {
 25
 26    @Test func createSendsTitleInArgvAndBodyOverStdin() async throws {
 27        let (client, stub) = try makeClient()
 28        stub.enqueue(.init(status: 200, json: createdJSON))
 29        let model = IssueCreateViewModel(client: client, repoPath: "krz/gitbay")
 30
 31        let number = await model.create(title: "org READMEs", body: "render them properly")
 32
 33        #expect(number == 7)
 34        let (argv, stdin) = try argvOf(try #require(stub.seen.first))
 35        #expect(argv == ["issue", "create", "krz/gitbay", "--title", "org READMEs", "--file", "-"])
 36        #expect(stdin == "render them properly")
 37    }
 38
 39    @Test func anEmptyBodyOmitsStdinAndTheFileFlag() async throws {
 40        let (client, stub) = try makeClient()
 41        stub.enqueue(.init(status: 200, json: createdJSON))
 42        let model = IssueCreateViewModel(client: client, repoPath: "krz/gitbay")
 43
 44        _ = await model.create(title: "just a title", body: "")
 45
 46        let (argv, stdin) = try argvOf(try #require(stub.seen.first))
 47        #expect(argv == ["issue", "create", "krz/gitbay", "--title", "just a title"])
 48        #expect(stdin == nil)
 49    }
 50
 51    @Test func aRefusalSurfacesAndReturnsNil() async throws {
 52        let (client, stub) = try makeClient()
 53        stub.enqueue(.init(status: 403, json:
 54            #"{"protocol_version":1,"error":"krz/gitbay is archived and read-only","exit_code":4}"#))
 55        let model = IssueCreateViewModel(client: client, repoPath: "krz/gitbay")
 56
 57        let number = await model.create(title: "t", body: "b")
 58
 59        #expect(number == nil)
 60        #expect(model.errorMessage == "krz/gitbay is archived and read-only")
 61    }
 62}
 63
 64@MainActor
 65struct MRCreateViewModelTests {
 66
 67    @Test func targetPrefillsFromRepoShow() async throws {
 68        let (client, stub) = try makeClient()
 69        stub.enqueue(.init(status: 200, json: """
 70            {"protocol_version":1,"data":{"path":"krz/gitbay","visibility":"public",\
 71            "default_branch":"main"},"exit_code":0}
 72            """))
 73        let model = MRCreateViewModel(client: client, repoPath: "krz/gitbay")
 74
 75        await model.loadDefaultBranch()
 76
 77        #expect(model.defaultBranch == "main")
 78    }
 79
 80    @Test func createSendsBranchesTitleAndBody() async throws {
 81        let (client, stub) = try makeClient()
 82        stub.enqueue(.init(status: 200, json:
 83            #"{"protocol_version":1,"data":{"number":4,"head_sha":"aa"},"exit_code":0}"#))
 84        let model = MRCreateViewModel(client: client, repoPath: "krz/gitbay")
 85
 86        let number = await model.create(
 87            source: "fix-thing", target: "main", title: "fix the thing", body: "details"
 88        )
 89
 90        #expect(number == 4)
 91        let (argv, stdin) = try argvOf(try #require(stub.seen.first))
 92        #expect(argv == ["mr", "create", "krz/gitbay",
 93                         "--source", "fix-thing", "--target", "main",
 94                         "--title", "fix the thing", "--file", "-"])
 95        #expect(stdin == "details")
 96    }
 97}
 98
 99@MainActor
100struct EditAndMilestoneTests {
101
102    private let issueShow = """
103        {"protocol_version":1,"data":{"number":11,"title":"iOS app","state":"open",\
104        "author":"krz","body":"Build it.","created_at":"2026-08-20T10:00:00.000Z"},"exit_code":0}
105        """
106
107    @Test func issueEditSendsTitleAndBodyAlways() async throws {
108        let (client, stub) = try makeClient()
109        stub.enqueue(.init(status: 200, json: issueShow, match: "argv=show"))
110        let model = IssueDetailViewModel(client: client, repoPath: "krz/gitbay", number: 11)
111        await model.load()
112        stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
113        stub.enqueue(.init(status: 200, json: issueShow, match: "argv=show"))
114
115        await model.edit(title: "iOS app (v1)", body: "")
116
117        let write = try #require(stub.seen.first { $0.method == "POST" })
118        let (argv, stdin) = try argvOf(write)
119        #expect(argv == ["issue", "edit", "krz/gitbay", "11", "--title", "iOS app (v1)", "--file", "-"])
120        #expect(stdin == "") // empty body clears, matching the CLI
121    }
122
123    @Test func milestoneSetAndClearSpellNoneCorrectly() async throws {
124        let (client, stub) = try makeClient()
125        stub.enqueue(.init(status: 200, json: issueShow, match: "argv=show"))
126        let model = IssueDetailViewModel(client: client, repoPath: "krz/gitbay", number: 11)
127        await model.load()
128        for _ in 0..<2 {
129            stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
130            stub.enqueue(.init(status: 200, json: issueShow, match: "argv=show"))
131        }
132
133        await model.setMilestone("v1.0.0")
134        await model.setMilestone(nil)
135
136        let writes = try stub.seen.filter { $0.method == "POST" }.map { try argvOf($0).0 }
137        #expect(writes[0] == ["issue", "milestone", "krz/gitbay", "11", "v1.0.0"])
138        #expect(writes[1] == ["issue", "milestone", "krz/gitbay", "11", "none"])
139    }
140
141    @Test func milestonesLoadOnceForThePicker() async throws {
142        let (client, stub) = try makeClient()
143        stub.enqueue(.init(status: 200, json: issueShow, match: "argv=show"))
144        let model = IssueDetailViewModel(client: client, repoPath: "krz/gitbay", number: 11)
145        await model.load()
146        stub.enqueue(.init(status: 200, json: """
147            {"protocol_version":1,"data":[{"title":"v1.0.0","state":"open","open":3,"closed":5}],\
148            "exit_code":0}
149            """, match: "argv=milestone"))
150
151        await model.loadMilestones()
152        await model.loadMilestones() // second call must not refetch
153
154        #expect(model.availableMilestones?.first?.title == "v1.0.0")
155        #expect(stub.seen.count { ($0.url.query() ?? "").contains("argv=milestone") } == 1)
156    }
157
158    @Test func mrEditSendsTitleAndBody() async throws {
159        let (client, stub) = try makeClient()
160        let mrShow = """
161            {"protocol_version":1,"data":{"number":7,"title":"old","state":"open","author":"cmc",\
162            "source":"b","target_ref":"main","head_sha":"aa","created_at":"2026-08-20T10:00:00.000Z"},\
163            "exit_code":0}
164            """
165        stub.enqueue(.init(status: 200, json: mrShow, match: "argv=show"))
166        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"output":"","exit_code":0}"#, match: "argv=diff"))
167        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"exit_code":0}"#, match: "argv=threads"))
168        let model = MRDetailViewModel(client: client, repoPath: "krz/gitbay", number: 7)
169        await model.load()
170        stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
171        stub.enqueue(.init(status: 200, json: mrShow, match: "argv=show"))
172        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"output":"","exit_code":0}"#, match: "argv=diff"))
173        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"exit_code":0}"#, match: "argv=threads"))
174
175        await model.edit(title: "new title", body: "new body")
176
177        let write = try #require(stub.seen.first { $0.method == "POST" })
178        let (argv, stdin) = try argvOf(write)
179        #expect(argv == ["mr", "edit", "krz/gitbay", "7", "--title", "new title", "--file", "-"])
180        #expect(stdin == "new body")
181    }
182}