diff options
| author | Christian Cleberg <[email protected]> | 2026-04-12 00:15:13 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-12 00:15:13 -0500 |
| commit | cb1ea5ea4f163d87053285d3fb7999b12a3558b9 (patch) | |
| tree | 84b562d12e642ff494194387b1b4e1d15b065eb0 /HutchTests/SystemStatusRepositoryTests.swift | |
| parent | 1c5a417277d986ee6fb7dc9dff0332338bdf6f3e (diff) | |
| download | hutch-cb1ea5ea4f163d87053285d3fb7999b12a3558b9.tar.gz hutch-cb1ea5ea4f163d87053285d3fb7999b12a3558b9.tar.bz2 hutch-cb1ea5ea4f163d87053285d3fb7999b12a3558b9.zip | |
harden system status and app reliabilityv2.13.1
Diffstat (limited to 'HutchTests/SystemStatusRepositoryTests.swift')
| -rw-r--r-- | HutchTests/SystemStatusRepositoryTests.swift | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/HutchTests/SystemStatusRepositoryTests.swift b/HutchTests/SystemStatusRepositoryTests.swift new file mode 100644 index 0000000..fb08732 --- /dev/null +++ b/HutchTests/SystemStatusRepositoryTests.swift @@ -0,0 +1,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: nil, + 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> + """# +} |
