krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
main: HutchTests/SystemStatusRepositoryTests.swift · raw
1import Foundation
2import Testing
3@testable import Hutch
4
5@MainActor
6struct SystemStatusRepositoryTests {
7
8 @Test
9 func fallsBackToPersistedSnapshotWhenRefreshFails() async throws {
10 let defaultsName = "SystemStatusRepositoryTests-\(UUID().uuidString)"
11 let defaults = try #require(UserDefaults(suiteName: defaultsName))
12 defaults.removePersistentDomain(forName: defaultsName)
13 defer { defaults.removePersistentDomain(forName: defaultsName) }
14
15 let cachedSnapshot = SystemStatusSnapshot(
16 services: [
17 StatusServiceState(id: "git.sr.ht", name: "git.sr.ht", slug: "git.sr.ht", status: .degraded, description: nil)
18 ],
19 activeIncidents: [],
20 lastUpdated: Date(timeIntervalSince1970: 120)
21 )
22 let cacheStore = SystemStatusCacheStore(defaults: defaults)
23 let initialRepository = SystemStatusRepository(
24 service: TestSystemStatusService(
25 snapshotHTMLHandler: { Self.cachedSnapshotHTML },
26 incidentsDataHandler: { Data(Self.emptyRSS.utf8) }
27 ),
28 cacheStore: cacheStore,
29 now: { Date(timeIntervalSince1970: 120) }
30 )
31
32 _ = try await initialRepository.snapshotResult(forceRefresh: true)
33
34 let fallbackRepository = SystemStatusRepository(
35 service: TestSystemStatusService(
36 snapshotHTMLHandler: { throw SRHTError.httpError(503) },
37 incidentsDataHandler: { Data(Self.emptyRSS.utf8) }
38 ),
39 ttl: 0,
40 cacheStore: cacheStore,
41 now: { Date(timeIntervalSince1970: 180) }
42 )
43
44 let result = try await fallbackRepository.snapshotResult(forceRefresh: true)
45
46 #expect(result.value == cachedSnapshot)
47 #expect(result.isStale)
48 #expect(result.lastSuccessfulAt == Date(timeIntervalSince1970: 120))
49 #expect(result.refreshErrorMessage != nil)
50 }
51
52 @Test
53 func fallsBackToPersistedIncidentsWhenRefreshFails() async throws {
54 let defaultsName = "SystemStatusRepositoryTests-\(UUID().uuidString)"
55 let defaults = try #require(UserDefaults(suiteName: defaultsName))
56 defaults.removePersistentDomain(forName: defaultsName)
57 defer { defaults.removePersistentDomain(forName: defaultsName) }
58
59 let cachedIncidents = [
60 StatusIncident(
61 id: "incident-1",
62 title: "builds.sr.ht outage",
63 summary: "Builds are failing.",
64 url: URL(string: "https://status.sr.ht/issues/1/"),
65 publishedAt: Date(timeIntervalSince1970: 200),
66 updatedAt: nil,
67 isActive: true
68 )
69 ]
70 let cacheStore = SystemStatusCacheStore(defaults: defaults)
71 let initialRepository = SystemStatusRepository(
72 service: TestSystemStatusService(
73 snapshotHTMLHandler: { Self.cachedOperationalHTML },
74 incidentsDataHandler: { Data(Self.cachedIncidentRSS.utf8) }
75 ),
76 cacheStore: cacheStore,
77 now: { Date(timeIntervalSince1970: 220) }
78 )
79
80 _ = try await initialRepository.recentIncidentsResult(forceRefresh: true)
81
82 let fallbackRepository = SystemStatusRepository(
83 service: TestSystemStatusService(
84 snapshotHTMLHandler: { Self.cachedOperationalHTML },
85 incidentsDataHandler: { throw SRHTError.httpError(504) }
86 ),
87 ttl: 0,
88 cacheStore: cacheStore,
89 now: { Date(timeIntervalSince1970: 260) }
90 )
91
92 let result = try await fallbackRepository.recentIncidentsResult(forceRefresh: true)
93
94 #expect(result.value == cachedIncidents)
95 #expect(result.isStale)
96 #expect(result.lastSuccessfulAt == Date(timeIntervalSince1970: 220))
97 #expect(result.refreshErrorMessage != nil)
98 }
99}
100
101private struct TestSystemStatusService: SystemStatusServing {
102 let snapshotHTMLHandler: @Sendable () async throws -> String
103 let incidentsDataHandler: @Sendable () async throws -> Data
104
105 func fetchSnapshotHTML() async throws -> String {
106 try await snapshotHTMLHandler()
107 }
108
109 func fetchIncidentFeedData() async throws -> Data {
110 try await incidentsDataHandler()
111 }
112}
113
114private extension SystemStatusRepositoryTests {
115 nonisolated static let cachedSnapshotHTML = #"""
116 <div class="component" data-status="disrupted">
117 <a href="/affected/git.sr.ht/">git.sr.ht</a>
118 <span class="component-status">Disrupted</span>
119 </div>
120 """#
121
122 nonisolated static let cachedOperationalHTML = #"""
123 <div class="component" data-status="ok">
124 <a href="/affected/meta.sr.ht/">meta.sr.ht</a>
125 <span class="component-status">Operational</span>
126 </div>
127 """#
128
129 nonisolated static let cachedIncidentRSS = #"""
130 <rss version="2.0">
131 <channel>
132 <item>
133 <title>builds.sr.ht outage</title>
134 <link>https://status.sr.ht/issues/1/</link>
135 <pubDate>Thu, 01 Jan 1970 00:03:20 +0000</pubDate>
136 <guid>incident-1</guid>
137 <description><p>Builds are failing.</p></description>
138 </item>
139 </channel>
140 </rss>
141 """#
142
143 nonisolated static let emptyRSS = #"""
144 <rss version="2.0">
145 <channel></channel>
146 </rss>
147 """#
148}