gitbayTests/AdminRepoTests.swift
129 lines · 5667 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 argv(_ url: URL) -> [String] {
16 URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.compactMap(\.value) ?? []
17}
18
19private func posted(_ stub: StubProtocol.Box) throws -> [[String]] {
20 try stub.seen.filter { $0.method == "POST" }.map { seen in
21 let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any])
22 return try #require(body["argv"] as? [String])
23 }
24}
25
26private let listJSON = """
27 {"protocol_version":1,"data":{"items":[\
28 {"path":"sim/app","visibility":"public","created_at":"2026-10-14T04:19:38.964Z","last_push":"2026-10-14T06:04:58.461Z","bytes":38159},\
29 {"path":"sim/old","visibility":"private","archived":true,"created_at":"2026-10-14T04:19:38.964Z","bytes":10}]}}
30 """
31
32@MainActor
33struct AdminRepoTests {
34
35 @Test func theListIsFilteredByOwnerAndVisibility() async throws {
36 let (client, stub) = try makeClient()
37 stub.enqueue(.init(status: 200, json: listJSON))
38 let model = AdminReposViewModel(client: client)
39 model.owner = " sim "
40 model.visibility = "private"
41
42 await model.load()
43
44 #expect(argv(try #require(stub.seen.first).url)
45 == ["admin", "repo", "list", "--owner", "sim", "--visibility", "private", "--limit", "50"])
46 let repos = try #require(model.list.state.value)
47 #expect(repos.map(\.isArchived) == [false, true])
48 #expect(repos.last?.lastPush == nil)
49 }
50
51 @Test func eachActionSendsItsCommand() {
52 #expect(AdminRepoAction.archive.argv("a/b") == ["admin", "repo", "archive", "a/b"])
53 #expect(AdminRepoAction.unarchive.argv("a/b") == ["admin", "repo", "unarchive", "a/b"])
54 #expect(AdminRepoAction.makePrivate.argv("a/b") == ["admin", "repo", "visibility", "a/b", "private"])
55 #expect(AdminRepoAction.reindex.argv("a/b") == ["admin", "symbols", "reindex", "a/b"])
56 #expect(AdminRepoAction.delete.argv("a/b") == ["admin", "repo", "delete", "a/b", "--yes"])
57 }
58
59 @Test func aReindexSaysItIsQueued() async throws {
60 let (client, stub) = try makeClient()
61 stub.enqueue(.init(status: 200, json: listJSON, match: "argv=list"))
62 stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"repo":"sim/app","state":"queued"},"exit_code":0}"#, match: "cmd"))
63 stub.enqueue(.init(status: 200, json: listJSON, match: "argv=list"))
64 let model = AdminReposViewModel(client: client)
65 await model.load()
66 let repo = try #require(model.list.state.value?.first)
67
68 await model.perform(.reindex, on: repo)
69
70 #expect(try posted(stub) == [["admin", "symbols", "reindex", "sim/app"]])
71 #expect(model.notice?.contains("queued") == true)
72 }
73}
74
75@MainActor
76struct AdminRepoDeleteTests {
77
78 @Test func aDeleteWhoseRetryFindsNothingIsDoneAndAnnounced() async throws {
79 let (client, stub) = try makeClient()
80 stub.enqueue(.init(status: 200, json: listJSON, match: "argv=list"))
81 stub.enqueue(.init(status: 500, json: #"{"protocol_version":1,"error":"cleanup failed","exit_code":1}"#, match: "cmd"))
82 stub.enqueue(.init(status: 404, json: #"{"protocol_version":1,"error":"no repository sim/app","exit_code":3}"#, match: "cmd"))
83 stub.enqueue(.init(status: 200, json: listJSON, match: "argv=list"))
84 let model = AdminReposViewModel(client: client)
85 await model.load()
86 let repo = try #require(model.list.state.value?.first)
87 let announced = NotificationCenter.default.notifications(named: .gitbayRepositoryChanged)
88
89 async let first = announced.first { _ in true }
90 await model.perform(.delete, on: repo)
91
92 #expect(model.actionError == nil)
93 #expect((await first)?.object as? String == "sim/app")
94 // The list stayed on screen through the reload.
95 #expect(model.list.state.value != nil)
96 }
97}
98
99@MainActor
100struct AdminOrgLimitsTests {
101
102 @Test func anOrgsLimitsLoadAndChange() async throws {
103 let (client, stub) = try makeClient()
104 let json = #"{"protocol_version":1,"data":{"org":"acme","repos":0,"bytes":0,"repos_owned":1,"bytes_owned":2,"override":false}}"#
105 stub.enqueue(.init(status: 200, json: json, match: "cmd"))
106 stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"org":"acme","repos":5,"bytes":0,"repos_owned":1,"bytes_owned":2,"override":true}}"#, match: "cmd"))
107 let model = AdminOrgLimitsViewModel(client: client)
108
109 await model.load(org: " acme ")
110 #expect(model.limits?.reposOwned == 1)
111 #expect(model.limits?.orgs == nil)
112
113 #expect(await model.set(repos: "5", megabytes: ""))
114
115 #expect(try posted(stub) == [["admin", "org", "limits", "acme"], ["admin", "org", "limits", "acme", "--repos", "5"]])
116 #expect(model.limits?.repos == 5)
117 }
118
119 @Test func anUnknownOrgSaysSo() async throws {
120 let (client, stub) = try makeClient()
121 stub.enqueue(.init(status: 404, json: #"{"protocol_version":1,"error":"no organization \"nope\"","exit_code":3}"#, match: "cmd"))
122 let model = AdminOrgLimitsViewModel(client: client)
123
124 await model.load(org: "nope")
125
126 #expect(model.limits == nil)
127 #expect(model.actionError == #"no organization "nope""#)
128 }
129}