From 942e848dde82ecea973f3598990ecb637bf8e55e Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Sat, 5 Sep 2026 11:13:55 +0400 Subject: [PATCH] test(update-checker): rewrite tests to exercise real production code REPLACED: three worthless tests that only tested test code, not production: - statusMessageUpToDateFormat built the expected string locally and matched it - statusMessageUpdateReadyFormat same self-referential test - statusChannelsAreIndependent was literally #expect(true, ...) ADDED: four real tests that drive production code: - dubaiTimeCheckTimeFormat: assert DubaiTime.checkTime() formats as HH:MM Dubai - manualCheckUpToDateIncludesTimestamp: inject stub appcast via testAppcastJSON seam, drive UpdateChecker.checkNow(manual: true), verify statusMessage matches exact format - automaticCheckUpToDateLeavesMessageNil: verify automatic check (manual: false) leaves statusMessage nil when up-to-date - statusChannelsAreIndependent: construct real AppModel via AppDelegate.makeLaunchModel(), assert setStatus() does NOT affect updateStatusMessage, setUpdateStatus() does NOT affect statusLine, and vice versa. PROVES the defect is caught: test fails with 3 issues if updateStatusMessage is reverted to an alias of statusLine. ADDED: testAppcastJSON seam to UpdateChecker for test injection of appcast data. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp --- Sources/Shotdeck/UpdateChecker.swift | 9 +- Tests/ShotdeckTests/UpdateCheckerTests.swift | 121 ++++++++++++++----- 2 files changed, 97 insertions(+), 33 deletions(-) diff --git a/Sources/Shotdeck/UpdateChecker.swift b/Sources/Shotdeck/UpdateChecker.swift index 5b705cb..4ccc003 100644 --- a/Sources/Shotdeck/UpdateChecker.swift +++ b/Sources/Shotdeck/UpdateChecker.swift @@ -36,6 +36,8 @@ final class UpdateChecker { /// this value (including nil) is returned instead of checking the file system. var snapshotPreviousVersionOverride: String? var snapshotUsesPreviousVersionOverride: Bool = false + /// Test seam: override appcast JSON. When set, returns this instead of fetching from URL. + var testAppcastJSON: String? init() { let config = URLSessionConfiguration.ephemeral @@ -359,7 +361,12 @@ final class UpdateChecker { } private func fetchAppcast() async throws -> Appcast { - let data = try await fetchData(from: Self.resolvedAppcastURL()) + let data: Data + if let testJSON = testAppcastJSON { + data = testJSON.data(using: .utf8) ?? Data() + } else { + data = try await fetchData(from: Self.resolvedAppcastURL()) + } return try JSONDecoder().decode(Appcast.self, from: data) } diff --git a/Tests/ShotdeckTests/UpdateCheckerTests.swift b/Tests/ShotdeckTests/UpdateCheckerTests.swift index d0d694c..7b31ed5 100644 --- a/Tests/ShotdeckTests/UpdateCheckerTests.swift +++ b/Tests/ShotdeckTests/UpdateCheckerTests.swift @@ -5,11 +5,10 @@ import Testing @Test("DubaiTime.checkTime formats as HH:MM Dubai") func dubaiTimeCheckTimeFormat() { - let testDate = Date(timeIntervalSince1970: 1725458520) // 2024-09-04 10:42:00 UTC - let result = DubaiTime.checkTime(testDate) + let now = Date() + let result = DubaiTime.checkTime(now) - // The format should be HH:MM Dubai (24-hour time in Dubai timezone) - // Dubai is UTC+4, so a UTC time needs conversion + // Dubai timezone format: HH:MM Dubai let pattern = "^[0-9]{2}:[0-9]{2} Dubai$" let regex = try? NSRegularExpression(pattern: pattern, options: []) let range = NSRange(result.startIndex..