native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
12 kB · 342 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343import Foundationimport ProwlCLISharedimport Testing
@testable import supacode
@MainActorstruct CommandFinishedNotificationTests { private let surfaceId = UUID()
// MARK: - Notification Generation
@Test func generatesNotificationWhenThresholdExceeded() { let state = makeState() state.handleCommandFinished(exitCode: 0, durationNs: 15_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) #expect(state.notifications.first?.title == "Command finished") #expect(state.notifications.first?.body == "Completed in 15s") #expect(state.notifications.first?.surfaceId == surfaceId) }
@Test func doesNotGenerateNotificationUnderThreshold() { let state = makeState() state.handleCommandFinished(exitCode: 0, durationNs: 5_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.isEmpty) }
@Test func doesNotGenerateNotificationAtExactThreshold() { let state = makeState(threshold: 10) state.handleCommandFinished(exitCode: 0, durationNs: 10_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) }
// MARK: - Exit Code Filtering
@Test func doesNotGenerateNotificationForSIGINT() { let state = makeState() state.handleCommandFinished(exitCode: 130, durationNs: 60_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.isEmpty) }
@Test func doesNotGenerateNotificationForSIGTERM() { let state = makeState() state.handleCommandFinished(exitCode: 143, durationNs: 60_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.isEmpty) }
@Test func generatesFailureNotificationForNonZeroExitCode() { let state = makeState() state.handleCommandFinished(exitCode: 1, durationNs: 30_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) #expect(state.notifications.first?.title == "Command failed") #expect(state.notifications.first?.body == "Failed (exit code 1) after 30s") }
@Test func generatesNotificationForNilExitCode() { let state = makeState() state.handleCommandFinished(exitCode: nil, durationNs: 20_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) #expect(state.notifications.first?.title == "Command finished") #expect(state.notifications.first?.body == "Completed in 20s") }
// MARK: - Feature Toggle
@Test func doesNotGenerateNotificationWhenDisabled() { let state = makeState() state.setCommandFinishedNotification(enabled: false, threshold: 10) state.handleCommandFinished(exitCode: 0, durationNs: 60_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.isEmpty) }
@Test func respectsUpdatedThreshold() { let state = makeState() state.setCommandFinishedNotification(enabled: true, threshold: 30) state.handleCommandFinished(exitCode: 0, durationNs: 20_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.isEmpty)
state.handleCommandFinished(exitCode: 0, durationNs: 31_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) }
// MARK: - Recent Interaction Suppression
@Test func doesNotGenerateNotificationAfterRecentKeyInput() { let state = makeState() state.recordKeyInput(forSurfaceID: surfaceId) state.handleCommandFinished(exitCode: 0, durationNs: 60_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.isEmpty) }
@Test func recentKeyInputOnDifferentSurfaceDoesNotSuppressNotification() { let state = makeState() let otherSurfaceId = UUID() state.recordKeyInput(forSurfaceID: otherSurfaceId) state.handleCommandFinished(exitCode: 0, durationNs: 60_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) }
@Test func generatesNotificationWithNoKeyInputHistory() { let state = makeState() state.handleCommandFinished(exitCode: 0, durationNs: 60_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) }
@Test func workflowNotificationIsReadAndMarkedViewedWhenItsWorktreeIsVisible() { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) let worktree = makeWorktree() let state = manager.state(for: worktree) manager.handleCommand(.setNotificationsEnabled(true)) manager.handleCommand(.setSelectedWorktreeID(worktree.id)) state.syncFocus(windowIsKey: true, windowIsVisible: true) var viewed: [Bool] = [] state.onNotificationReceived = { _, _, _, isViewed in viewed.append(isViewed) }
state.appendNotification( title: "Workflow needs attention", body: "Reviewer is waiting", surfaceId: surfaceId, treatAsViewedWhenWorktreeIsVisible: true )
#expect(state.notifications.count == 1) #expect(state.notifications.first?.isRead == true) #expect(viewed == [true]) }
@Test func selectedWorkflowNotificationRemainsUnreadWhenTheAppIsInTheBackground() { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) let worktree = makeWorktree() let state = manager.state(for: worktree) manager.handleCommand(.setNotificationsEnabled(true)) manager.handleCommand(.setSelectedWorktreeID(worktree.id)) state.syncFocus(windowIsKey: false, windowIsVisible: true) var viewed: [Bool] = [] state.onNotificationReceived = { _, _, _, isViewed in viewed.append(isViewed) }
state.appendNotification( title: "Workflow needs attention", body: "Reviewer is waiting", surfaceId: surfaceId, treatAsViewedWhenWorktreeIsVisible: true )
#expect(state.notifications.count == 1) #expect(state.notifications.first?.isRead == false) #expect(viewed == [false]) }
@Test func workflowNotificationRemainsUnreadAndPropagatesForABackgroundWorktree() { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) let worktree = makeWorktree() let state = manager.state(for: worktree) manager.handleCommand(.setNotificationsEnabled(true)) manager.handleCommand(.setSelectedWorktreeID("another-worktree")) var received = 0 state.onNotificationReceived = { _, _, _, _ in received += 1 }
state.appendNotification( title: "Workflow needs attention", body: "Reviewer is waiting", surfaceId: surfaceId, treatAsViewedWhenWorktreeIsVisible: true )
#expect(state.notifications.count == 1) #expect(state.notifications.first?.isRead == false) #expect(received == 1) }
// MARK: - Duration Formatting
@Test func formatDurationSeconds() { #expect(WorktreeTerminalState.formatDuration(5) == "5s") #expect(WorktreeTerminalState.formatDuration(59) == "59s") }
@Test func formatDurationMinutes() { #expect(WorktreeTerminalState.formatDuration(60) == "1m") #expect(WorktreeTerminalState.formatDuration(90) == "1m 30s") #expect(WorktreeTerminalState.formatDuration(3599) == "59m 59s") }
@Test func formatDurationHours() { #expect(WorktreeTerminalState.formatDuration(3600) == "1h") #expect(WorktreeTerminalState.formatDuration(3660) == "1h 1m") #expect(WorktreeTerminalState.formatDuration(7200) == "2h") }
// MARK: - Manager Propagation
@Test func managerPropagatesSettingsToExistingStates() { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) let worktree = makeWorktree() let state = manager.state(for: worktree)
manager.setCommandFinishedNotification(enabled: true, threshold: 30) state.handleCommandFinished(exitCode: 0, durationNs: 20_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.isEmpty)
state.handleCommandFinished(exitCode: 0, durationNs: 31_000_000_000, surfaceId: surfaceId)
#expect(state.notifications.count == 1) }
@Test func managerPropagatesSettingsToNewStates() { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) manager.setCommandFinishedNotification(enabled: true, threshold: 60)
let worktree = makeWorktree() let state = manager.state(for: worktree)
state.handleCommandFinished(exitCode: 0, durationNs: 30_000_000_000, surfaceId: surfaceId) #expect(state.notifications.isEmpty)
state.handleCommandFinished(exitCode: 0, durationNs: 61_000_000_000, surfaceId: surfaceId) #expect(state.notifications.count == 1) }
// MARK: - Auto-close on success
@Test func autoCloseFlagIsConsumedOnSuccess() { let state = makeState() state.markSurfaceForAutoClose(surfaceId) #expect(state.isMarkedForAutoClose(surfaceId))
state.handleCommandFinished(exitCode: 0, durationNs: 1_000_000_000, surfaceId: surfaceId)
#expect(!state.isMarkedForAutoClose(surfaceId)) }
@Test func autoCloseFlagIsConsumedOnFailureButDoesNotClose() { let state = makeState() state.markSurfaceForAutoClose(surfaceId)
state.handleCommandFinished(exitCode: 1, durationNs: 30_000_000_000, surfaceId: surfaceId)
#expect(!state.isMarkedForAutoClose(surfaceId)) // Standard failure notification still fires since close was not triggered. #expect(state.notifications.count == 1) #expect(state.notifications.first?.title == "Command failed") }
@Test func unmarkedSurfaceDoesNotConsumeAutoCloseState() { let state = makeState() let otherSurfaceId = UUID() state.markSurfaceForAutoClose(otherSurfaceId)
state.handleCommandFinished(exitCode: 0, durationNs: 15_000_000_000, surfaceId: surfaceId)
#expect(state.isMarkedForAutoClose(otherSurfaceId)) }
// MARK: - Custom command success toast
@Test func customCommandSuccessEmitsNameAndDuration() { let state = makeState() var received: [(String, Int)] = [] state.onCustomCommandSucceeded = { name, durationMs in received.append((name, durationMs)) } state.markSurfaceForCustomCommand(surfaceId, name: "Build")
state.handleCommandFinished(exitCode: 0, durationNs: 1_500_000_000, surfaceId: surfaceId)
#expect(received.count == 1) #expect(received.first?.0 == "Build") #expect(received.first?.1 == 1_500) }
@Test func customCommandFailureSkipsSuccessEvent() { let state = makeState() var received: [(String, Int)] = [] state.onCustomCommandSucceeded = { name, durationMs in received.append((name, durationMs)) } state.markSurfaceForCustomCommand(surfaceId, name: "Build")
state.handleCommandFinished(exitCode: 2, durationNs: 5_000_000_000, surfaceId: surfaceId)
#expect(received.isEmpty) }
@Test func customCommandMarkIsOneShot() { let state = makeState() var received: [(String, Int)] = [] state.onCustomCommandSucceeded = { name, durationMs in received.append((name, durationMs)) } state.markSurfaceForCustomCommand(surfaceId, name: "Build")
state.handleCommandFinished(exitCode: 0, durationNs: 1_000_000_000, surfaceId: surfaceId) state.handleCommandFinished(exitCode: 0, durationNs: 2_000_000_000, surfaceId: surfaceId)
#expect(received.count == 1) }
@Test func customCommandDurationFormatter() { #expect(formatCustomCommandDuration(0) == "0ms") #expect(formatCustomCommandDuration(250) == "250ms") #expect(formatCustomCommandDuration(999) == "999ms") #expect(formatCustomCommandDuration(1_000) == "1.0s") #expect(formatCustomCommandDuration(1_540) == "1.5s") #expect(formatCustomCommandDuration(9_900) == "9.9s") #expect(formatCustomCommandDuration(12_000) == "12s") #expect(formatCustomCommandDuration(75_000) == "1m 15s") }
// MARK: - Helpers
private func makeState(threshold: Int = 10) -> WorktreeTerminalState { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) let worktree = makeWorktree() let state = manager.state(for: worktree) state.setCommandFinishedNotification(enabled: true, threshold: threshold) return state }
private func makeWorktree() -> Worktree { Worktree( id: "/tmp/repo/wt-\(UUID().uuidString)", name: "wt-test", detail: "detail", workingDirectory: URL(fileURLWithPath: "/tmp/repo/wt-test"), repositoryRootURL: URL(fileURLWithPath: "/tmp/repo"), ) }}