Files
Termind/TFMTests/ShellIntegrationLoggingTests.swift
T

131 lines
5.1 KiB
Swift

import Foundation
import Logging
import Testing
@testable import TermindRemoteInfrastructure
@Suite
struct ShellIntegrationLoggingTests {
@Test
func lifecycleLogsAreCorrelatedAndExcludeRemoteValues() throws {
let capture = ShellIntegrationLogCapture()
let logger = Logger(label: "termind.shell-integration-test") { _ in
ShellIntegrationCaptureHandler(capture: capture)
}
let hostID = UUID()
var lifecycle = RemoteShellIntegrationLifecycleLogger(logger: logger)
lifecycle.begin(hostID: hostID, enabled: true)
var bootstrap = RemoteShellIntegrationBootstrap(token: "LOGGING")
_ = bootstrap.receive(
Data(
("__TRMD_LOGGING_SYSTEM__sensitive-system||1|SensitiveKernel|1|arm64__\r\n"
+ "__TRMD_LOGGING_SHELL__/sensitive/shell/bash__\r\n").utf8
)
)
let completed = bootstrap.receive(
Data(
"__TRMD_LOGGING_HOME__%2Fsensitive%2Fhome\r\n__TRMD_LOGGING_DONE__\r\n".utf8
)
)
lifecycle.record(try #require(completed.diagnostic), hostID: hostID)
lifecycle.sessionEnded(hostID: hostID)
let entries = capture.entries
try #require(entries.map(\.message) == ["ssh.shell_integration.started", "ssh.shell_integration.completed"])
#expect(entries.map(\.level) == [.info, .info])
#expect(entries[1].metadata["outcome"] == "success")
#expect(entries[1].metadata["home_resolution"] == "resolved")
#expect(entries[1].metadata["system_info"] == "available")
#expect(entries[0].metadata["operation_id"] == entries[1].metadata["operation_id"])
#expect(entries.allSatisfy { $0.metadata["host_id"] == hostID.uuidString.lowercased() })
let serialized = entries.map { "\($0.message) \($0.metadata)" }.joined(separator: "\n")
#expect(!serialized.localizedCaseInsensitiveContains("sensitive"))
#expect(!serialized.contains("LOGGING"))
}
@Test
func delayedTimeoutDisabledAndSessionEndUseStableReasonsWithoutDuplicates() {
let capture = ShellIntegrationLogCapture()
let logger = Logger(label: "termind.shell-integration-test") { _ in
ShellIntegrationCaptureHandler(capture: capture)
}
let hostID = UUID()
var lifecycle = RemoteShellIntegrationLifecycleLogger(logger: logger)
lifecycle.begin(hostID: hostID, enabled: false)
lifecycle.begin(hostID: hostID, enabled: true)
lifecycle.record(.probeDelayed, hostID: hostID)
lifecycle.record(.timedOut(stage: .installation, late: true), hostID: hostID)
lifecycle.sessionEnded(hostID: hostID)
lifecycle.begin(hostID: hostID, enabled: true)
lifecycle.sessionEnded(hostID: hostID)
lifecycle.sessionEnded(hostID: hostID)
let entries = capture.entries
#expect(
entries.map(\.message) == [
"ssh.shell_integration.skipped",
"ssh.shell_integration.started",
"ssh.shell_integration.delayed",
"ssh.shell_integration.timed_out",
"ssh.shell_integration.started",
"ssh.shell_integration.skipped",
])
#expect(entries.map(\.level) == [.info, .info, .warning, .warning, .info, .info])
#expect(entries[0].metadata["reason"] == "disabled")
#expect(entries[2].metadata["reason"] == "probe_timeout")
#expect(entries[3].metadata["reason"] == "timeout")
#expect(entries[3].metadata["stage"] == "installation")
#expect(entries[5].metadata["reason"] == "session_ended")
}
}
private nonisolated struct ShellIntegrationLogEntry: Sendable {
let level: Logger.Level
let message: String
let metadata: [String: String]
}
private nonisolated final class ShellIntegrationLogCapture: @unchecked Sendable {
private let lock = NSLock()
private var storage: [ShellIntegrationLogEntry] = []
var entries: [ShellIntegrationLogEntry] { lock.withLock { storage } }
func append(_ event: LogEvent, metadata: Logger.Metadata) {
lock.withLock {
storage.append(
ShellIntegrationLogEntry(
level: event.level,
message: event.message.description,
metadata: metadata.mapValues(\.description)
)
)
}
}
}
private nonisolated struct ShellIntegrationCaptureHandler: LogHandler {
let capture: ShellIntegrationLogCapture
var metadataProvider: Logger.MetadataProvider?
var metadata: Logger.Metadata = [:]
var logLevel: Logger.Level = .trace
subscript(metadataKey key: String) -> Logger.Metadata.Value? {
get { metadata[key] }
set { metadata[key] = newValue }
}
func log(event: LogEvent) {
var combined = metadata
if let provided = metadataProvider?.get() {
combined.merge(provided) { _, provided in provided }
}
if let explicit = event.metadata {
combined.merge(explicit) { _, explicit in explicit }
}
capture.append(event, metadata: combined)
}
}