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