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>&lt;p&gt;&lt;strong&gt;SourceHut was disrupted by a DDoS attack&lt;/strong&gt;.&lt;/p&gt;</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>&lt;p&gt;&lt;strong&gt;The maintenance is complete&lt;/strong&gt;.&lt;/p&gt;</description>
125        </item>
126      </channel>
127    </rss>
128    """#
129}