diff options
| author | Christian Cleberg <[email protected]> | 2026-04-11 23:36:35 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-11 23:36:35 -0500 |
| commit | 00cc231e9b419d27b412036d93457c2cbabe0b16 (patch) | |
| tree | 6b042b802fcddcd1e03f1ae0f666965620225485 /HutchTests | |
| parent | 0fde7529ceaebf1cd9baa01826d95d587023d001 (diff) | |
| download | hutch-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.swift | 12 | ||||
| -rw-r--r-- | HutchTests/SystemStatusServiceTests.swift | 129 |
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><p><strong>SourceHut was disrupted by a DDoS attack</strong>.</p></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><p><strong>The maintenance is complete</strong>.</p></description> + </item> + </channel> + </rss> + """# +} |
