Client + watchdog menu-bar app: polls the claire daemon (status/fleet/ budget/health), shows NEEDS-YOU, and auto-recovers the daemon on silent DB-write failure via launchctl kickstart. Built on LilithMenuBar / LilithTrayResources. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 lines
2.4 KiB
Swift
48 lines
2.4 KiB
Swift
import XCTest
|
|
@testable import ClaireTrayApp
|
|
|
|
final class WatchdogTests: XCTestCase {
|
|
private let t0 = Date(timeIntervalSince1970: 1_000_000)
|
|
|
|
func testHealthyStaysOK() {
|
|
var w = Watchdog(failureThreshold: 3, cooldown: 120, maxAttempts: 5)
|
|
XCTAssertEqual(w.record(healthy: true, now: t0), .ok)
|
|
XCTAssertEqual(w.record(healthy: true, now: t0.addingTimeInterval(25)), .ok)
|
|
}
|
|
|
|
func testRecoversOnlyAfterThreshold() {
|
|
var w = Watchdog(failureThreshold: 3, cooldown: 120, maxAttempts: 5)
|
|
XCTAssertEqual(w.record(healthy: false, now: t0), .watching) // 1
|
|
XCTAssertEqual(w.record(healthy: false, now: t0.addingTimeInterval(25)), .watching) // 2
|
|
XCTAssertEqual(w.record(healthy: false, now: t0.addingTimeInterval(50)), .recover) // 3 → kick
|
|
}
|
|
|
|
func testCooldownSuppressesRepeatKicks() {
|
|
var w = Watchdog(failureThreshold: 1, cooldown: 120, maxAttempts: 5)
|
|
XCTAssertEqual(w.record(healthy: false, now: t0), .recover) // kick at t0
|
|
// Within cooldown: no second kick.
|
|
XCTAssertEqual(w.record(healthy: false, now: t0.addingTimeInterval(60)), .watching)
|
|
// After cooldown: kick again.
|
|
XCTAssertEqual(w.record(healthy: false, now: t0.addingTimeInterval(130)), .recover)
|
|
}
|
|
|
|
func testGivesUpAfterMaxAttempts() {
|
|
var w = Watchdog(failureThreshold: 1, cooldown: 0, maxAttempts: 2)
|
|
XCTAssertEqual(w.record(healthy: false, now: t0), .recover) // attempt 1
|
|
XCTAssertEqual(w.record(healthy: false, now: t0.addingTimeInterval(1)), .recover) // attempt 2
|
|
XCTAssertEqual(w.record(healthy: false, now: t0.addingTimeInterval(2)), .gaveUp) // ceiling hit
|
|
XCTAssertTrue(w.gaveUp)
|
|
}
|
|
|
|
func testRecoveryResetsAfterDaemonHealthyAgain() {
|
|
var w = Watchdog(failureThreshold: 1, cooldown: 0, maxAttempts: 2)
|
|
_ = w.record(healthy: false, now: t0) // recover
|
|
_ = w.record(healthy: false, now: t0.addingTimeInterval(1)) // recover
|
|
_ = w.record(healthy: false, now: t0.addingTimeInterval(2)) // gaveUp
|
|
XCTAssertTrue(w.gaveUp)
|
|
XCTAssertEqual(w.record(healthy: true, now: t0.addingTimeInterval(3)), .ok) // recovered
|
|
XCTAssertFalse(w.gaveUp)
|
|
// Counters reset → a fresh failure run can recover again.
|
|
XCTAssertEqual(w.record(healthy: false, now: t0.addingTimeInterval(4)), .recover)
|
|
}
|
|
}
|