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
|
|
@ -64,7 +64,7 @@ final class EventSource: NSObject {
|
|||
}
|
||||
|
||||
// Add last event ID if available
|
||||
if let lastEventId = lastEventId {
|
||||
if let lastEventId {
|
||||
request.setValue(lastEventId, forHTTPHeaderField: "Last-Event-ID")
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ final class EventSource: NSObject {
|
|||
|
||||
// Update reconnect time
|
||||
if let retry = eventRetry {
|
||||
reconnectTime = TimeInterval(retry) / 1000.0
|
||||
reconnectTime = TimeInterval(retry) / 1_000.0
|
||||
}
|
||||
|
||||
// Dispatch event
|
||||
|
|
@ -174,7 +174,12 @@ final class EventSource: NSObject {
|
|||
// MARK: - 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 {
|
||||
completionHandler(.cancel)
|
||||
return
|
||||
|
|
@ -205,7 +210,7 @@ extension EventSource: URLSessionDataDelegate {
|
|||
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
isConnected = false
|
||||
|
||||
if let error = error {
|
||||
if let error {
|
||||
logger.error("EventSource error: \(error)")
|
||||
}
|
||||
|
||||
|
|
@ -218,10 +223,16 @@ extension EventSource: URLSessionDataDelegate {
|
|||
// MARK: - 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
|
||||
if challenge.protectionSpace.host == "localhost" {
|
||||
let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
|
||||
if challenge.protectionSpace.host == "localhost",
|
||||
let serverTrust = challenge.protectionSpace.serverTrust
|
||||
{
|
||||
let credential = URLCredential(trust: serverTrust)
|
||||
completionHandler(.useCredential, credential)
|
||||
} else {
|
||||
completionHandler(.performDefaultHandling, nil)
|
||||
|
|
|
|||
|
|
@ -69,9 +69,9 @@ final class NotificationControlHandler {
|
|||
logger.info("Received notification: \(title) - \(body) (type: \(typeString ?? "unknown"))")
|
||||
|
||||
// Map type string to ServerEventType and create ServerEvent
|
||||
if let typeString = typeString,
|
||||
let eventType = ServerEventType(rawValue: typeString) {
|
||||
|
||||
if let typeString,
|
||||
let eventType = ServerEventType(rawValue: typeString)
|
||||
{
|
||||
let serverEvent = ServerEvent(
|
||||
type: eventType,
|
||||
sessionId: sessionId,
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ final class NotificationService: NSObject {
|
|||
|
||||
// Format duration if provided
|
||||
if duration > 0 {
|
||||
let seconds = duration / 1000
|
||||
let seconds = duration / 1_000
|
||||
if seconds < 60 {
|
||||
content.subtitle = "\(seconds)s"
|
||||
} else {
|
||||
|
|
@ -438,7 +438,7 @@ final class NotificationService: NSObject {
|
|||
|
||||
eventSource?.onError = { [weak self] error in
|
||||
Task { @MainActor in
|
||||
if let error = error {
|
||||
if let error {
|
||||
self?.logger.error("❌ EventSource error: \(error)")
|
||||
}
|
||||
self?.isConnected = false
|
||||
|
|
@ -614,7 +614,7 @@ final class NotificationService: NSObject {
|
|||
|
||||
// Format duration if provided
|
||||
if duration > 0 {
|
||||
let seconds = duration / 1000
|
||||
let seconds = duration / 1_000
|
||||
if seconds < 60 {
|
||||
content.subtitle = "\(seconds)s"
|
||||
} else {
|
||||
|
|
@ -698,7 +698,7 @@ final class NotificationService: NSObject {
|
|||
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
||||
|
||||
UNUserNotificationCenter.current().add(request) { [weak self] error in
|
||||
if let error = error {
|
||||
if let error {
|
||||
self?.logger.error("Failed to deliver notification: \(error)")
|
||||
} else {
|
||||
self?.logger.debug("Notification delivered: \(identifier)")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// Server event model for notification handling
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Types of server events that can be received from the VibeTunnel server.
|
||||
|
|
@ -54,19 +51,19 @@ enum ServerEventType: String, Codable, CaseIterable {
|
|||
var description: String {
|
||||
switch self {
|
||||
case .sessionStart:
|
||||
return "Session Started"
|
||||
"Session Started"
|
||||
case .sessionExit:
|
||||
return "Session Ended"
|
||||
"Session Ended"
|
||||
case .commandFinished:
|
||||
return "Command Completed"
|
||||
"Command Completed"
|
||||
case .commandError:
|
||||
return "Command Error"
|
||||
"Command Error"
|
||||
case .bell:
|
||||
return "Terminal Bell"
|
||||
"Terminal Bell"
|
||||
case .claudeTurn:
|
||||
return "Your Turn"
|
||||
"Your Turn"
|
||||
case .connected:
|
||||
return "Connected"
|
||||
"Connected"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +77,9 @@ enum ServerEventType: String, Codable, CaseIterable {
|
|||
var shouldNotify: Bool {
|
||||
switch self {
|
||||
case .sessionStart, .sessionExit, .claudeTurn:
|
||||
return true
|
||||
true
|
||||
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.
|
||||
/// - command: Optional command that started the session.
|
||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/sessionStart``.
|
||||
static func sessionStart(sessionId: String, sessionName: String? = nil, command: String? = nil) -> ServerEvent {
|
||||
ServerEvent(
|
||||
static func sessionStart(sessionId: String, sessionName: String? = nil, command: String? = nil) -> Self {
|
||||
Self(
|
||||
type: .sessionStart,
|
||||
sessionId: sessionId,
|
||||
sessionName: sessionName,
|
||||
|
|
@ -225,8 +222,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
|||
/// - sessionName: Optional human-readable name for the session.
|
||||
/// - exitCode: Optional process exit code.
|
||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/sessionExit``.
|
||||
static func sessionExit(sessionId: String, sessionName: String? = nil, exitCode: Int? = nil) -> ServerEvent {
|
||||
ServerEvent(
|
||||
static func sessionExit(sessionId: String, sessionName: String? = nil, exitCode: Int? = nil) -> Self {
|
||||
Self(
|
||||
type: .sessionExit,
|
||||
sessionId: sessionId,
|
||||
sessionName: sessionName,
|
||||
|
|
@ -244,8 +241,15 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
|||
/// - duration: Execution time in milliseconds.
|
||||
/// - exitCode: Optional process exit code.
|
||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/commandFinished``.
|
||||
static func commandFinished(sessionId: String, command: String, duration: Int, exitCode: Int? = nil) -> ServerEvent {
|
||||
ServerEvent(
|
||||
static func commandFinished(
|
||||
sessionId: String,
|
||||
command: String,
|
||||
duration: Int,
|
||||
exitCode: Int? = nil
|
||||
)
|
||||
-> Self
|
||||
{
|
||||
Self(
|
||||
type: .commandFinished,
|
||||
sessionId: sessionId,
|
||||
command: command,
|
||||
|
|
@ -264,8 +268,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
|||
/// - exitCode: The process exit code.
|
||||
/// - duration: Optional execution time in milliseconds.
|
||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/commandError``.
|
||||
static func commandError(sessionId: String, command: String, exitCode: Int, duration: Int? = nil) -> ServerEvent {
|
||||
ServerEvent(
|
||||
static func commandError(sessionId: String, command: String, exitCode: Int, duration: Int? = nil) -> Self {
|
||||
Self(
|
||||
type: .commandError,
|
||||
sessionId: sessionId,
|
||||
command: command,
|
||||
|
|
@ -283,8 +287,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
|||
/// - sessionId: The unique identifier for the session.
|
||||
/// - sessionName: Optional human-readable name for the session.
|
||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/claudeTurn``.
|
||||
static func claudeTurn(sessionId: String, sessionName: String? = nil) -> ServerEvent {
|
||||
ServerEvent(
|
||||
static func claudeTurn(sessionId: String, sessionName: String? = nil) -> Self {
|
||||
Self(
|
||||
type: .claudeTurn,
|
||||
sessionId: sessionId,
|
||||
sessionName: sessionName,
|
||||
|
|
@ -298,8 +302,8 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
|||
///
|
||||
/// - Parameter sessionId: The unique identifier for the session.
|
||||
/// - Returns: A configured `ServerEvent` of type ``ServerEventType/bell``.
|
||||
static func bell(sessionId: String) -> ServerEvent {
|
||||
ServerEvent(
|
||||
static func bell(sessionId: String) -> Self {
|
||||
Self(
|
||||
type: .bell,
|
||||
sessionId: sessionId,
|
||||
message: "Terminal bell"
|
||||
|
|
@ -336,20 +340,20 @@ struct ServerEvent: Codable, Identifiable, Equatable {
|
|||
///
|
||||
/// - Returns: A formatted duration string, or `nil` if no duration is set.
|
||||
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"
|
||||
} else if duration < 60000 {
|
||||
return String(format: "%.1fs", Double(duration) / 1000.0)
|
||||
} else if duration < 3600000 {
|
||||
let minutes = duration / 60000
|
||||
let seconds = (duration % 60000) / 1000
|
||||
} else if duration < 60_000 {
|
||||
return String(format: "%.1fs", Double(duration) / 1_000.0)
|
||||
} else if duration < 3_600_000 {
|
||||
let minutes = duration / 60_000
|
||||
let seconds = (duration % 60_000) / 1_000
|
||||
return "\(minutes)m \(seconds)s"
|
||||
} else {
|
||||
let hours = duration / 3600000
|
||||
let minutes = (duration % 3600000) / 60000
|
||||
let seconds = (duration % 60000) / 1000
|
||||
let hours = duration / 3_600_000
|
||||
let minutes = (duration % 3_600_000) / 60_000
|
||||
let seconds = (duration % 60_000) / 1_000
|
||||
return "\(hours)h \(minutes)m \(seconds)s"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,12 @@ final class SessionMonitor {
|
|||
}
|
||||
|
||||
/// 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
|
||||
var uniqueDirectoriesToCheck = Set<String>()
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,10 @@ struct PathSplittingTests {
|
|||
|
||||
// List contents of parent directory
|
||||
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) }
|
||||
// We can't assert specific matches as they depend on the user's home directory
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import Testing
|
||||
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")
|
||||
|
|
@ -39,7 +39,7 @@ struct ServerEventTests {
|
|||
sessionName: "Long Running Command",
|
||||
command: "npm install",
|
||||
exitCode: 0,
|
||||
duration: 15000,
|
||||
duration: 15_000,
|
||||
processInfo: "Node.js process",
|
||||
message: "Command completed successfully"
|
||||
)
|
||||
|
|
@ -52,7 +52,7 @@ struct ServerEventTests {
|
|||
#expect(decoded.sessionName == "Long Running Command")
|
||||
#expect(decoded.command == "npm install")
|
||||
#expect(decoded.exitCode == 0)
|
||||
#expect(decoded.duration == 15000)
|
||||
#expect(decoded.duration == 15_000)
|
||||
#expect(decoded.processInfo == "Node.js process")
|
||||
#expect(decoded.message == "Command completed successfully")
|
||||
}
|
||||
|
|
@ -76,6 +76,7 @@ struct ServerEventTests {
|
|||
}
|
||||
|
||||
// MARK: - Event Type Logic Tests
|
||||
|
||||
// Testing actual business logic, not Swift's enum implementation
|
||||
|
||||
@Test("Event type descriptions are user-friendly")
|
||||
|
|
@ -104,6 +105,7 @@ struct ServerEventTests {
|
|||
}
|
||||
|
||||
// MARK: - Edge Cases
|
||||
|
||||
// These test important edge cases for data integrity
|
||||
|
||||
@Test("Handles empty strings correctly")
|
||||
|
|
@ -146,6 +148,7 @@ struct ServerEventTests {
|
|||
}
|
||||
|
||||
// MARK: - Convenience Initializers
|
||||
|
||||
// These test that convenience initializers create properly configured events
|
||||
|
||||
@Test("sessionStart convenience initializer sets correct fields")
|
||||
|
|
@ -183,14 +186,14 @@ struct ServerEventTests {
|
|||
let event = ServerEvent.commandFinished(
|
||||
sessionId: "test-789",
|
||||
command: "npm install",
|
||||
duration: 15000,
|
||||
duration: 15_000,
|
||||
exitCode: 0
|
||||
)
|
||||
|
||||
#expect(event.type == .commandFinished)
|
||||
#expect(event.sessionId == "test-789")
|
||||
#expect(event.command == "npm install")
|
||||
#expect(event.duration == 15000)
|
||||
#expect(event.duration == 15_000)
|
||||
#expect(event.exitCode == 0)
|
||||
#expect(!event.shouldNotify)
|
||||
}
|
||||
|
|
@ -220,6 +223,7 @@ struct ServerEventTests {
|
|||
}
|
||||
|
||||
// MARK: - Computed Properties with Logic
|
||||
|
||||
// These test actual business logic in computed properties
|
||||
|
||||
@Test("displayName fallback logic works correctly")
|
||||
|
|
@ -243,9 +247,9 @@ struct ServerEventTests {
|
|||
|
||||
@Test("formattedDuration handles different time ranges", arguments: [
|
||||
(500, "500ms"),
|
||||
(2500, "2.5s"),
|
||||
(125000, "2m 5s"),
|
||||
(3661000, "1h 1m 1s")
|
||||
(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)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import Testing
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import VibeTunnel
|
||||
|
||||
@Suite("TunnelSession & Related Types")
|
||||
struct TunnelSessionTests {
|
||||
|
||||
// MARK: - TunnelSession Logic Tests
|
||||
|
||||
// Only testing actual logic, not property synthesis
|
||||
|
||||
@Test("updateActivity updates lastActivity timestamp")
|
||||
|
|
@ -23,7 +23,7 @@ struct TunnelSessionTests {
|
|||
|
||||
@Test("TunnelSession is Codable with all fields")
|
||||
func tunnelSessionCodable() throws {
|
||||
var originalSession = TunnelSession(processID: 67890)
|
||||
var originalSession = TunnelSession(processID: 67_890)
|
||||
originalSession.updateActivity()
|
||||
|
||||
let data = try JSONEncoder().encode(originalSession)
|
||||
|
|
@ -38,6 +38,7 @@ struct TunnelSessionTests {
|
|||
}
|
||||
|
||||
// MARK: - CreateSessionRequest Tests
|
||||
|
||||
// Testing optional field handling in Codable
|
||||
|
||||
@Test("CreateSessionRequest encodes/decodes with all optional fields")
|
||||
|
|
@ -91,6 +92,7 @@ struct TunnelSessionTests {
|
|||
}
|
||||
|
||||
// MARK: - CreateSessionResponse Tests
|
||||
|
||||
// Simple type but worth testing Codable with Date precision
|
||||
|
||||
@Test("CreateSessionResponse handles date encoding correctly")
|
||||
|
|
|
|||
Loading…
Reference in a new issue