a native ios client for gitbay

client ios swift

https://gitbay.org

gitbayTests/DiscoveryTests.swift

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

161 lines · 6200 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
 15struct FeedEventTests {
 16
 17    private func decode(_ json: String) throws -> FeedEvent {
 18        let decoder = JSONDecoder()
 19        decoder.dateDecodingStrategy = .iso8601
 20        return try decoder.decode(FeedEvent.self, from: Data(json.utf8))
 21    }
 22
 23    @Test func knownKindsGetPhrasesAndDestinations() throws {
 24        let merged = try decode("""
 25            {"id":777,"repo":"krz/gitbay-ios","actor":"cmc","kind":"mr.merged",\
 26            "data":{"number":6,"sha":"b55fbb"},"created_at":"2026-08-27T15:31:10Z"}
 27            """)
 28        #expect(merged.phrase == "merged !6")
 29        #expect(merged.destination == .mr(repo: "krz/gitbay-ios", number: 6))
 30
 31        let build = try decode("""
 32            {"id":1,"repo":"krz/gitbay","actor":"","kind":"build.success",\
 33            "data":{"number":56,"job":"ci"},"created_at":"2026-08-27T15:31:10Z"}
 34            """)
 35        #expect(build.phrase == "build success ci")
 36        #expect(build.displayActor == "gitbay")
 37        #expect(build.destination == .build(repo: "krz/gitbay", number: 56))
 38
 39        let release = try decode("""
 40            {"id":2,"repo":"krz/orgo","actor":"krz","kind":"release.created",\
 41            "data":{"tag":"v2.1.0"},"created_at":"2026-08-27T15:31:10Z"}
 42            """)
 43        #expect(release.phrase == "released v2.1.0")
 44        #expect(release.destination == .release(repo: "krz/orgo", tag: "v2.1.0"))
 45    }
 46
 47    @Test func unknownKindsRenderRawAndFallBackToTheRepo() throws {
 48        let event = try decode("""
 49            {"id":3,"repo":"krz/gitbay","kind":"wiki.edited",\
 50            "data":{"page":"Parity"},"created_at":"2026-08-27T15:31:10Z"}
 51            """)
 52        #expect(event.phrase == "wiki.edited")
 53        #expect(event.destination == .repo("krz/gitbay"))
 54    }
 55
 56    @Test func missingDataDoesNotSinkTheEvent() throws {
 57        let event = try decode("""
 58            {"id":4,"repo":"krz/gitbay","actor":"cmc","kind":"push",\
 59            "created_at":"2026-08-27T15:31:10Z"}
 60            """)
 61        #expect(event.phrase == "pushed")
 62        #expect(event.destination == .repo("krz/gitbay"))
 63    }
 64}
 65
 66@MainActor
 67struct GrepViewModelTests {
 68
 69    @Test func searchGroupsMatchesByFileInServerOrder() async throws {
 70        let (client, stub) = try makeClient()
 71        stub.enqueue(.init(status: 200, json: """
 72            {"protocol_version":1,"data":[\
 73            {"path":"internal/a.go","line":3,"text":"foo bar"},\
 74            {"path":"internal/a.go","line":9,"text":"more foo"},\
 75            {"path":"cmd/b.go","line":1,"text":"foo again"}\
 76            ],"exit_code":0}
 77            """))
 78        let model = GrepViewModel(client: client, repoPath: "krz/gitbay")
 79
 80        await model.search("  foo ")
 81
 82        #expect(model.byFile.map(\.file) == ["internal/a.go", "cmd/b.go"])
 83        #expect(model.byFile[0].matches.map(\.line) == [3, 9])
 84        let seen = try #require(stub.seen.first)
 85        #expect(seen.url.query() == "argv=repo&argv=grep&argv=krz/gitbay&argv=foo")
 86    }
 87
 88    @Test func noMatchesIsAnEmptyStateNamingTheQuery() async throws {
 89        let (client, stub) = try makeClient()
 90        stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"exit_code":0}"#))
 91        let model = GrepViewModel(client: client, repoPath: "krz/gitbay")
 92
 93        await model.search("nothing")
 94
 95        guard case .empty(let message)? = model.state else {
 96            Issue.record("expected .empty, got \(String(describing: model.state))")
 97            return
 98        }
 99        #expect(message.contains("nothing"))
100    }
101
102    @Test func aBlankQueryDoesNotSearch() async throws {
103        let (client, stub) = try makeClient()
104        _ = client
105        let model = GrepViewModel(client: client, repoPath: "krz/gitbay")
106
107        await model.search("   ")
108
109        #expect(model.state == nil)
110        #expect(stub.seen.isEmpty)
111    }
112}
113
114@MainActor
115struct RepoSearchTests {
116
117    @Test func aTypedQuerySearchesServerSideAfterTheDebounce() async throws {
118        let (client, stub) = try makeClient()
119        stub.enqueue(.init(status: 200, json: """
120            {"protocol_version":1,"data":{"items":[\
121            {"path":"cmc/notes","visibility":"private"}]},"exit_code":0}
122            """, match: "argv=repo&argv=list"))
123        stub.enqueue(.init(status: 200, json: """
124            {"protocol_version":1,"data":[\
125            {"path":"krz/space-wiki","visibility":"public","description":"A fun wiki about space.",\
126            "topics":["wiki"]}],"exit_code":0}
127            """, match: "argv=search"))
128        let model = RepoListViewModel(client: client)
129        await model.load()
130
131        model.searchText = "space"
132        // Before the server answers, the client filter runs over loaded
133        // pages  no match here.
134        #expect(model.visibleRepos.isEmpty)
135        try await Task.sleep(for: .milliseconds(700))
136
137        // The server search found a public repo not in the account's list.
138        #expect(model.visibleRepos.map(\.path) == ["krz/space-wiki"])
139        #expect(stub.seen.contains {
140            $0.url.query() == "argv=repo&argv=search&argv=space"
141        })
142    }
143
144    @Test func clearingTheQueryRestoresThePagedList() async throws {
145        let (client, stub) = try makeClient()
146        stub.enqueue(.init(status: 200, json: """
147            {"protocol_version":1,"data":{"items":[\
148            {"path":"cmc/notes","visibility":"private"}]},"exit_code":0}
149            """, match: "argv=repo&argv=list"))
150        let model = RepoListViewModel(client: client)
151        await model.load()
152
153        model.searchText = "zzz"
154        model.searchText = ""
155        try await Task.sleep(for: .milliseconds(500))
156
157        #expect(model.visibleRepos.map(\.path) == ["cmc/notes"])
158        // The emptied query never reached the server.
159        #expect(!stub.seen.contains { ($0.url.query() ?? "").contains("argv=search") })
160    }
161}