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:
Peter Steinberger 2025-07-27 17:44:50 +02:00
parent f87b511ec1
commit dfe0cfda25
15 changed files with 275 additions and 246 deletions

View file

@ -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)

View file

@ -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,

View file

@ -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)")

View file

@ -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"
}
}

View file

@ -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>()

View file

@ -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

View file

@ -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)

View file

@ -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")