1fabc2abdbf1

report wallhaven status page as an outage instead of a decoding failure (v0.6.3)

time
2026-09-19 14:07 UTC · 3 weeks ago
machine
koh2
agent
deepseek/deepseek-flash
session
fix pill clicks and error banner
parent
987de19ffe51
3 files Info.plist+2 −2Sources/Haven/API/WallhavenAPI.swift+14 −0Tests/HavenTests/WallhavenAPITests.swift+25 −2
Info.plist+2 −2
@@ -9,9 +9,9 @@99     <key>CFBundleName</key>1010     <string>Haven</string>1111     <key>CFBundleShortVersionString</key>12-    <string>0.6.2</string>12+    <string>0.6.3</string>1313     <key>CFBundleVersion</key>14-    <string>8</string>14+    <string>9</string>1515     <key>LSMinimumSystemVersion</key>1616     <string>13.0</string>1717     <key>LSUIElement</key>

view file

Sources/Haven/API/WallhavenAPI.swift+14 −0
@@ -94,6 +94,12 @@9494     }9595   }9696 97+  /// Wallhaven's status page is HTML; the API itself is always JSON, so any98+  /// other content type means the response did not come from the API.99+  static func isStatusPage(response: HTTPURLResponse) -> Bool {100+    !(response.mimeType ?? "").contains("json")101+  }102+97103   private func performSearch(queryItems: [URLQueryItem]) async throws -> ([Wallpaper], Meta?) {98104     var urlComponents = URLComponents(99105       url: Configuration.baseURL.appendingPathComponent("search"), resolvingAgainstBaseURL: true)!@@ -109,6 +115,14 @@109115 110116     guard (200...299).contains(httpResponse.statusCode) else {111117       throw WallhavenError.apiError(Self.message(forStatus: httpResponse.statusCode))118+    }119+120+    // Wallhaven answers API calls with its HTML status page ("taking a little121+    // nap") while its server is down, under a 200 status. Saying so beats122+    // reporting a decoding failure for a body that was never going to decode.123+    guard !Self.isStatusPage(response: httpResponse) else {124+      throw WallhavenError.apiError(125+        "Wallhaven is offline right now — it's serving its status page.")112126     }113127 114128     do {

view file

Tests/HavenTests/WallhavenAPITests.swift+25 −2
@@ -38,12 +38,14 @@3838     return WallhavenAPI(session: URLSession(configuration: configuration))3939   }4040 41-  private func response(for request: URLRequest, statusCode: Int = 200) -> HTTPURLResponse {41+  private func response(42+    for request: URLRequest, statusCode: Int = 200, contentType: String = "application/json"43+  ) -> HTTPURLResponse {4244     HTTPURLResponse(4345       url: request.url!,4446       statusCode: statusCode,4547       httpVersion: "HTTP/1.1",46-      headerFields: ["Content-Type": "application/json"]48+      headerFields: ["Content-Type": contentType]4749     )!4850   }4951 @@ -153,6 +155,27 @@153155       } catch {154156         Issue.record("Unexpected error: \(error)")155157       }158+    }159+  }160+161+  @Test("Wallhaven's HTML status page is reported as an outage, not a bad response")162+  func statusPageIsReportedAsOffline() async {163+    MockURLProtocol.requestHandler = { request in164+      (165+        response(166+          for: request,167+          contentType: "text/html; charset=utf-8"),168+        Data("<!DOCTYPE html><title>wallhaven.cc Status</title>".utf8)169+      )170+    }171+172+    do {173+      _ = try await makeAPI().searchWallpapers(query: "")174+      Issue.record("Expected a status page to throw")175+    } catch WallhavenError.apiError(let message) {176+      #expect(message == "Wallhaven is offline right now — it's serving its status page.")177+    } catch {178+      Issue.record("Unexpected error: \(error)")156179     }157180   }158181 

view file