gitbayTests/BuildListPagingTests.swift
114 lines · 4347 bytes
12 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 buildRow(_ number: Int, subject: String? = nil) -> String {
16 let subjectField = subject.map { #","subject":"\#($0)""# } ?? ""
17 return """
18 {"number":\(number),"job":"test","status":"success",\
19 "sha":"db7503f06f11c31a41f1c8e91187e1ff8f861c7f","ref":"main",\
20 "created_at":"2026-09-22T04:40:16.000Z"\(subjectField)}
21 """
22}
23
24struct BuildSubjectTests {
25
26 private func decode(_ json: String) throws -> Build {
27 try GitbayClient.decoder().decode(Build.self, from: Data(json.utf8))
28 }
29
30 @Test func aRowLeadsWithTheCommitSubject() throws {
31 let build = try decode(buildRow(1479, subject: "CHANGELOG: v1.34.1"))
32 #expect(build.subject == "CHANGELOG: v1.34.1")
33 #expect(build.title == "#1479 CHANGELOG: v1.34.1")
34 }
35
36 /// A build whose commit is gone has no subject to report; the sha is
37 /// all there is.
38 @Test func aRowWithoutASubjectFallsBackToTheSha() throws {
39 let build = try decode(buildRow(12))
40 #expect(build.subject == nil)
41 #expect(build.title == "#12 db7503f06f")
42 }
43
44 @Test func anEmptySubjectAlsoFallsBack() throws {
45 let build = try decode(buildRow(12, subject: ""))
46 #expect(build.title == "#12 db7503f06f")
47 }
48}
49
50private func page(_ rows: [String], next: String? = nil) -> String {
51 let nextField = next.map { #","next":"\#($0)""# } ?? ""
52 return #"{"protocol_version":1,"data":{"items":["# + rows.joined(separator: ",")
53 + "]" + nextField + #"},"exit_code":0}"#
54}
55
56@MainActor
57struct BuildListPagingTests {
58
59 /// The web asks for thirty; so does the app.
60 @Test func asksForThirtyAndFollowsTheCursor() async throws {
61 let (client, stub) = try makeClient()
62 stub.enqueue(.init(status: 200, json: page([buildRow(5), buildRow(4)], next: "YnVpbGQ6NA"),
63 match: "argv=build&argv=list"))
64 let model = BuildListViewModel(client: client, repoPath: "krz/gitbay")
65
66 await model.load()
67
68 #expect(try #require(model.state.value).map(\.number) == [5, 4])
69 #expect(model.list.hasMore)
70 let first = try #require(stub.seen.first)
71 #expect(first.url.query() == "argv=build&argv=list&argv=krz/gitbay&argv=--limit&argv=30")
72
73 stub.enqueue(.init(status: 200, json: page([buildRow(3)]), match: "argv=build&argv=list"))
74 await model.list.loadMore()
75
76 #expect(try #require(model.state.value).map(\.number) == [5, 4, 3])
77 #expect(model.list.hasMore == false)
78 let second = try #require(stub.seen.last)
79 #expect(second.url.query()?.hasSuffix("argv=--limit&argv=30&argv=--cursor&argv=YnVpbGQ6NA") == true)
80 }
81
82 /// Rows are shown in the order the server sent them. A client sort
83 /// would reorder rows across a page boundary.
84 @Test func keepsTheServersOrder() async throws {
85 let (client, stub) = try makeClient()
86 stub.enqueue(.init(status: 200, json: page([buildRow(3), buildRow(4)])))
87 let model = BuildListViewModel(client: client, repoPath: "krz/gitbay")
88
89 await model.load()
90
91 #expect(try #require(model.state.value).map(\.number) == [3, 4])
92 }
93
94 @Test func aFilterChangeStartsOverFromTheFirstPage() async throws {
95 let (client, stub) = try makeClient()
96 stub.enqueue(.init(status: 200, json: page([buildRow(5)], next: "YnVpbGQ6NQ")))
97 let model = BuildListViewModel(client: client, repoPath: "krz/gitbay")
98 await model.load()
99
100 stub.enqueue(.init(status: 200, json: page([])))
101 var filter = BuildFilter()
102 filter.status = .failure
103 model.filter = filter
104 await until {
105 if case .empty = model.state { return true }
106 return false
107 }
108
109 let second = try #require(stub.seen.last)
110 #expect(second.url.query()
111 == "argv=build&argv=list&argv=krz/gitbay&argv=--status&argv=failure&argv=--limit&argv=30")
112 #expect(model.list.hasMore == false)
113 }
114}