summaryrefslogtreecommitdiff
path: root/HutchTests
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-11 23:36:35 -0500
committerChristian Cleberg <[email protected]>2026-04-11 23:36:35 -0500
commit00cc231e9b419d27b412036d93457c2cbabe0b16 (patch)
tree6b042b802fcddcd1e03f1ae0f666965620225485 /HutchTests
parent0fde7529ceaebf1cd9baa01826d95d587023d001 (diff)
downloadhutch-00cc231e9b419d27b412036d93457c2cbabe0b16.tar.gz
hutch-00cc231e9b419d27b412036d93457c2cbabe0b16.tar.bz2
hutch-00cc231e9b419d27b412036d93457c2cbabe0b16.zip
Add SourceHut system status screen and home disruption banner
Diffstat (limited to 'HutchTests')
-rw-r--r--HutchTests/SRHTWebURLTests.swift12
-rw-r--r--HutchTests/SystemStatusServiceTests.swift129
2 files changed, 141 insertions, 0 deletions
diff --git a/HutchTests/SRHTWebURLTests.swift b/HutchTests/SRHTWebURLTests.swift
new file mode 100644
index 0000000..15f242d
--- /dev/null
+++ b/HutchTests/SRHTWebURLTests.swift
@@ -0,0 +1,12 @@
+import Foundation
+import Testing
+@testable import Hutch
+
+struct SRHTWebURLTests {
+
+ @Test
+ func browserOnlyServiceURLsUseCanonicalHosts() {
+ #expect(SRHTWebURL.chat.absoluteString == "https://chat.sr.ht")
+ #expect(SRHTWebURL.status.absoluteString == "https://status.sr.ht")
+ }
+}
diff --git a/HutchTests/SystemStatusServiceTests.swift b/HutchTests/SystemStatusServiceTests.swift
new file mode 100644
index 0000000..58ace33
--- /dev/null
+++ b/HutchTests/SystemStatusServiceTests.swift
@@ -0,0 +1,129 @@
+import Foundation
+import Testing
+@testable import Hutch
+
+struct SystemStatusServiceTests {
+
+ @Test
+ func parsesCurrentStatusHTMLIntoServicesAndActiveIncidents() throws {
+ let snapshot = try SystemStatusService.parseSnapshotHTML(Self.sampleHTML, fetchedAt: Date(timeIntervalSince1970: 100))
+
+ #expect(snapshot.services.count == 3)
+ #expect(snapshot.services[0].name == "git.sr.ht")
+ #expect(snapshot.services[0].status == .degraded)
+ #expect(snapshot.services[1].status == .operational)
+ #expect(snapshot.hasDisruption)
+ #expect(snapshot.activeIncidents.count == 1)
+ #expect(snapshot.activeIncidents[0].title == "SourceHut disrupted due to DDoS attack")
+ #expect(snapshot.activeIncidents[0].summary == "SourceHut was disrupted by a DDoS attack.")
+ #expect(snapshot.activeIncidents[0].url?.absoluteString == "https://status.sr.ht/issues/2026-04-06-ddos-attack/")
+ }
+
+ @Test
+ func parsesIncidentFeedRSS() async throws {
+ let incidents = try await SystemStatusService.parseIncidentFeedXML(Data(Self.sampleRSS.utf8))
+
+ #expect(incidents.count == 2)
+ #expect(incidents[0].title == "SourceHut disrupted due to DDoS attack")
+ #expect(incidents[0].isActive == true)
+ #expect(incidents[0].summary == "SourceHut was disrupted by a DDoS attack.")
+ #expect(incidents[1].title == "Planned maintenance on all services")
+ #expect(incidents[1].isActive == false)
+ #expect(incidents[1].updatedAt != nil)
+ }
+
+ @Test
+ func bannerSummaryPrefersSpecificServiceThenCount() {
+ let operational = SystemStatusSnapshot(
+ services: [
+ StatusServiceState(id: "git.sr.ht", name: "git.sr.ht", slug: "git.sr.ht", status: .operational, description: nil)
+ ],
+ activeIncidents: [],
+ lastUpdated: .now
+ )
+
+ let oneDisrupted = SystemStatusSnapshot(
+ services: [
+ StatusServiceState(id: "git.sr.ht", name: "git.sr.ht", slug: "git.sr.ht", status: .degraded, description: nil),
+ StatusServiceState(id: "hg.sr.ht", name: "hg.sr.ht", slug: "hg.sr.ht", status: .operational, description: nil)
+ ],
+ activeIncidents: [],
+ lastUpdated: .now
+ )
+
+ let multipleDisrupted = SystemStatusSnapshot(
+ services: [
+ StatusServiceState(id: "git.sr.ht", name: "git.sr.ht", slug: "git.sr.ht", status: .degraded, description: nil),
+ StatusServiceState(id: "builds.sr.ht", name: "builds.sr.ht", slug: "builds.sr.ht", status: .majorOutage, description: nil)
+ ],
+ activeIncidents: [],
+ lastUpdated: .now
+ )
+
+ #expect(operational.hasDisruption == false)
+ #expect(oneDisrupted.bannerSummary == "git.sr.ht disrupted")
+ #expect(multipleDisrupted.bannerSummary == "2 services disrupted")
+ }
+
+ private static let sampleHTML = #"""
+ <!DOCTYPE html>
+ <html>
+ <body class="status-homepage status-disrupted">
+ <div class="announcement-box" style="border-bottom: 0">
+ <div class="padding">
+ <p>
+ <a href="/issues/2026-04-06-ddos-attack/"><strong class="bold">SourceHut disrupted due to DDoS attack →</strong></a>
+ </p>
+ <p><small><a href="/affected/git.sr.ht/" class="tag no-underline">git.sr.ht</a></small></p>
+ <p><strong>SourceHut was disrupted by a DDoS attack</strong>.</p>
+ </div>
+ <hr class="clean announcement-box">
+ </div>
+ <div class="components">
+ <div class="component" data-status="disrupted">
+ <a href="/affected/git.sr.ht/" class="no-underline">git.sr.ht</a>
+ <span class="component-status">Disrupted</span>
+ </div>
+ <div class="component" data-status="ok">
+ <a href="/affected/hg.sr.ht/" class="no-underline">hg.sr.ht</a>
+ <span class="component-status">Operational</span>
+ </div>
+ <div class="component" data-status="notice">
+ <a href="/affected/man.sr.ht/" class="no-underline">man.sr.ht</a>
+ <span class="component-status">Maintenance</span>
+ </div>
+ </div>
+ <a href="https://status.sr.ht/issues/2026-04-06-ddos-attack/" class="issue no-underline">
+ <small class="date float-right " title="Apr 5 10:00:00 2026 UTC">April 5, 2026 at 10:00 AM UTC</small>
+ <h3>SourceHut disrupted due to DDoS attack</h3>
+ <strong class="error">▲ This issue is not resolved yet</strong>
+ </a>
+ </body>
+ </html>
+ """#
+
+ private static let sampleRSS = #"""
+ <?xml version="1.0" encoding="utf-8" standalone="yes"?>
+ <rss version="2.0">
+ <channel>
+ <title>sr.ht status</title>
+ <item>
+ <title>SourceHut disrupted due to DDoS attack</title>
+ <link>https://status.sr.ht/issues/2026-04-06-ddos-attack/</link>
+ <pubDate>Sun, 05 Apr 2026 10:00:00 +0000</pubDate>
+ <guid>https://status.sr.ht/issues/2026-04-06-ddos-attack/</guid>
+ <category></category>
+ <description>&lt;p&gt;&lt;strong&gt;SourceHut was disrupted by a DDoS attack&lt;/strong&gt;.&lt;/p&gt;</description>
+ </item>
+ <item>
+ <title>[Resolved] Planned maintenance on all services</title>
+ <link>https://status.sr.ht/issues/2025-10-22-planned-maintenance/</link>
+ <pubDate>Wed, 22 Oct 2025 11:00:00 +0000</pubDate>
+ <guid>https://status.sr.ht/issues/2025-10-22-planned-maintenance/</guid>
+ <category>2025-10-22 12:25:00</category>
+ <description>&lt;p&gt;&lt;strong&gt;The maintenance is complete&lt;/strong&gt;.&lt;/p&gt;</description>
+ </item>
+ </channel>
+ </rss>
+ """#
+}