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>&lt;p&gt;Builds are failing.&lt;/p&gt;</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}