krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.2.0: HutchTests/RepositoryACLViewModelTests.swift · raw
1import Foundation
2import Testing
3@testable import Hutch
4
5struct RepositoryACLViewModelTests {
6
7 @Test
8 @MainActor
9 func addValidationRejectsOwnerAndDuplicates() async {
10 let service = MockRepositoryACLService()
11 service.fetchResponses = [[
12 RepositoryACLEntry(
13 id: 2,
14 mode: .ro,
15 entity: Entity(canonicalName: "~alice")
16 )
17 ]]
18 let viewModel = RepositoryACLViewModel(repository: makeRepository(), service: service)
19 viewModel.addUsername = "~owner"
20 #expect(viewModel.addValidationMessage == "The repository owner already has access.")
21
22 await viewModel.load()
23 viewModel.addUsername = "alice"
24 #expect(viewModel.addValidationMessage == "That user already has access.")
25 }
26
27 @Test
28 @MainActor
29 func addEntryRefreshesListAfterSuccess() async {
30 let service = MockRepositoryACLService()
31 service.fetchResponses = [[
32 RepositoryACLEntry(
33 id: 2,
34 mode: .rw,
35 entity: Entity(canonicalName: "~alice")
36 )
37 ]]
38
39 let viewModel = RepositoryACLViewModel(repository: makeRepository(), service: service)
40 viewModel.addUsername = "alice"
41 viewModel.addMode = .rw
42
43 let didAdd = await viewModel.addEntry()
44
45 #expect(didAdd)
46 #expect(service.upsertRequests == [MockRepositoryACLService.UpsertRequest(repositoryId: 1, entity: "~alice", mode: .rw)])
47 #expect(service.fetchRequestRids == ["rid-1"])
48 #expect(viewModel.visibleEntries.map(\.entity.canonicalName) == ["~alice"])
49 #expect(viewModel.addUsername.isEmpty)
50 }
51
52 @Test
53 @MainActor
54 func permissionUpdateFailureLeavesExistingEntryUntouched() async {
55 let service = MockRepositoryACLService()
56 service.upsertError = SRHTError.httpError(500)
57
58 let entry = RepositoryACLEntry(
59 id: 2,
60 mode: .ro,
61 entity: Entity(canonicalName: "~alice")
62 )
63 let viewModel = RepositoryACLViewModel(repository: makeRepository(), service: service)
64 service.fetchResponses = [[entry]]
65 await viewModel.load()
66
67 await viewModel.updatePermission(for: entry, to: .rw)
68
69 #expect(viewModel.visibleEntries.first?.mode == .ro)
70 #expect(viewModel.updatingEntryIDs.isEmpty)
71 #expect(viewModel.error != nil)
72 }
73
74 @Test
75 @MainActor
76 func removeFailureKeepsEntryVisible() async {
77 let service = MockRepositoryACLService()
78 service.deleteError = SRHTError.httpError(500)
79
80 let entry = RepositoryACLEntry(
81 id: 2,
82 mode: .ro,
83 entity: Entity(canonicalName: "~alice")
84 )
85 let viewModel = RepositoryACLViewModel(repository: makeRepository(), service: service)
86 service.fetchResponses = [[entry]]
87 await viewModel.load()
88
89 await viewModel.removeEntry(entry)
90
91 #expect(viewModel.visibleEntries.map(\.id) == [2])
92 #expect(viewModel.deletingEntryIDs.isEmpty)
93 #expect(viewModel.error != nil)
94 }
95
96 @Test
97 @MainActor
98 func initialLoadFailureSetsBlockingErrorState() async {
99 let service = MockRepositoryACLService()
100 service.fetchError = SRHTError.httpError(500)
101
102 let viewModel = RepositoryACLViewModel(repository: makeRepository(), service: service)
103
104 await viewModel.load()
105
106 #expect(viewModel.loadError != nil)
107 #expect(viewModel.visibleEntries.isEmpty)
108 }
109
110 @MainActor
111 private func makeRepository() -> RepositorySummary {
112 RepositorySummary(
113 fields: .init(
114 id: 1,
115 rid: "rid-1",
116 service: .git,
117 name: "repo",
118 description: nil,
119 visibility: .publicVisibility,
120 updated: .now,
121 owner: Entity(canonicalName: "~owner"),
122 head: nil
123 )
124 )
125 }
126}
127
128@MainActor
129private final class MockRepositoryACLService: RepositoryACLServicing {
130 struct UpsertRequest: Equatable {
131 let repositoryId: Int
132 let entity: String
133 let mode: AccessMode
134 }
135
136 var fetchResponses: [[RepositoryACLEntry]] = []
137 var fetchError: Error?
138 var upsertResponse = RepositoryACLEntry(
139 id: 2,
140 mode: .ro,
141 entity: Entity(canonicalName: "~alice")
142 )
143 var upsertError: Error?
144 var deleteError: Error?
145
146 private(set) var fetchRequestRids: [String] = []
147 private(set) var upsertRequests: [UpsertRequest] = []
148 private(set) var deleteRequestIDs: [Int] = []
149
150 func fetchACLs(repositoryRid: String) async throws -> [RepositoryACLEntry] {
151 fetchRequestRids.append(repositoryRid)
152 if let fetchError {
153 throw fetchError
154 }
155 if !fetchResponses.isEmpty {
156 return fetchResponses.removeFirst()
157 }
158 return []
159 }
160
161 func upsertACL(repositoryId: Int, entity: String, mode: AccessMode) async throws -> RepositoryACLEntry {
162 upsertRequests.append(UpsertRequest(repositoryId: repositoryId, entity: entity, mode: mode))
163 if let upsertError {
164 throw upsertError
165 }
166 return RepositoryACLEntry(id: upsertResponse.id, mode: mode, entity: Entity(canonicalName: entity))
167 }
168
169 func deleteACL(entryId: Int) async throws {
170 deleteRequestIDs.append(entryId)
171 if let deleteError {
172 throw deleteError
173 }
174 }
175}