krz/hutch

an ios client for sourcehut

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

main: HutchTests/HomeViewModelTests.swift · raw

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