From 7c41a8cc53ce32be8a8ccd3846e7ef9f4a18f4c2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Jun 2025 14:48:17 +0200 Subject: [PATCH] mac: more tests --- .../AppleScriptExecutorTests.swift | 103 +++++++++++++ .../DockIconManagerTests.swift | 109 ++++++++++++++ mac/VibeTunnelTests/NgrokServiceTests.swift | 136 ++++++++++++++++++ mac/VibeTunnelTests/StartupManagerTests.swift | 86 +++++++++++ 4 files changed, 434 insertions(+) create mode 100644 mac/VibeTunnelTests/AppleScriptExecutorTests.swift create mode 100644 mac/VibeTunnelTests/DockIconManagerTests.swift create mode 100644 mac/VibeTunnelTests/NgrokServiceTests.swift create mode 100644 mac/VibeTunnelTests/StartupManagerTests.swift diff --git a/mac/VibeTunnelTests/AppleScriptExecutorTests.swift b/mac/VibeTunnelTests/AppleScriptExecutorTests.swift new file mode 100644 index 00000000..3db62033 --- /dev/null +++ b/mac/VibeTunnelTests/AppleScriptExecutorTests.swift @@ -0,0 +1,103 @@ +import Foundation +import Testing +@testable import VibeTunnel + +@Suite("AppleScript Executor Tests", .tags(.integration)) +struct AppleScriptExecutorTests { + + @Test("Execute simple AppleScript") + @MainActor + func executeSimpleScript() throws { + let script = """ + return "Hello from AppleScript" + """ + + let result = try AppleScriptExecutor.shared.executeWithResult(script) + #expect(result == "Hello from AppleScript") + } + + @Test("Execute script with math") + @MainActor + func executeScriptWithMath() throws { + let script = """ + return 2 + 2 + """ + + let result = try AppleScriptExecutor.shared.executeWithResult(script) + #expect(result == "4") + } + + @Test("Handle script error") + @MainActor + func handleScriptError() throws { + let script = """ + error "This is a test error" + """ + + do { + _ = try AppleScriptExecutor.shared.executeWithResult(script) + Issue.record("Expected error to be thrown") + } catch { + #expect(error.localizedDescription.contains("test error")) + } + } + + @Test("Handle invalid syntax") + @MainActor + func handleInvalidSyntax() throws { + let script = """ + this is not valid applescript syntax + """ + + do { + _ = try AppleScriptExecutor.shared.executeWithResult(script) + Issue.record("Expected error to be thrown") + } catch { + // Should throw a syntax error + #expect(error is AppleScriptError) + } + } + + @Test("Execute empty script") + @MainActor + func executeEmptyScript() throws { + let script = "" + + do { + let result = try AppleScriptExecutor.shared.executeWithResult(script) + #expect(result.isEmpty || result == "missing value") + } catch { + // Empty script might throw an error, which is also acceptable + #expect(error is AppleScriptError) + } + } + + @Test("Check Terminal application") + @MainActor + func checkTerminalApplication() throws { + let script = """ + tell application "System Events" + return exists application process "Terminal" + end tell + """ + + let result = try AppleScriptExecutor.shared.executeWithResult(script) + // Result will be "true" or "false" as a string + #expect(result == "true" || result == "false") + } + + @Test("Test async execution") + func testAsyncExecution() async throws { + // Test the async method + let hasPermission = await AppleScriptExecutor.shared.checkPermission() + #expect(hasPermission == true || hasPermission == false) + } + + @Test("Singleton instance") + @MainActor + func singletonInstance() { + let instance1 = AppleScriptExecutor.shared + let instance2 = AppleScriptExecutor.shared + #expect(instance1 === instance2) + } +} \ No newline at end of file diff --git a/mac/VibeTunnelTests/DockIconManagerTests.swift b/mac/VibeTunnelTests/DockIconManagerTests.swift new file mode 100644 index 00000000..43fe58b8 --- /dev/null +++ b/mac/VibeTunnelTests/DockIconManagerTests.swift @@ -0,0 +1,109 @@ +import Foundation +import Testing +import AppKit +@testable import VibeTunnel + +@Suite("Dock Icon Manager Tests") +struct DockIconManagerTests { + + @Test("Singleton instance") + @MainActor + func singletonInstance() { + let instance1 = DockIconManager.shared + let instance2 = DockIconManager.shared + #expect(instance1 === instance2) + } + + @Test("User preference for dock icon") + func userPreferenceForDockIcon() { + // Store current value to restore later + let currentValue = UserDefaults.standard.bool(forKey: "showInDock") + + // Test with dock icon enabled + UserDefaults.standard.set(true, forKey: "showInDock") + #expect(UserDefaults.standard.bool(forKey: "showInDock") == true) + + // Test with dock icon disabled + UserDefaults.standard.set(false, forKey: "showInDock") + #expect(UserDefaults.standard.bool(forKey: "showInDock") == false) + + // Restore original value + UserDefaults.standard.set(currentValue, forKey: "showInDock") + } + + @Test("Update dock visibility based on windows") + @MainActor + func updateDockVisibilityBasedOnWindows() { + let manager = DockIconManager.shared + + // Save original preference + let originalPref = UserDefaults.standard.bool(forKey: "showInDock") + + // Set preference to hide dock + UserDefaults.standard.set(false, forKey: "showInDock") + + // Update visibility - with no windows, dock should be hidden + manager.updateDockVisibility() + + // The policy depends on whether there are windows open + // In test environment, NSApp might be nil + if let app = NSApp { + #expect(app.activationPolicy() == .regular || app.activationPolicy() == .accessory) + } else { + // In test environment without NSApp, just verify no crash + #expect(true) + } + + // Restore original preference + UserDefaults.standard.set(originalPref, forKey: "showInDock") + } + + @Test("Temporarily show dock") + @MainActor + func temporarilyShowDock() { + let manager = DockIconManager.shared + + // Call temporarilyShowDock + manager.temporarilyShowDock() + + // Should always show as regular + if let app = NSApp { + #expect(app.activationPolicy() == .regular) + } else { + // In test environment without NSApp, just verify no crash + #expect(true) + } + } + + @Test("Dock visibility with user preference") + @MainActor + func dockVisibilityWithUserPreference() { + let manager = DockIconManager.shared + let originalPref = UserDefaults.standard.bool(forKey: "showInDock") + + // Test with showInDock = true (user wants dock visible) + UserDefaults.standard.set(true, forKey: "showInDock") + manager.updateDockVisibility() + if let app = NSApp { + #expect(app.activationPolicy() == .regular) + } else { + // In test environment without NSApp, just verify no crash + #expect(true) + } + + // Test with showInDock = false (user wants dock hidden) + UserDefaults.standard.set(false, forKey: "showInDock") + manager.updateDockVisibility() + // Dock visibility depends on whether windows are open + // In test environment, NSApp might be nil + if let app = NSApp { + #expect(app.activationPolicy() == .regular || app.activationPolicy() == .accessory) + } else { + // In test environment without NSApp, just verify no crash + #expect(true) + } + + // Restore + UserDefaults.standard.set(originalPref, forKey: "showInDock") + } +} \ No newline at end of file diff --git a/mac/VibeTunnelTests/NgrokServiceTests.swift b/mac/VibeTunnelTests/NgrokServiceTests.swift new file mode 100644 index 00000000..7e594664 --- /dev/null +++ b/mac/VibeTunnelTests/NgrokServiceTests.swift @@ -0,0 +1,136 @@ +import Foundation +import Testing +@testable import VibeTunnel + +@Suite("Ngrok Service Tests", .tags(.networking)) +struct NgrokServiceTests { + let testAuthToken = "test_auth_token_123" + let testPort = 8888 + + @Test("Singleton instance") + @MainActor + func singletonInstance() { + let instance1 = NgrokService.shared + let instance2 = NgrokService.shared + #expect(instance1 === instance2) + } + + @Test("Initial state") + @MainActor + func initialState() { + let service = NgrokService.shared + #expect(service.isActive == false) + #expect(service.publicUrl == nil) + #expect(service.tunnelStatus == nil) + } + + @Test("Auth token management") + @MainActor + func authTokenManagement() { + let service = NgrokService.shared + + // Save original token + let originalToken = service.authToken + + // Set test token + service.authToken = testAuthToken + #expect(service.authToken == testAuthToken) + #expect(service.hasAuthToken == true) + + // Clear token + service.authToken = nil + #expect(service.authToken == nil) + #expect(service.hasAuthToken == false) + + // Restore original token + service.authToken = originalToken + } + + @Test("Start without auth token fails") + @MainActor + func startWithoutAuthToken() async throws { + let service = NgrokService.shared + + // Save original token + let originalToken = service.authToken + + // Clear token + service.authToken = nil + + do { + _ = try await service.start(port: testPort) + Issue.record("Expected error to be thrown") + } catch let error as NgrokError { + #expect(error == .authTokenMissing) + } catch { + Issue.record("Expected NgrokError.authTokenMissing") + } + + // Restore original token + service.authToken = originalToken + } + + @Test("Stop when not running") + @MainActor + func stopWhenNotRunning() async throws { + let service = NgrokService.shared + + // Ensure not running + if service.isActive { + try await service.stop() + } + + // Stop again should be safe + try await service.stop() + + #expect(service.isActive == false) + #expect(service.publicUrl == nil) + } + + @Test("Is running check") + @MainActor + func isRunningCheck() async { + let service = NgrokService.shared + + let running = await service.isRunning() + #expect(running == service.isActive) + } + + @Test("Get status when inactive") + @MainActor + func getStatusWhenInactive() async { + let service = NgrokService.shared + + // Ensure not running + if service.isActive { + try? await service.stop() + } + + let status = await service.getStatus() + #expect(status == nil) + } + + @Test("NgrokError descriptions") + func ngrokErrorDescriptions() { + let errors: [NgrokError] = [ + .notInstalled, + .authTokenMissing, + .tunnelCreationFailed("test error"), + .invalidConfiguration, + .networkError("connection failed") + ] + + for error in errors { + #expect(error.errorDescription != nil) + #expect(!error.errorDescription!.isEmpty) + } + } + + @Test("NgrokError equality") + func ngrokErrorEquality() { + #expect(NgrokError.notInstalled == NgrokError.notInstalled) + #expect(NgrokError.authTokenMissing == NgrokError.authTokenMissing) + #expect(NgrokError.tunnelCreationFailed("a") == NgrokError.tunnelCreationFailed("a")) + #expect(NgrokError.tunnelCreationFailed("a") != NgrokError.tunnelCreationFailed("b")) + } +} \ No newline at end of file diff --git a/mac/VibeTunnelTests/StartupManagerTests.swift b/mac/VibeTunnelTests/StartupManagerTests.swift new file mode 100644 index 00000000..2b03daf7 --- /dev/null +++ b/mac/VibeTunnelTests/StartupManagerTests.swift @@ -0,0 +1,86 @@ +import Foundation +import Testing +import ServiceManagement +@testable import VibeTunnel + +@Suite("Startup Manager Tests") +struct StartupManagerTests { + + @Test("Create instance") + @MainActor + func createInstance() { + let manager = StartupManager() + // Just verify we can create an instance + #expect(manager.isLaunchAtLoginEnabled == true || manager.isLaunchAtLoginEnabled == false) + } + + @Test("Initial launch at login state") + @MainActor + func initialLaunchAtLoginState() { + let manager = StartupManager() + + // The initial state depends on system configuration + // We just verify it returns a boolean + let state = manager.isLaunchAtLoginEnabled + #expect(state == true || state == false) + } + + @Test("Set launch at login") + @MainActor + func setLaunchAtLogin() { + let manager = StartupManager() + + // Try to enable (may fail in test environment) + manager.setLaunchAtLogin(enabled: true) + + // Try to disable (may fail in test environment) + manager.setLaunchAtLogin(enabled: false) + + // We can't verify the actual state change in tests + // Just ensure the methods don't crash + #expect(true) + } + + @Test("Service management availability") + @available(macOS 13.0, *) + func serviceManagementAvailability() { + // Test that we can at least query the service status + let service = SMAppService.mainApp + + // Status should be queryable + let status = service.status + + // We just verify that we can get a status without crashing + // The actual value depends on the test environment + #expect(status.rawValue >= 0) + } + + @Test("App bundle identifier") + func appBundleIdentifier() { + // In test environment, bundle identifier might be nil + let bundleId = Bundle.main.bundleIdentifier + + if let bundleId = bundleId { + #expect(!bundleId.isEmpty) + // In test environment, might be different than production + #expect(bundleId.contains("VibeTunnel") || bundleId.contains("xctest") || bundleId.contains("swift")) + } else { + // It's OK for bundle ID to be nil in test environment + #expect(bundleId == nil) + } + } + + @Test("Multiple operations") + @MainActor + func multipleOperations() { + let manager = StartupManager() + + // Perform multiple operations + manager.setLaunchAtLogin(enabled: true) + manager.setLaunchAtLogin(enabled: false) + manager.setLaunchAtLogin(enabled: true) + + // Just ensure no crashes + #expect(true) + } +} \ No newline at end of file