mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Fix test failures and resolve all linting warnings
- 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
This commit is contained in:
parent
f87b511ec1
commit
dfe0cfda25
15 changed files with 275 additions and 246 deletions
|
|
@ -227,7 +227,7 @@ final class ConfigManager {
|
||||||
|
|
||||||
// Set notification defaults to match TypeScript defaults
|
// Set notification defaults to match TypeScript defaults
|
||||||
// Master switch is OFF by default, but individual preferences are set to true
|
// Master switch is OFF by default, but individual preferences are set to true
|
||||||
self.notificationsEnabled = false // Changed from true to match web defaults
|
self.notificationsEnabled = false // Changed from true to match web defaults
|
||||||
self.notificationSessionStart = true
|
self.notificationSessionStart = true
|
||||||
self.notificationSessionExit = true
|
self.notificationSessionExit = true
|
||||||
self.notificationCommandCompletion = true
|
self.notificationCommandCompletion = true
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ final class EventSource: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add last event ID if available
|
// Add last event ID if available
|
||||||
if let lastEventId = lastEventId {
|
if let lastEventId {
|
||||||
request.setValue(lastEventId, forHTTPHeaderField: "Last-Event-ID")
|
request.setValue(lastEventId, forHTTPHeaderField: "Last-Event-ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ final class EventSource: NSObject {
|
||||||
|
|
||||||
// Update reconnect time
|
// Update reconnect time
|
||||||
if let retry = eventRetry {
|
if let retry = eventRetry {
|
||||||
reconnectTime = TimeInterval(retry) / 1000.0
|
reconnectTime = TimeInterval(retry) / 1_000.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch event
|
// Dispatch event
|
||||||
|
|
@ -174,7 +174,12 @@ final class EventSource: NSObject {
|
||||||
// MARK: - URLSessionDataDelegate
|
// MARK: - URLSessionDataDelegate
|
||||||
|
|
||||||
extension EventSource: URLSessionDataDelegate {
|
extension EventSource: URLSessionDataDelegate {
|
||||||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
func urlSession(
|
||||||
|
_ session: URLSession,
|
||||||
|
dataTask: URLSessionDataTask,
|
||||||
|
didReceive response: URLResponse,
|
||||||
|
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
||||||
|
) {
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
completionHandler(.cancel)
|
completionHandler(.cancel)
|
||||||
return
|
return
|
||||||
|
|
@ -205,7 +210,7 @@ extension EventSource: URLSessionDataDelegate {
|
||||||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||||
isConnected = false
|
isConnected = false
|
||||||
|
|
||||||
if let error = error {
|
if let error {
|
||||||
logger.error("EventSource error: \(error)")
|
logger.error("EventSource error: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,10 +223,16 @@ extension EventSource: URLSessionDataDelegate {
|
||||||
// MARK: - URLSessionDelegate
|
// MARK: - URLSessionDelegate
|
||||||
|
|
||||||
extension EventSource: URLSessionDelegate {
|
extension EventSource: URLSessionDelegate {
|
||||||
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
func urlSession(
|
||||||
|
_ session: URLSession,
|
||||||
|
didReceive challenge: URLAuthenticationChallenge,
|
||||||
|
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
||||||
|
) {
|
||||||
// Accept the server's certificate for localhost connections
|
// Accept the server's certificate for localhost connections
|
||||||
if challenge.protectionSpace.host == "localhost" {
|
if challenge.protectionSpace.host == "localhost",
|
||||||
let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
|
let serverTrust = challenge.protectionSpace.serverTrust
|
||||||
|
{
|
||||||
|
let credential = URLCredential(trust: serverTrust)
|
||||||
completionHandler(.useCredential, credential)
|
completionHandler(.useCredential, credential)
|
||||||
} else {
|
} else {
|
||||||
completionHandler(.performDefaultHandling, nil)
|
completionHandler(.performDefaultHandling, nil)
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,9 @@ final class NotificationControlHandler {
|
||||||
logger.info("Received notification: \(title) - \(body) (type: \(typeString ?? "unknown"))")
|
logger.info("Received notification: \(title) - \(body) (type: \(typeString ?? "unknown"))")
|
||||||
|
|
||||||
// Map type string to ServerEventType and create ServerEvent
|
// Map type string to ServerEventType and create ServerEvent
|
||||||
if let typeString = typeString,
|
if let typeString,
|
||||||
let eventType = ServerEventType(rawValue: typeString) {
|
let eventType = ServerEventType(rawValue: typeString)
|
||||||
|
{
|
||||||
let serverEvent = ServerEvent(
|
let serverEvent = ServerEvent(
|
||||||
type: eventType,
|
type: eventType,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ final class NotificationService: NSObject {
|
||||||
|
|
||||||
// Format duration if provided
|
// Format duration if provided
|
||||||
if duration > 0 {
|
if duration > 0 {
|
||||||
let seconds = duration / 1000
|
let seconds = duration / 1_000
|
||||||
if seconds < 60 {
|
if seconds < 60 {
|
||||||
content.subtitle = "\(seconds)s"
|
content.subtitle = "\(seconds)s"
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -438,7 +438,7 @@ final class NotificationService: NSObject {
|
||||||
|
|
||||||
eventSource?.onError = { [weak self] error in
|
eventSource?.onError = { [weak self] error in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
if let error = error {
|
if let error {
|
||||||
self?.logger.error("❌ EventSource error: \(error)")
|
self?.logger.error("❌ EventSource error: \(error)")
|
||||||
}
|
}
|
||||||
self?.isConnected = false
|
self?.isConnected = false
|
||||||
|
|
@ -526,7 +526,7 @@ final class NotificationService: NSObject {
|
||||||
}
|
}
|
||||||
case "connected":
|
case "connected":
|
||||||
logger.info("🔗 Received connected event from server")
|
logger.info("🔗 Received connected event from server")
|
||||||
// No notification for connected events
|
// No notification for connected events
|
||||||
default:
|
default:
|
||||||
logger.warning("Unknown event type: \(type)")
|
logger.warning("Unknown event type: \(type)")
|
||||||
}
|
}
|
||||||
|
|
@ -614,7 +614,7 @@ final class NotificationService: NSObject {
|
||||||
|
|
||||||
// Format duration if provided
|
// Format duration if provided
|
||||||
if duration > 0 {
|
if duration > 0 {
|
||||||
let seconds = duration / 1000
|
let seconds = duration / 1_000
|
||||||
if seconds < 60 {
|
if seconds < 60 {
|
||||||
content.subtitle = "\(seconds)s"
|
content.subtitle = "\(seconds)s"
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -698,7 +698,7 @@ final class NotificationService: NSObject {
|
||||||
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
||||||
|
|
||||||
UNUserNotificationCenter.current().add(request) { [weak self] error in
|
UNUserNotificationCenter.current().add(request) { [weak self] error in
|
||||||
if let error = error {
|
if let error {
|
||||||
self?.logger.error("Failed to deliver notification: \(error)")
|
self?.logger.error("Failed to deliver notification: \(error)")
|
||||||
} else {
|
} else {
|
||||||
self?.logger.debug("Notification delivered: \(identifier)")
|
self?.logger.debug("Notification delivered: \(identifier)")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
// Server event model for notification handling
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Types of server events that can be received from the VibeTunnel server.
|
/// Types of server events that can be received from the VibeTunnel server.
|
||||||
|
|
@ -54,19 +51,19 @@ enum ServerEventType: String, Codable, CaseIterable {
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .sessionStart:
|
case .sessionStart:
|
||||||
return "Session Started"
|
"Session Started"
|
||||||
case .sessionExit:
|
case .sessionExit:
|
||||||
return "Session Ended"
|
"Session Ended"
|
||||||
case .commandFinished:
|
case .commandFinished:
|
||||||
return "Command Completed"
|
"Command Completed"
|
||||||
case .commandError:
|
case .commandError:
|
||||||
return "Command Error"
|
"Command Error"
|
||||||
case .bell:
|
case .bell:
|
||||||
return "Terminal Bell"
|
"Terminal Bell"
|
||||||
case .claudeTurn:
|
case .claudeTurn:
|
||||||
return "Your Turn"
|
"Your Turn"
|
||||||
case .connected:
|
case .connected:
|
||||||
return "Connected"
|
"Connected"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,9 +77,9 @@ enum ServerEventType: String, Codable, CaseIterable {
|
||||||
var shouldNotify: Bool {
|
var shouldNotify: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .sessionStart, .sessionExit, .claudeTurn:
|
case .sessionStart, .sessionExit, .claudeTurn:
|
||||||
return true
|
true
|
||||||
case .commandFinished, .commandError, .bell, .connected:
|
case .commandFinished, .commandError, .bell, .connected:
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -207,8 +204,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
||||||
/// - sessionName: Optional human-readable name for the session.
|
/// - sessionName: Optional human-readable name for the session.
|
||||||
/// - command: Optional command that started the session.
|
/// - command: Optional command that started the session.
|
||||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/sessionStart``.
|
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/sessionStart``.
|
||||||
static func sessionStart(sessionId: String, sessionName: String? = nil, command: String? = nil) -> ServerEvent {
|
static func sessionStart(sessionId: String, sessionName: String? = nil, command: String? = nil) -> Self {
|
||||||
ServerEvent(
|
Self(
|
||||||
type: .sessionStart,
|
type: .sessionStart,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
sessionName: sessionName,
|
sessionName: sessionName,
|
||||||
|
|
@ -225,8 +222,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
||||||
/// - sessionName: Optional human-readable name for the session.
|
/// - sessionName: Optional human-readable name for the session.
|
||||||
/// - exitCode: Optional process exit code.
|
/// - exitCode: Optional process exit code.
|
||||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/sessionExit``.
|
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/sessionExit``.
|
||||||
static func sessionExit(sessionId: String, sessionName: String? = nil, exitCode: Int? = nil) -> ServerEvent {
|
static func sessionExit(sessionId: String, sessionName: String? = nil, exitCode: Int? = nil) -> Self {
|
||||||
ServerEvent(
|
Self(
|
||||||
type: .sessionExit,
|
type: .sessionExit,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
sessionName: sessionName,
|
sessionName: sessionName,
|
||||||
|
|
@ -244,8 +241,15 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
||||||
/// - duration: Execution time in milliseconds.
|
/// - duration: Execution time in milliseconds.
|
||||||
/// - exitCode: Optional process exit code.
|
/// - exitCode: Optional process exit code.
|
||||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/commandFinished``.
|
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/commandFinished``.
|
||||||
static func commandFinished(sessionId: String, command: String, duration: Int, exitCode: Int? = nil) -> ServerEvent {
|
static func commandFinished(
|
||||||
ServerEvent(
|
sessionId: String,
|
||||||
|
command: String,
|
||||||
|
duration: Int,
|
||||||
|
exitCode: Int? = nil
|
||||||
|
)
|
||||||
|
-> Self
|
||||||
|
{
|
||||||
|
Self(
|
||||||
type: .commandFinished,
|
type: .commandFinished,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
command: command,
|
command: command,
|
||||||
|
|
@ -264,8 +268,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
||||||
/// - exitCode: The process exit code.
|
/// - exitCode: The process exit code.
|
||||||
/// - duration: Optional execution time in milliseconds.
|
/// - duration: Optional execution time in milliseconds.
|
||||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/commandError``.
|
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/commandError``.
|
||||||
static func commandError(sessionId: String, command: String, exitCode: Int, duration: Int? = nil) -> ServerEvent {
|
static func commandError(sessionId: String, command: String, exitCode: Int, duration: Int? = nil) -> Self {
|
||||||
ServerEvent(
|
Self(
|
||||||
type: .commandError,
|
type: .commandError,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
command: command,
|
command: command,
|
||||||
|
|
@ -283,8 +287,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
||||||
/// - sessionId: The unique identifier for the session.
|
/// - sessionId: The unique identifier for the session.
|
||||||
/// - sessionName: Optional human-readable name for the session.
|
/// - sessionName: Optional human-readable name for the session.
|
||||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/claudeTurn``.
|
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/claudeTurn``.
|
||||||
static func claudeTurn(sessionId: String, sessionName: String? = nil) -> ServerEvent {
|
static func claudeTurn(sessionId: String, sessionName: String? = nil) -> Self {
|
||||||
ServerEvent(
|
Self(
|
||||||
type: .claudeTurn,
|
type: .claudeTurn,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
sessionName: sessionName,
|
sessionName: sessionName,
|
||||||
|
|
@ -298,8 +302,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
||||||
///
|
///
|
||||||
/// - Parameter sessionId: The unique identifier for the session.
|
/// - Parameter sessionId: The unique identifier for the session.
|
||||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/bell``.
|
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/bell``.
|
||||||
static func bell(sessionId: String) -> ServerEvent {
|
static func bell(sessionId: String) -> Self {
|
||||||
ServerEvent(
|
Self(
|
||||||
type: .bell,
|
type: .bell,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
message: "Terminal bell"
|
message: "Terminal bell"
|
||||||
|
|
@ -336,20 +340,20 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
||||||
///
|
///
|
||||||
/// - Returns: A formatted duration string, or `nil` if no duration is set.
|
/// - Returns: A formatted duration string, or `nil` if no duration is set.
|
||||||
var formattedDuration: String? {
|
var formattedDuration: String? {
|
||||||
guard let duration = duration else { return nil }
|
guard let duration else { return nil }
|
||||||
|
|
||||||
if duration < 1000 {
|
if duration < 1_000 {
|
||||||
return "\(duration)ms"
|
return "\(duration)ms"
|
||||||
} else if duration < 60000 {
|
} else if duration < 60_000 {
|
||||||
return String(format: "%.1fs", Double(duration) / 1000.0)
|
return String(format: "%.1fs", Double(duration) / 1_000.0)
|
||||||
} else if duration < 3600000 {
|
} else if duration < 3_600_000 {
|
||||||
let minutes = duration / 60000
|
let minutes = duration / 60_000
|
||||||
let seconds = (duration % 60000) / 1000
|
let seconds = (duration % 60_000) / 1_000
|
||||||
return "\(minutes)m \(seconds)s"
|
return "\(minutes)m \(seconds)s"
|
||||||
} else {
|
} else {
|
||||||
let hours = duration / 3600000
|
let hours = duration / 3_600_000
|
||||||
let minutes = (duration % 3600000) / 60000
|
let minutes = (duration % 3_600_000) / 60_000
|
||||||
let seconds = (duration % 60000) / 1000
|
let seconds = (duration % 60_000) / 1_000
|
||||||
return "\(hours)h \(minutes)m \(seconds)s"
|
return "\(hours)h \(minutes)m \(seconds)s"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,12 @@ final class SessionMonitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pre-cache Git repositories for sessions, deduplicating by repository root
|
/// Pre-cache Git repositories for sessions, deduplicating by repository root
|
||||||
private func preCacheGitRepositories(for sessions: [ServerSessionInfo], using gitMonitor: GitRepositoryMonitor) async {
|
private func preCacheGitRepositories(
|
||||||
|
for sessions: [ServerSessionInfo],
|
||||||
|
using gitMonitor: GitRepositoryMonitor
|
||||||
|
)
|
||||||
|
async
|
||||||
|
{
|
||||||
// Track unique directories we need to check
|
// Track unique directories we need to check
|
||||||
var uniqueDirectoriesToCheck = Set<String>()
|
var uniqueDirectoriesToCheck = Set<String>()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,10 @@ struct PathSplittingTests {
|
||||||
|
|
||||||
// List contents of parent directory
|
// List contents of parent directory
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let contents = try #require(try? fileManager.contentsOfDirectory(at: parentURL, includingPropertiesForKeys: nil))
|
let contents = try #require(try? fileManager.contentsOfDirectory(
|
||||||
|
at: parentURL,
|
||||||
|
includingPropertiesForKeys: nil
|
||||||
|
))
|
||||||
|
|
||||||
let matching = contents.filter { $0.lastPathComponent.hasPrefix(prefix) }
|
let matching = contents.filter { $0.lastPathComponent.hasPrefix(prefix) }
|
||||||
// We can't assert specific matches as they depend on the user's home directory
|
// We can't assert specific matches as they depend on the user's home directory
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import Testing
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Testing
|
||||||
@testable import VibeTunnel
|
@testable import VibeTunnel
|
||||||
|
|
||||||
@Suite("ServerEvent")
|
@Suite("ServerEvent")
|
||||||
struct ServerEventTests {
|
struct ServerEventTests {
|
||||||
|
|
||||||
// MARK: - Codable Tests
|
// MARK: - Codable Tests
|
||||||
|
|
||||||
// These are valuable - testing JSON encoding/decoding with optional fields
|
// These are valuable - testing JSON encoding/decoding with optional fields
|
||||||
|
|
||||||
@Test("Codable round-trip with multiple optional fields")
|
@Test("Codable round-trip with multiple optional fields")
|
||||||
|
|
@ -39,7 +39,7 @@ struct ServerEventTests {
|
||||||
sessionName: "Long Running Command",
|
sessionName: "Long Running Command",
|
||||||
command: "npm install",
|
command: "npm install",
|
||||||
exitCode: 0,
|
exitCode: 0,
|
||||||
duration: 15000,
|
duration: 15_000,
|
||||||
processInfo: "Node.js process",
|
processInfo: "Node.js process",
|
||||||
message: "Command completed successfully"
|
message: "Command completed successfully"
|
||||||
)
|
)
|
||||||
|
|
@ -52,7 +52,7 @@ struct ServerEventTests {
|
||||||
#expect(decoded.sessionName == "Long Running Command")
|
#expect(decoded.sessionName == "Long Running Command")
|
||||||
#expect(decoded.command == "npm install")
|
#expect(decoded.command == "npm install")
|
||||||
#expect(decoded.exitCode == 0)
|
#expect(decoded.exitCode == 0)
|
||||||
#expect(decoded.duration == 15000)
|
#expect(decoded.duration == 15_000)
|
||||||
#expect(decoded.processInfo == "Node.js process")
|
#expect(decoded.processInfo == "Node.js process")
|
||||||
#expect(decoded.message == "Command completed successfully")
|
#expect(decoded.message == "Command completed successfully")
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +76,7 @@ struct ServerEventTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Event Type Logic Tests
|
// MARK: - Event Type Logic Tests
|
||||||
|
|
||||||
// Testing actual business logic, not Swift's enum implementation
|
// Testing actual business logic, not Swift's enum implementation
|
||||||
|
|
||||||
@Test("Event type descriptions are user-friendly")
|
@Test("Event type descriptions are user-friendly")
|
||||||
|
|
@ -104,6 +105,7 @@ struct ServerEventTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Edge Cases
|
// MARK: - Edge Cases
|
||||||
|
|
||||||
// These test important edge cases for data integrity
|
// These test important edge cases for data integrity
|
||||||
|
|
||||||
@Test("Handles empty strings correctly")
|
@Test("Handles empty strings correctly")
|
||||||
|
|
@ -146,6 +148,7 @@ struct ServerEventTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Convenience Initializers
|
// MARK: - Convenience Initializers
|
||||||
|
|
||||||
// These test that convenience initializers create properly configured events
|
// These test that convenience initializers create properly configured events
|
||||||
|
|
||||||
@Test("sessionStart convenience initializer sets correct fields")
|
@Test("sessionStart convenience initializer sets correct fields")
|
||||||
|
|
@ -183,14 +186,14 @@ struct ServerEventTests {
|
||||||
let event = ServerEvent.commandFinished(
|
let event = ServerEvent.commandFinished(
|
||||||
sessionId: "test-789",
|
sessionId: "test-789",
|
||||||
command: "npm install",
|
command: "npm install",
|
||||||
duration: 15000,
|
duration: 15_000,
|
||||||
exitCode: 0
|
exitCode: 0
|
||||||
)
|
)
|
||||||
|
|
||||||
#expect(event.type == .commandFinished)
|
#expect(event.type == .commandFinished)
|
||||||
#expect(event.sessionId == "test-789")
|
#expect(event.sessionId == "test-789")
|
||||||
#expect(event.command == "npm install")
|
#expect(event.command == "npm install")
|
||||||
#expect(event.duration == 15000)
|
#expect(event.duration == 15_000)
|
||||||
#expect(event.exitCode == 0)
|
#expect(event.exitCode == 0)
|
||||||
#expect(!event.shouldNotify)
|
#expect(!event.shouldNotify)
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +223,7 @@ struct ServerEventTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Computed Properties with Logic
|
// MARK: - Computed Properties with Logic
|
||||||
|
|
||||||
// These test actual business logic in computed properties
|
// These test actual business logic in computed properties
|
||||||
|
|
||||||
@Test("displayName fallback logic works correctly")
|
@Test("displayName fallback logic works correctly")
|
||||||
|
|
@ -243,9 +247,9 @@ struct ServerEventTests {
|
||||||
|
|
||||||
@Test("formattedDuration handles different time ranges", arguments: [
|
@Test("formattedDuration handles different time ranges", arguments: [
|
||||||
(500, "500ms"),
|
(500, "500ms"),
|
||||||
(2500, "2.5s"),
|
(2_500, "2.5s"),
|
||||||
(125000, "2m 5s"),
|
(125_000, "2m 5s"),
|
||||||
(3661000, "1h 1m 1s")
|
(3_661_000, "1h 1m 1s")
|
||||||
])
|
])
|
||||||
func formattedDurationLogic(duration: Int, expected: String) {
|
func formattedDurationLogic(duration: Int, expected: String) {
|
||||||
let event = ServerEvent(type: .commandFinished, duration: duration)
|
let event = ServerEvent(type: .commandFinished, duration: duration)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import Testing
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Testing
|
||||||
@testable import VibeTunnel
|
@testable import VibeTunnel
|
||||||
|
|
||||||
@Suite("TunnelSession & Related Types")
|
@Suite("TunnelSession & Related Types")
|
||||||
struct TunnelSessionTests {
|
struct TunnelSessionTests {
|
||||||
|
|
||||||
// MARK: - TunnelSession Logic Tests
|
// MARK: - TunnelSession Logic Tests
|
||||||
|
|
||||||
// Only testing actual logic, not property synthesis
|
// Only testing actual logic, not property synthesis
|
||||||
|
|
||||||
@Test("updateActivity updates lastActivity timestamp")
|
@Test("updateActivity updates lastActivity timestamp")
|
||||||
|
|
@ -23,7 +23,7 @@ struct TunnelSessionTests {
|
||||||
|
|
||||||
@Test("TunnelSession is Codable with all fields")
|
@Test("TunnelSession is Codable with all fields")
|
||||||
func tunnelSessionCodable() throws {
|
func tunnelSessionCodable() throws {
|
||||||
var originalSession = TunnelSession(processID: 67890)
|
var originalSession = TunnelSession(processID: 67_890)
|
||||||
originalSession.updateActivity()
|
originalSession.updateActivity()
|
||||||
|
|
||||||
let data = try JSONEncoder().encode(originalSession)
|
let data = try JSONEncoder().encode(originalSession)
|
||||||
|
|
@ -38,6 +38,7 @@ struct TunnelSessionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CreateSessionRequest Tests
|
// MARK: - CreateSessionRequest Tests
|
||||||
|
|
||||||
// Testing optional field handling in Codable
|
// Testing optional field handling in Codable
|
||||||
|
|
||||||
@Test("CreateSessionRequest encodes/decodes with all optional fields")
|
@Test("CreateSessionRequest encodes/decodes with all optional fields")
|
||||||
|
|
@ -91,6 +92,7 @@ struct TunnelSessionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CreateSessionResponse Tests
|
// MARK: - CreateSessionResponse Tests
|
||||||
|
|
||||||
// Simple type but worth testing Codable with Date precision
|
// Simple type but worth testing Codable with Date precision
|
||||||
|
|
||||||
@Test("CreateSessionResponse handles date encoding correctly")
|
@Test("CreateSessionResponse handles date encoding correctly")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue