import Foundation import Testing @testable import gitbay private func makeClient() throws -> (GitbayClient, StubProtocol.Box) { let box = StubProtocol.box() let client = GitbayClient( instance: try GitbayInstance(url: "https://gitbay.org"), token: "test-token", session: box.session() ) return (client, box) } private func argv(_ url: URL) -> [String] { URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.compactMap(\.value) ?? [] } @MainActor struct AdminInstanceTests { @Test func statsDecode() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: """ {"protocol_version":1,"data":{"counts":{"users":3,"orgs":1,"repos":1,"issues":1,"open_issues":1,\ "mrs":4,"open_mrs":2},"db_bytes":876544,"repo_bytes":38159,"lfs_bytes":0,\ "repos":[{"path":"sim/app","bytes":38159}]}} """)) let model = AdminInstanceViewModel(client: client) await model.loadStats() let stats = try #require(model.stats.value) #expect(stats.counts.openMRs == 2) #expect(stats.repos.first?.bytes == 38159) #expect(argv(try #require(stub.seen.first).url) == ["admin", "stats"]) } @Test func auditTakesTheTypedFilters() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: """ {"protocol_version":1,"data":[{"id":172,"actor":"sim","action":"cmd admin org limits",\ "data":"{}","created_at":"2026-10-14T08:09:23.929Z"}]} """)) let model = AdminInstanceViewModel(client: client) model.auditActor = " sim " model.auditAction = "cmd admin" model.auditSince = "" await model.loadAudit() #expect(argv(try #require(stub.seen.first).url) == ["audit", "--actor", "sim", "--action", "cmd admin", "--limit", "200"]) #expect(model.audit.value?.first?.action == "cmd admin org limits") } @Test func aBadSinceSaysWhy() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 400, json: #"{"protocol_version":1,"error":"--since takes a duration like 24h or a date","exit_code":2}"#)) let model = AdminInstanceViewModel(client: client) model.auditSince = "yesterday" await model.loadAudit() guard case .failed(let message) = model.audit else { Issue.record("not failed"); return } #expect(message == "--since takes a duration like 24h or a date") } @Test func runnersDecodeAndRemoveByFingerprint() async throws { let (client, stub) = try makeClient() let json = """ {"protocol_version":1,"data":{"queue":{"pending":3,"claimed_24h":1,"claim_wait_avg_s":1,\ "claim_wait_max_s":1,"reaped_24h":0},"runners":[{"username":"ci","fingerprint":"SHA256:r",\ "last_seen":"2026-10-14T05:36:28.062Z","scope":"krz/gitbay","build_repo":"krz/gitbay",\ "build_number":9,"build_job":"test"}]}} """ stub.enqueue(.init(status: 200, json: json, match: "argv=runners")) stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{},"exit_code":0}"#, match: "cmd")) stub.enqueue(.init(status: 200, json: json, match: "argv=runners")) let model = AdminInstanceViewModel(client: client) await model.loadRunners() let runner = try #require(model.runners.value?.runners.first) #expect(runner.buildNumber == 9) #expect(model.runners.value?.queue.pending == 3) await model.removeRunner(runner) let post = try #require(stub.seen.first { $0.method == "POST" }) let body = try #require(try JSONSerialization.jsonObject(with: post.body) as? [String: Any]) #expect(body["argv"] as? [String] == ["admin", "runners", "remove", "SHA256:r"]) } @Test func inboundCheckDecodesOff() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"enabled":false,"messages":0,"unseen":0,"require_dkim":false}}"#)) let model = AdminInstanceViewModel(client: client) await model.checkInbound() #expect(model.inbound.value?.enabled == false) } }