mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
- Fix notification preference tests to match default enabled: false - Fix PtyManager initialization in integration tests - Fix path splitting tests for macOS URL behavior - Add hour formatting to duration display (1h 23m 45s format) - Fix non-optional URL nil comparison warning - Fix force unwrapping warning in EventSource.swift - Apply SwiftFormat formatting fixes - Update test expectations to match actual behavior
281 lines
12 KiB
Swift
281 lines
12 KiB
Swift
import Testing
|
|
import UserNotifications
|
|
@testable import VibeTunnel
|
|
|
|
@Suite("NotificationService Tests")
|
|
struct NotificationServiceTests {
|
|
@Test("Notification preferences are loaded correctly from ConfigManager")
|
|
@MainActor
|
|
func loadPreferencesFromConfig() {
|
|
// This test verifies that NotificationPreferences correctly loads values from ConfigManager
|
|
let configManager = ConfigManager.shared
|
|
let preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
|
|
// Verify that preferences match ConfigManager values
|
|
#expect(preferences.sessionStart == configManager.notificationSessionStart)
|
|
#expect(preferences.sessionExit == configManager.notificationSessionExit)
|
|
#expect(preferences.commandCompletion == configManager.notificationCommandCompletion)
|
|
#expect(preferences.commandError == configManager.notificationCommandError)
|
|
#expect(preferences.bell == configManager.notificationBell)
|
|
#expect(preferences.claudeTurn == configManager.notificationClaudeTurn)
|
|
#expect(preferences.soundEnabled == configManager.notificationSoundEnabled)
|
|
#expect(preferences.vibrationEnabled == configManager.notificationVibrationEnabled)
|
|
}
|
|
|
|
@Test("Default notification values match expected defaults")
|
|
@MainActor
|
|
func verifyDefaultValues() {
|
|
// This test documents what the default values SHOULD be
|
|
// In production, these would be set when no config file exists
|
|
|
|
// Expected defaults based on TypeScript config:
|
|
// - Master switch (notificationsEnabled) should be false
|
|
// - Individual preferences should be true (except claudeTurn)
|
|
// - Sound and vibration should be enabled
|
|
|
|
// Note: In actual tests, ConfigManager loads from ~/.vibetunnel/config.json
|
|
// To test true defaults, we would need to:
|
|
// 1. Mock ConfigManager
|
|
// 2. Clear the config file
|
|
// 3. Force ConfigManager to use defaults
|
|
|
|
// For now, we document the expected behavior
|
|
let expectedMasterSwitch = false
|
|
let expectedSessionStart = true
|
|
let expectedSessionExit = true
|
|
let expectedCommandCompletion = true
|
|
let expectedCommandError = true
|
|
let expectedBell = true
|
|
let expectedClaudeTurn = false
|
|
let expectedSound = true
|
|
let expectedVibration = true
|
|
|
|
// These are the values that SHOULD be used when no config exists
|
|
#expect(expectedMasterSwitch == false, "Master switch should be OFF by default")
|
|
#expect(expectedSessionStart == true, "Session start should be enabled by default")
|
|
#expect(expectedSessionExit == true, "Session exit should be enabled by default")
|
|
#expect(expectedCommandCompletion == true, "Command completion should be enabled by default")
|
|
#expect(expectedCommandError == true, "Command error should be enabled by default")
|
|
#expect(expectedBell == true, "Bell should be enabled by default")
|
|
#expect(expectedClaudeTurn == false, "Claude turn should be disabled by default")
|
|
#expect(expectedSound == true, "Sound should be enabled by default")
|
|
#expect(expectedVibration == true, "Vibration should be enabled by default")
|
|
}
|
|
|
|
@Test("Notification preferences can be updated")
|
|
@MainActor
|
|
func testUpdatePreferences() {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Create custom preferences
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.sessionStart = true
|
|
preferences.bell = true
|
|
|
|
// Update preferences
|
|
service.updatePreferences(preferences)
|
|
|
|
// Verify preferences were updated in ConfigManager
|
|
#expect(configManager.notificationSessionStart == true)
|
|
#expect(configManager.notificationBell == true)
|
|
}
|
|
|
|
@Test("Session start notification is sent when enabled")
|
|
@MainActor
|
|
func sessionStartNotification() async throws {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Enable notifications master switch
|
|
configManager.updateNotificationPreferences(enabled: true)
|
|
|
|
// Enable session start notifications
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.sessionStart = true
|
|
service.updatePreferences(preferences)
|
|
|
|
// Send session start notification
|
|
let sessionName = "Test Session"
|
|
await service.sendSessionStartNotification(sessionName: sessionName)
|
|
|
|
// Verify notification would be created (actual delivery depends on system permissions)
|
|
// In a real test environment, we'd mock UNUserNotificationCenter
|
|
#expect(configManager.notificationsEnabled == true)
|
|
#expect(preferences.sessionStart == true)
|
|
}
|
|
|
|
@Test("Session exit notification includes exit code")
|
|
@MainActor
|
|
func sessionExitNotification() async throws {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Enable notifications master switch
|
|
configManager.updateNotificationPreferences(enabled: true)
|
|
|
|
// Enable session exit notifications
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.sessionExit = true
|
|
service.updatePreferences(preferences)
|
|
|
|
// Test successful exit
|
|
await service.sendSessionExitNotification(sessionName: "Test Session", exitCode: 0)
|
|
|
|
// Test error exit
|
|
await service.sendSessionExitNotification(sessionName: "Failed Session", exitCode: 1)
|
|
|
|
#expect(configManager.notificationsEnabled == true)
|
|
#expect(preferences.sessionExit == true)
|
|
}
|
|
|
|
@Test("Command completion notification respects duration threshold")
|
|
@MainActor
|
|
func commandCompletionNotification() async throws {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Enable notifications master switch
|
|
configManager.updateNotificationPreferences(enabled: true)
|
|
|
|
// Enable command completion notifications
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.commandCompletion = true
|
|
service.updatePreferences(preferences)
|
|
|
|
// Test short duration
|
|
await service.sendCommandCompletionNotification(
|
|
command: "ls",
|
|
duration: 1_000 // 1 second
|
|
)
|
|
|
|
// Test long duration
|
|
await service.sendCommandCompletionNotification(
|
|
command: "long-running-command",
|
|
duration: 5_000 // 5 seconds
|
|
)
|
|
|
|
#expect(configManager.notificationsEnabled == true)
|
|
#expect(preferences.commandCompletion == true)
|
|
}
|
|
|
|
@Test("Command error notification is sent for non-zero exit codes")
|
|
@MainActor
|
|
func commandErrorNotification() async throws {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Enable notifications master switch
|
|
configManager.updateNotificationPreferences(enabled: true)
|
|
|
|
// Enable command error notifications
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.commandError = true
|
|
service.updatePreferences(preferences)
|
|
|
|
// Test command with error
|
|
// Note: The service handles command errors through the event stream,
|
|
// not through direct method calls
|
|
await service.sendCommandCompletionNotification(
|
|
command: "failing-command",
|
|
duration: 1_000
|
|
)
|
|
|
|
#expect(configManager.notificationsEnabled == true)
|
|
#expect(preferences.commandError == true)
|
|
}
|
|
|
|
@Test("Bell notification is sent when enabled")
|
|
@MainActor
|
|
func bellNotification() async throws {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Enable notifications master switch
|
|
configManager.updateNotificationPreferences(enabled: true)
|
|
|
|
// Enable bell notifications
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.bell = true
|
|
service.updatePreferences(preferences)
|
|
|
|
// Send bell notification
|
|
// Note: Bell notifications are handled through the event stream
|
|
await service.sendGenericNotification(title: "Terminal Bell", body: "Test Session")
|
|
|
|
#expect(configManager.notificationsEnabled == true)
|
|
#expect(preferences.bell == true)
|
|
}
|
|
|
|
@Test("Notifications are not sent when disabled")
|
|
@MainActor
|
|
func disabledNotifications() async throws {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Test 1: Master switch disabled (default)
|
|
configManager.updateNotificationPreferences(enabled: false)
|
|
|
|
// Even with individual preferences enabled, nothing should fire
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.sessionStart = true
|
|
preferences.sessionExit = true
|
|
preferences.commandCompletion = true
|
|
preferences.commandError = true
|
|
preferences.bell = true
|
|
service.updatePreferences(preferences)
|
|
|
|
// Try to send various notifications
|
|
await service.sendSessionStartNotification(sessionName: "Test")
|
|
await service.sendSessionExitNotification(sessionName: "Test", exitCode: 0)
|
|
await service.sendCommandCompletionNotification(
|
|
command: "test",
|
|
duration: 5_000
|
|
)
|
|
await service.sendGenericNotification(title: "Bell", body: "Test")
|
|
|
|
// Master switch should block all notifications
|
|
#expect(configManager.notificationsEnabled == false)
|
|
|
|
// Test 2: Master switch enabled but individual preferences disabled
|
|
configManager.updateNotificationPreferences(enabled: true)
|
|
|
|
preferences.sessionStart = false
|
|
preferences.sessionExit = false
|
|
preferences.commandCompletion = false
|
|
preferences.commandError = false
|
|
preferences.bell = false
|
|
service.updatePreferences(preferences)
|
|
|
|
// Try to send notifications again
|
|
await service.sendSessionStartNotification(sessionName: "Test")
|
|
await service.sendSessionExitNotification(sessionName: "Test", exitCode: 0)
|
|
|
|
// Individual preferences should block notifications
|
|
#expect(preferences.sessionStart == false)
|
|
#expect(preferences.sessionExit == false)
|
|
#expect(preferences.commandCompletion == false)
|
|
#expect(preferences.bell == false)
|
|
}
|
|
|
|
@Test("Service handles missing session names gracefully")
|
|
@MainActor
|
|
func missingSessionNames() async throws {
|
|
let service = NotificationService.shared
|
|
let configManager = ConfigManager.shared
|
|
|
|
// Enable notifications
|
|
configManager.updateNotificationPreferences(enabled: true)
|
|
|
|
var preferences = NotificationService.NotificationPreferences(fromConfig: configManager)
|
|
preferences.sessionExit = true
|
|
service.updatePreferences(preferences)
|
|
|
|
// Send notification with empty name
|
|
await service.sendSessionExitNotification(sessionName: "", exitCode: 0)
|
|
|
|
// Should handle gracefully
|
|
#expect(configManager.notificationsEnabled == true)
|
|
#expect(preferences.sessionExit == true)
|
|
}
|
|
}
|