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