gitbayTests/BuildListPagingTests.swift

v1.10.0
gitbay-ios/gitbayTests/BuildListPagingTests.swift history · blame · raw

114 lines · 4347 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 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}