krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v3.2.1: 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) == [2, 3])
 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}