krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v2.13.0: HutchTests/SystemStatusServiceTests.swift · raw
1import Foundation
2import Testing
3@testable import Hutch
4
5struct SystemStatusServiceTests {
6
7 @Test
8 func parsesCurrentStatusHTMLIntoServicesAndActiveIncidents() throws {
9 let snapshot = try SystemStatusService.parseSnapshotHTML(Self.sampleHTML, fetchedAt: Date(timeIntervalSince1970: 100))
10
11 #expect(snapshot.services.count == 3)
12 #expect(snapshot.services[0].name == "git.sr.ht")
13 #expect(snapshot.services[0].status == .degraded)
14 #expect(snapshot.services[1].status == .operational)
15 #expect(snapshot.hasDisruption)
16 #expect(snapshot.activeIncidents.count == 1)
17 #expect(snapshot.activeIncidents[0].title == "SourceHut disrupted due to DDoS attack")
18 #expect(snapshot.activeIncidents[0].summary == "SourceHut was disrupted by a DDoS attack.")
19 #expect(snapshot.activeIncidents[0].url?.absoluteString == "https://status.sr.ht/issues/2026-04-06-ddos-attack/")
20 }
21
22 @Test
23 func parsesIncidentFeedRSS() async throws {
24 let incidents = try await SystemStatusService.parseIncidentFeedXML(Data(Self.sampleRSS.utf8))
25
26 #expect(incidents.count == 2)
27 #expect(incidents[0].title == "SourceHut disrupted due to DDoS attack")
28 #expect(incidents[0].isActive == true)
29 #expect(incidents[0].summary == "SourceHut was disrupted by a DDoS attack.")
30 #expect(incidents[1].title == "Planned maintenance on all services")
31 #expect(incidents[1].isActive == false)
32 #expect(incidents[1].updatedAt != nil)
33 }
34
35 @Test
36 func bannerSummaryPrefersSpecificServiceThenCount() {
37 let operational = SystemStatusSnapshot(
38 services: [
39 StatusServiceState(id: "git.sr.ht", name: "git.sr.ht", slug: "git.sr.ht", status: .operational, description: nil)
40 ],
41 activeIncidents: [],
42 lastUpdated: .now
43 )
44
45 let oneDisrupted = SystemStatusSnapshot(
46 services: [
47 StatusServiceState(id: "git.sr.ht", name: "git.sr.ht", slug: "git.sr.ht", status: .degraded, description: nil),
48 StatusServiceState(id: "hg.sr.ht", name: "hg.sr.ht", slug: "hg.sr.ht", status: .operational, description: nil)
49 ],
50 activeIncidents: [],
51 lastUpdated: .now
52 )
53
54 let multipleDisrupted = SystemStatusSnapshot(
55 services: [
56 StatusServiceState(id: "git.sr.ht", name: "git.sr.ht", slug: "git.sr.ht", status: .degraded, description: nil),
57 StatusServiceState(id: "builds.sr.ht", name: "builds.sr.ht", slug: "builds.sr.ht", status: .majorOutage, description: nil)
58 ],
59 activeIncidents: [],
60 lastUpdated: .now
61 )
62
63 #expect(operational.hasDisruption == false)
64 #expect(oneDisrupted.bannerSummary == "git.sr.ht disrupted")
65 #expect(multipleDisrupted.bannerSummary == "2 services disrupted")
66 }
67
68 private static let sampleHTML = #"""
69 <!DOCTYPE html>
70 <html>
71 <body class="status-homepage status-disrupted">
72 <div class="announcement-box" style="border-bottom: 0">
73 <div class="padding">
74 <p>
75 <a href="/issues/2026-04-06-ddos-attack/"><strong class="bold">SourceHut disrupted due to DDoS attack →</strong></a>
76 </p>
77 <p><small><a href="/affected/git.sr.ht/" class="tag no-underline">git.sr.ht</a></small></p>
78 <p><strong>SourceHut was disrupted by a DDoS attack</strong>.</p>
79 </div>
80 <hr class="clean announcement-box">
81 </div>
82 <div class="components">
83 <div class="component" data-status="disrupted">
84 <a href="/affected/git.sr.ht/" class="no-underline">git.sr.ht</a>
85 <span class="component-status">Disrupted</span>
86 </div>
87 <div class="component" data-status="ok">
88 <a href="/affected/hg.sr.ht/" class="no-underline">hg.sr.ht</a>
89 <span class="component-status">Operational</span>
90 </div>
91 <div class="component" data-status="notice">
92 <a href="/affected/man.sr.ht/" class="no-underline">man.sr.ht</a>
93 <span class="component-status">Maintenance</span>
94 </div>
95 </div>
96 <a href="https://status.sr.ht/issues/2026-04-06-ddos-attack/" class="issue no-underline">
97 <small class="date float-right " title="Apr 5 10:00:00 2026 UTC">April 5, 2026 at 10:00 AM UTC</small>
98 <h3>SourceHut disrupted due to DDoS attack</h3>
99 <strong class="error">▲ This issue is not resolved yet</strong>
100 </a>
101 </body>
102 </html>
103 """#
104
105 private static let sampleRSS = #"""
106 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
107 <rss version="2.0">
108 <channel>
109 <title>sr.ht status</title>
110 <item>
111 <title>SourceHut disrupted due to DDoS attack</title>
112 <link>https://status.sr.ht/issues/2026-04-06-ddos-attack/</link>
113 <pubDate>Sun, 05 Apr 2026 10:00:00 +0000</pubDate>
114 <guid>https://status.sr.ht/issues/2026-04-06-ddos-attack/</guid>
115 <category></category>
116 <description><p><strong>SourceHut was disrupted by a DDoS attack</strong>.</p></description>
117 </item>
118 <item>
119 <title>[Resolved] Planned maintenance on all services</title>
120 <link>https://status.sr.ht/issues/2025-10-22-planned-maintenance/</link>
121 <pubDate>Wed, 22 Oct 2025 11:00:00 +0000</pubDate>
122 <guid>https://status.sr.ht/issues/2025-10-22-planned-maintenance/</guid>
123 <category>2025-10-22 12:25:00</category>
124 <description><p><strong>The maintenance is complete</strong>.</p></description>
125 </item>
126 </channel>
127 </rss>
128 """#
129}