krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.5.0: HutchTests/HomeViewModelTests.swift · raw
1import Foundation
2import Testing
3@testable import Hutch
4
5struct HomeViewModelTests {
6
7 @Test
8 func failedBuildsKeepsOnlyFailedAndTimedOutJobs() {
9 let jobs = [
10 makeJob(id: 1, status: .success, created: Date(timeIntervalSince1970: 10)),
11 makeJob(id: 2, status: .failed, created: Date(timeIntervalSince1970: 20)),
12 makeJob(id: 3, status: .timeout, created: Date(timeIntervalSince1970: 30)),
13 makeJob(id: 4, status: .running, created: Date(timeIntervalSince1970: 40))
14 ]
15
16 let failedBuilds = HomeViewModel.failedBuilds(from: jobs)
17
18 #expect(failedBuilds.map(\.job.id) == [3, 2])
19 }
20
21 @Test
22 func failedBuildsWithinLookbackExcludeOlderFailures() {
23 let now = Date(timeIntervalSince1970: 60 * 60 * 24 * 20)
24 let recentFailure = HomeBuildItem(
25 job: JobSummary(
26 id: 1,
27 created: now.addingTimeInterval(-(60 * 60 * 24)),
28 updated: now.addingTimeInterval(-(60 * 60 * 24)),
29 status: .failed,
30 note: nil,
31 tags: [],
32 visibility: nil,
33 image: nil,
34 tasks: []
35 ),
36 repositoryName: nil,
37 repositoryOwner: nil
38 )
39 let oldFailure = HomeBuildItem(
40 job: JobSummary(
41 id: 2,
42 created: now.addingTimeInterval(-(60 * 60 * 24 * 10)),
43 updated: now.addingTimeInterval(-(60 * 60 * 24 * 10)),
44 status: .timeout,
45 note: nil,
46 tags: [],
47 visibility: nil,
48 image: nil,
49 tasks: []
50 ),
51 repositoryName: nil,
52 repositoryOwner: nil
53 )
54 let recentSuccess = HomeBuildItem(
55 job: JobSummary(
56 id: 3,
57 created: now.addingTimeInterval(-(60 * 60 * 24)),
58 updated: now.addingTimeInterval(-(60 * 60 * 24)),
59 status: .success,
60 note: nil,
61 tags: [],
62 visibility: nil,
63 image: nil,
64 tasks: []
65 ),
66 repositoryName: nil,
67 repositoryOwner: nil
68 )
69
70 let filtered = HomeViewModel.failedBuilds(
71 in: [recentFailure, oldFailure, recentSuccess],
72 lookbackDays: 7,
73 now: now,
74 calendar: Calendar(identifier: .gregorian)
75 )
76
77 #expect(filtered.map(\.job.id) == [1])
78 }
79
80 @Test
81 func failedBuildLookbackDaysFallsBackToDefaultWhenUnsetOrInvalid() {
82 let defaults = UserDefaults(suiteName: #function)!
83 defaults.removePersistentDomain(forName: #function)
84
85 #expect(HomeViewModel.failedBuildLookbackDays(defaults: defaults) == HomeViewModel.defaultFailedBuildLookbackDays)
86
87 defaults.set(99, forKey: AppStorageKeys.homeFailedBuildLookbackDays)
88
89 #expect(HomeViewModel.failedBuildLookbackDays(defaults: defaults) == HomeViewModel.defaultFailedBuildLookbackDays)
90 }
91
92 @Test
93 func deduplicateInboxThreadsCollapsesMessagesIntoOneThreadSummary() {
94 let list = InboxMailingListReference(
95 id: 1,
96 rid: "list",
97 name: "hutch-devel",
98 owner: Entity(canonicalName: "~owner")
99 )
100 let first = InboxThreadSummary(
101 rootEmailID: 10,
102 rootMessageID: "message-1",
103 threadRootEmailIDs: [10],
104 threadRootMessageIDs: ["message-1"],
105 listID: list.id,
106 listRID: list.rid,
107 listName: list.name,
108 listOwner: list.owner,
109 subject: "Re: [PATCH] add search",
110 latestSender: Entity(canonicalName: "~alice"),
111 lastActivityAt: Date(timeIntervalSince1970: 100),
112 messageCount: 1,
113 repo: "hutch",
114 containsPatch: true,
115 isUnread: true
116 )
117 let second = InboxThreadSummary(
118 rootEmailID: 11,
119 rootMessageID: "message-2",
120 threadRootEmailIDs: [11],
121 threadRootMessageIDs: ["message-2"],
122 listID: list.id,
123 listRID: list.rid,
124 listName: list.name,
125 listOwner: list.owner,
126 subject: "[PATCH] add search",
127 latestSender: Entity(canonicalName: "~bob"),
128 lastActivityAt: Date(timeIntervalSince1970: 200),
129 messageCount: 2,
130 repo: "hutch",
131 containsPatch: true,
132 isUnread: true
133 )
134
135 let deduplicated = HomeViewModel.deduplicateInboxThreads([first, second])
136
137 #expect(deduplicated.count == 1)
138 #expect(deduplicated[0].rootEmailID == 11)
139 #expect(deduplicated[0].threadRootEmailIDs == [10, 11])
140 #expect(deduplicated[0].threadRootMessageIDs == ["message-1", "message-2"])
141 #expect(deduplicated[0].messageCount == 2)
142 }
143
144 @Test
145 func matchesCurrentUserAssigneeNormalizesCanonicalNameAndUsername() {
146 let currentUser = User(
147 id: 42,
148 created: nil,
149 updated: nil,
150 username: "owner",
151 canonicalName: "~owner",
152 email: "owner@example.com",
153 url: nil,
154 location: nil,
155 bio: nil,
156 avatar: nil,
157 pronouns: nil,
158 userType: nil,
159 receivesPaidServices: nil,
160 suspensionNotice: nil
161 )
162
163 #expect(HomeViewModel.matchesCurrentUserAssignee(Entity(canonicalName: "~owner"), currentUser: currentUser))
164 #expect(HomeViewModel.matchesCurrentUserAssignee(Entity(canonicalName: "owner"), currentUser: currentUser))
165 #expect(HomeViewModel.matchesCurrentUserAssignee(Entity(canonicalName: currentUser.username), currentUser: currentUser))
166 #expect(HomeViewModel.matchesCurrentUserAssignee(Entity(canonicalName: "~someone-else"), currentUser: currentUser) == false)
167 }
168
169 @Test
170 func primaryRepositoryReferenceParsesManifestSourceURL() {
171 let manifest = """
172 image: alpine/latest
173 sources:
174 - https://git.sr.ht/~owner/hutch
175 tasks:
176 - true
177 """
178
179 let repository = HomeViewModel.primaryRepositoryReference(in: manifest)
180
181 #expect(repository?.ownerCanonicalName == "~owner")
182 #expect(repository?.name == "hutch")
183 }
184
185 @Test
186 func buildItemsAreSortedForTriage() {
187 let jobs = [
188 makeJob(id: 1, status: .success, created: Date(timeIntervalSince1970: 10)),
189 makeJob(id: 2, status: .running, created: Date(timeIntervalSince1970: 20)),
190 makeJob(id: 3, status: .failed, created: Date(timeIntervalSince1970: 30))
191 ]
192
193 let sorted = HomeViewModel.buildItems(from: jobs)
194
195 #expect(sorted.map(\.job.id) == [3, 2, 1])
196 }
197
198 private func makeJob(id: Int, status: JobStatus, created: Date) -> HomeJobPayload {
199 HomeJobPayload(
200 id: id,
201 created: created,
202 updated: created,
203 status: status,
204 note: nil,
205 tags: [],
206 visibility: nil,
207 image: nil,
208 tasks: [],
209 manifest: nil
210 )
211 }
212}