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
276 lines
9.9 KiB
Swift
276 lines
9.9 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import VibeTunnel
|
|
|
|
@Suite("ServerEvent")
|
|
struct ServerEventTests {
|
|
// MARK: - Codable Tests
|
|
|
|
// These are valuable - testing JSON encoding/decoding with optional fields
|
|
|
|
@Test("Codable round-trip with multiple optional fields")
|
|
func codableRoundTrip() throws {
|
|
let originalEvent = ServerEvent(
|
|
type: .sessionStart,
|
|
sessionId: "test-session-123",
|
|
sessionName: "Test Session",
|
|
command: "ls -la",
|
|
exitCode: nil,
|
|
duration: nil,
|
|
processInfo: nil,
|
|
message: "Session started successfully"
|
|
)
|
|
|
|
let data = try JSONEncoder().encode(originalEvent)
|
|
let decodedEvent = try JSONDecoder().decode(ServerEvent.self, from: data)
|
|
|
|
#expect(originalEvent.type == decodedEvent.type)
|
|
#expect(originalEvent.sessionId == decodedEvent.sessionId)
|
|
#expect(originalEvent.sessionName == decodedEvent.sessionName)
|
|
#expect(originalEvent.command == decodedEvent.command)
|
|
#expect(originalEvent.message == decodedEvent.message)
|
|
}
|
|
|
|
@Test("Codable with all fields populated")
|
|
func codableWithAllFields() throws {
|
|
let event = ServerEvent(
|
|
type: .commandFinished,
|
|
sessionId: "session-456",
|
|
sessionName: "Long Running Command",
|
|
command: "npm install",
|
|
exitCode: 0,
|
|
duration: 15_000,
|
|
processInfo: "Node.js process",
|
|
message: "Command completed successfully"
|
|
)
|
|
|
|
let data = try JSONEncoder().encode(event)
|
|
let decoded = try JSONDecoder().decode(ServerEvent.self, from: data)
|
|
|
|
#expect(decoded.type == .commandFinished)
|
|
#expect(decoded.sessionId == "session-456")
|
|
#expect(decoded.sessionName == "Long Running Command")
|
|
#expect(decoded.command == "npm install")
|
|
#expect(decoded.exitCode == 0)
|
|
#expect(decoded.duration == 15_000)
|
|
#expect(decoded.processInfo == "Node.js process")
|
|
#expect(decoded.message == "Command completed successfully")
|
|
}
|
|
|
|
@Test("Codable with minimal fields preserves nils")
|
|
func codableWithMinimalFields() throws {
|
|
let event = ServerEvent(type: .bell)
|
|
|
|
let data = try JSONEncoder().encode(event)
|
|
let decoded = try JSONDecoder().decode(ServerEvent.self, from: data)
|
|
|
|
#expect(decoded.type == .bell)
|
|
#expect(decoded.sessionId == nil)
|
|
#expect(decoded.sessionName == nil)
|
|
#expect(decoded.command == nil)
|
|
#expect(decoded.exitCode == nil)
|
|
#expect(decoded.duration == nil)
|
|
#expect(decoded.processInfo == nil)
|
|
#expect(decoded.message == nil)
|
|
#expect(decoded.timestamp != nil)
|
|
}
|
|
|
|
// MARK: - Event Type Logic Tests
|
|
|
|
// Testing actual business logic, not Swift's enum implementation
|
|
|
|
@Test("Event type descriptions are user-friendly")
|
|
func eventTypeDescriptions() {
|
|
#expect(ServerEventType.sessionStart.description == "Session Started")
|
|
#expect(ServerEventType.sessionExit.description == "Session Ended")
|
|
#expect(ServerEventType.commandFinished.description == "Command Completed")
|
|
#expect(ServerEventType.commandError.description == "Command Error")
|
|
#expect(ServerEventType.bell.description == "Terminal Bell")
|
|
#expect(ServerEventType.claudeTurn.description == "Your Turn")
|
|
#expect(ServerEventType.connected.description == "Connected")
|
|
}
|
|
|
|
@Test("shouldNotify returns correct values for notification logic")
|
|
func eventTypeShouldNotify() {
|
|
// These events should trigger notifications
|
|
#expect(ServerEventType.sessionStart.shouldNotify)
|
|
#expect(ServerEventType.sessionExit.shouldNotify)
|
|
#expect(ServerEventType.claudeTurn.shouldNotify)
|
|
|
|
// These events should not trigger notifications
|
|
#expect(!ServerEventType.commandFinished.shouldNotify)
|
|
#expect(!ServerEventType.commandError.shouldNotify)
|
|
#expect(!ServerEventType.bell.shouldNotify)
|
|
#expect(!ServerEventType.connected.shouldNotify)
|
|
}
|
|
|
|
// MARK: - Edge Cases
|
|
|
|
// These test important edge cases for data integrity
|
|
|
|
@Test("Handles empty strings correctly")
|
|
func handlesEmptyStrings() throws {
|
|
let event = ServerEvent(
|
|
type: .sessionStart,
|
|
sessionId: "",
|
|
sessionName: "",
|
|
command: "",
|
|
message: ""
|
|
)
|
|
|
|
let data = try JSONEncoder().encode(event)
|
|
let decoded = try JSONDecoder().decode(ServerEvent.self, from: data)
|
|
|
|
// Empty strings should be preserved, not converted to nil
|
|
#expect(decoded.sessionId == "")
|
|
#expect(decoded.sessionName == "")
|
|
#expect(decoded.command == "")
|
|
#expect(decoded.message == "")
|
|
}
|
|
|
|
@Test("Handles special characters in JSON encoding")
|
|
func handlesSpecialCharacters() throws {
|
|
let event = ServerEvent(
|
|
type: .commandError,
|
|
sessionId: "session-123",
|
|
sessionName: "Test Session with \"quotes\" and 'apostrophes'",
|
|
command: "echo 'Hello, World!' && echo \"Test\"",
|
|
exitCode: -1,
|
|
message: "Error: Command failed with special chars: <>&\"'"
|
|
)
|
|
|
|
let data = try JSONEncoder().encode(event)
|
|
let decoded = try JSONDecoder().decode(ServerEvent.self, from: data)
|
|
|
|
#expect(decoded.sessionName == "Test Session with \"quotes\" and 'apostrophes'")
|
|
#expect(decoded.command == "echo 'Hello, World!' && echo \"Test\"")
|
|
#expect(decoded.message == "Error: Command failed with special chars: <>&\"'")
|
|
}
|
|
|
|
// MARK: - Convenience Initializers
|
|
|
|
// These test that convenience initializers create properly configured events
|
|
|
|
@Test("sessionStart convenience initializer sets correct fields")
|
|
func sessionStartInitializer() {
|
|
let event = ServerEvent.sessionStart(
|
|
sessionId: "test-123",
|
|
sessionName: "Test Session",
|
|
command: "ls -la"
|
|
)
|
|
|
|
#expect(event.type == .sessionStart)
|
|
#expect(event.sessionId == "test-123")
|
|
#expect(event.sessionName == "Test Session")
|
|
#expect(event.command == "ls -la")
|
|
#expect(event.shouldNotify)
|
|
}
|
|
|
|
@Test("sessionExit convenience initializer sets correct fields")
|
|
func sessionExitInitializer() {
|
|
let event = ServerEvent.sessionExit(
|
|
sessionId: "test-456",
|
|
sessionName: "Test Session",
|
|
exitCode: 0
|
|
)
|
|
|
|
#expect(event.type == .sessionExit)
|
|
#expect(event.sessionId == "test-456")
|
|
#expect(event.sessionName == "Test Session")
|
|
#expect(event.exitCode == 0)
|
|
#expect(event.shouldNotify)
|
|
}
|
|
|
|
@Test("commandFinished convenience initializer sets correct fields")
|
|
func commandFinishedInitializer() {
|
|
let event = ServerEvent.commandFinished(
|
|
sessionId: "test-789",
|
|
command: "npm install",
|
|
duration: 15_000,
|
|
exitCode: 0
|
|
)
|
|
|
|
#expect(event.type == .commandFinished)
|
|
#expect(event.sessionId == "test-789")
|
|
#expect(event.command == "npm install")
|
|
#expect(event.duration == 15_000)
|
|
#expect(event.exitCode == 0)
|
|
#expect(!event.shouldNotify)
|
|
}
|
|
|
|
@Test("claudeTurn convenience initializer includes default message")
|
|
func claudeTurnInitializer() {
|
|
let event = ServerEvent.claudeTurn(
|
|
sessionId: "claude-session",
|
|
sessionName: "Claude Chat"
|
|
)
|
|
|
|
#expect(event.type == .claudeTurn)
|
|
#expect(event.sessionId == "claude-session")
|
|
#expect(event.sessionName == "Claude Chat")
|
|
#expect(event.message == "Claude has finished responding")
|
|
#expect(event.shouldNotify)
|
|
}
|
|
|
|
@Test("bell convenience initializer includes default message")
|
|
func bellInitializer() {
|
|
let event = ServerEvent.bell(sessionId: "bell-session")
|
|
|
|
#expect(event.type == .bell)
|
|
#expect(event.sessionId == "bell-session")
|
|
#expect(event.message == "Terminal bell")
|
|
#expect(!event.shouldNotify)
|
|
}
|
|
|
|
// MARK: - Computed Properties with Logic
|
|
|
|
// These test actual business logic in computed properties
|
|
|
|
@Test("displayName fallback logic works correctly")
|
|
func displayNameLogic() {
|
|
// Priority 1: Session name
|
|
let event1 = ServerEvent(type: .sessionStart, sessionName: "My Session")
|
|
#expect(event1.displayName == "My Session")
|
|
|
|
// Priority 2: Command (when no session name)
|
|
let event2 = ServerEvent(type: .sessionStart, command: "ls -la")
|
|
#expect(event2.displayName == "ls -la")
|
|
|
|
// Priority 3: Session ID (when no name or command)
|
|
let event3 = ServerEvent(type: .sessionStart, sessionId: "session-123")
|
|
#expect(event3.displayName == "session-123")
|
|
|
|
// Fallback: Unknown Session
|
|
let event4 = ServerEvent(type: .sessionStart)
|
|
#expect(event4.displayName == "Unknown Session")
|
|
}
|
|
|
|
@Test("formattedDuration handles different time ranges", arguments: [
|
|
(500, "500ms"),
|
|
(2_500, "2.5s"),
|
|
(125_000, "2m 5s"),
|
|
(3_661_000, "1h 1m 1s")
|
|
])
|
|
func formattedDurationLogic(duration: Int, expected: String) {
|
|
let event = ServerEvent(type: .commandFinished, duration: duration)
|
|
#expect(event.formattedDuration == expected)
|
|
}
|
|
|
|
@Test("formattedDuration returns nil when duration is nil")
|
|
func formattedDurationNil() {
|
|
let event = ServerEvent(type: .sessionStart)
|
|
#expect(event.formattedDuration == nil)
|
|
}
|
|
|
|
@Test("formattedTimestamp uses correct format")
|
|
func formattedTimestampFormat() {
|
|
let timestamp = Date()
|
|
let event = ServerEvent(type: .sessionStart, timestamp: timestamp)
|
|
|
|
let formatter = DateFormatter()
|
|
formatter.timeStyle = .medium
|
|
let expected = formatter.string(from: timestamp)
|
|
|
|
#expect(event.formattedTimestamp == expected)
|
|
}
|
|
}
|