test: add gateway/runtime utility coverage
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdis
|
||||
|
||||
@Suite struct GatewayEnvironmentTests {
|
||||
@Test func semverParsesCommonForms() {
|
||||
#expect(Semver.parse("1.2.3") == Semver(major: 1, minor: 2, patch: 3))
|
||||
#expect(Semver.parse("v2.0.0") == Semver(major: 2, minor: 0, patch: 0))
|
||||
#expect(Semver.parse("3.4.5-beta.1") == Semver(major: 3, minor: 4, patch: 0)) // patch drops trailing text
|
||||
#expect(Semver.parse(nil) == nil)
|
||||
#expect(Semver.parse("invalid") == nil)
|
||||
}
|
||||
|
||||
@Test func semverCompatibilityRequiresSameMajorAndNotOlder() {
|
||||
let required = Semver(major: 2, minor: 1, patch: 0)
|
||||
#expect(Semver(major: 2, minor: 1, patch: 0).compatible(with: required))
|
||||
#expect(Semver(major: 2, minor: 2, patch: 0).compatible(with: required))
|
||||
#expect(Semver(major: 3, minor: 0, patch: 0).compatible(with: required) == false)
|
||||
#expect(Semver(major: 1, minor: 9, patch: 9).compatible(with: required) == false)
|
||||
}
|
||||
|
||||
@Test func gatewayPortDefaultsAndRespectsOverride() {
|
||||
let defaultPort = GatewayEnvironment.gatewayPort()
|
||||
#expect(defaultPort == 18789)
|
||||
|
||||
UserDefaults.standard.set(19999, forKey: "gatewayPort")
|
||||
defer { UserDefaults.standard.removeObject(forKey: "gatewayPort") }
|
||||
#expect(GatewayEnvironment.gatewayPort() == 19999)
|
||||
}
|
||||
}
|
||||
71
apps/macos/Tests/ClawdisIPCTests/RuntimeLocatorTests.swift
Normal file
71
apps/macos/Tests/ClawdisIPCTests/RuntimeLocatorTests.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdis
|
||||
|
||||
@Suite struct RuntimeLocatorTests {
|
||||
private func makeTempExecutable(contents: String) throws -> URL {
|
||||
let dir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
let path = dir.appendingPathComponent("node")
|
||||
try contents.write(to: path, atomically: true, encoding: .utf8)
|
||||
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: path.path)
|
||||
return path
|
||||
}
|
||||
|
||||
@Test func resolveSucceedsWithValidNode() throws {
|
||||
let script = """
|
||||
#!/bin/sh
|
||||
echo v22.5.0
|
||||
"""
|
||||
let node = try self.makeTempExecutable(contents: script)
|
||||
let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
||||
guard case let .success(res) = result else {
|
||||
Issue.record("Expected success, got \(result)")
|
||||
return
|
||||
}
|
||||
#expect(res.path == node.path)
|
||||
#expect(res.version == RuntimeVersion(major: 22, minor: 5, patch: 0))
|
||||
}
|
||||
|
||||
@Test func resolveFailsWhenTooOld() throws {
|
||||
let script = """
|
||||
#!/bin/sh
|
||||
echo v18.2.0
|
||||
"""
|
||||
let node = try self.makeTempExecutable(contents: script)
|
||||
let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
||||
guard case let .failure(.unsupported(_, found, _, path, _)) = result else {
|
||||
Issue.record("Expected unsupported error, got \(result)")
|
||||
return
|
||||
}
|
||||
#expect(found == RuntimeVersion(major: 18, minor: 2, patch: 0))
|
||||
#expect(path == node.path)
|
||||
}
|
||||
|
||||
@Test func resolveFailsWhenVersionUnparsable() throws {
|
||||
let script = """
|
||||
#!/bin/sh
|
||||
echo node-version:unknown
|
||||
"""
|
||||
let node = try self.makeTempExecutable(contents: script)
|
||||
let result = RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
||||
guard case let .failure(.versionParse(_, raw, path, _)) = result else {
|
||||
Issue.record("Expected versionParse error, got \(result)")
|
||||
return
|
||||
}
|
||||
#expect(raw.contains("unknown"))
|
||||
#expect(path == node.path)
|
||||
}
|
||||
|
||||
@Test func describeFailureIncludesPaths() {
|
||||
let msg = RuntimeLocator.describeFailure(.notFound(searchPaths: ["/tmp/a", "/tmp/b"]))
|
||||
#expect(msg.contains("PATH searched: /tmp/a:/tmp/b"))
|
||||
}
|
||||
|
||||
@Test func runtimeVersionParsesWithLeadingVAndMetadata() {
|
||||
#expect(RuntimeVersion.from(string: "v22.1.3") == RuntimeVersion(major: 22, minor: 1, patch: 3))
|
||||
#expect(RuntimeVersion.from(string: "node 22.3.0-alpha.1") == RuntimeVersion(major: 22, minor: 3, patch: 0))
|
||||
#expect(RuntimeVersion.from(string: "bogus") == nil)
|
||||
}
|
||||
}
|
||||
80
apps/macos/Tests/ClawdisIPCTests/UtilitiesTests.swift
Normal file
80
apps/macos/Tests/ClawdisIPCTests/UtilitiesTests.swift
Normal file
@@ -0,0 +1,80 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdis
|
||||
|
||||
@Suite struct UtilitiesTests {
|
||||
@Test func ageStringsCoverCommonWindows() {
|
||||
let now = Date(timeIntervalSince1970: 1_000_000)
|
||||
#expect(age(from: now, now: now) == "just now")
|
||||
#expect(age(from: now.addingTimeInterval(-45), now: now) == "just now")
|
||||
#expect(age(from: now.addingTimeInterval(-75), now: now) == "1 minute ago")
|
||||
#expect(age(from: now.addingTimeInterval(-10 * 60), now: now) == "10m ago")
|
||||
#expect(age(from: now.addingTimeInterval(-3_600), now: now) == "1 hour ago")
|
||||
#expect(age(from: now.addingTimeInterval(-5 * 3_600), now: now) == "5h ago")
|
||||
#expect(age(from: now.addingTimeInterval(-26 * 3_600), now: now) == "yesterday")
|
||||
#expect(age(from: now.addingTimeInterval(-3 * 86_400), now: now) == "3d ago")
|
||||
}
|
||||
|
||||
@Test func parseSSHTargetSupportsUserPortAndDefaults() {
|
||||
let parsed1 = CommandResolver.parseSSHTarget("alice@example.com:2222")
|
||||
#expect(parsed1?.user == "alice")
|
||||
#expect(parsed1?.host == "example.com")
|
||||
#expect(parsed1?.port == 2222)
|
||||
|
||||
let parsed2 = CommandResolver.parseSSHTarget("example.com")
|
||||
#expect(parsed2?.user == nil)
|
||||
#expect(parsed2?.host == "example.com")
|
||||
#expect(parsed2?.port == 22)
|
||||
|
||||
let parsed3 = CommandResolver.parseSSHTarget("bob@host")
|
||||
#expect(parsed3?.user == "bob")
|
||||
#expect(parsed3?.host == "host")
|
||||
#expect(parsed3?.port == 22)
|
||||
}
|
||||
|
||||
@Test func sanitizedTargetStripsLeadingSSHPrefix() {
|
||||
UserDefaults.standard.set(AppState.ConnectionMode.remote.rawValue, forKey: connectionModeKey)
|
||||
UserDefaults.standard.set("ssh alice@example.com", forKey: remoteTargetKey)
|
||||
defer {
|
||||
UserDefaults.standard.removeObject(forKey: connectionModeKey)
|
||||
UserDefaults.standard.removeObject(forKey: remoteTargetKey)
|
||||
}
|
||||
|
||||
let settings = CommandResolver.connectionSettings()
|
||||
#expect(settings.mode == .remote)
|
||||
#expect(settings.target == "alice@example.com")
|
||||
}
|
||||
|
||||
@Test func gatewayEntrypointPrefersDistOverBin() throws {
|
||||
let tmp = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
let dist = tmp.appendingPathComponent("dist/index.js")
|
||||
let bin = tmp.appendingPathComponent("bin/clawdis.js")
|
||||
try FileManager.default.createDirectory(at: dist.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try FileManager.default.createDirectory(at: bin.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
FileManager.default.createFile(atPath: dist.path, contents: Data())
|
||||
FileManager.default.createFile(atPath: bin.path, contents: Data())
|
||||
|
||||
let entry = CommandResolver.gatewayEntrypoint(in: tmp)
|
||||
#expect(entry == dist.path)
|
||||
}
|
||||
|
||||
@Test func logLocatorPicksNewestLogFile() throws {
|
||||
let fm = FileManager.default
|
||||
let dir = URL(fileURLWithPath: "/tmp/clawdis", isDirectory: true)
|
||||
try? fm.createDirectory(at: dir, withIntermediateDirectories: true)
|
||||
|
||||
let older = dir.appendingPathComponent("clawdis-old-\(UUID().uuidString).log")
|
||||
let newer = dir.appendingPathComponent("clawdis-new-\(UUID().uuidString).log")
|
||||
fm.createFile(atPath: older.path, contents: Data("old".utf8))
|
||||
fm.createFile(atPath: newer.path, contents: Data("new".utf8))
|
||||
try fm.setAttributes([.modificationDate: Date(timeIntervalSinceNow: -100)], ofItemAtPath: older.path)
|
||||
try fm.setAttributes([.modificationDate: Date()], ofItemAtPath: newer.path)
|
||||
|
||||
let best = LogLocator.bestLogFile()
|
||||
#expect(best?.lastPathComponent == newer.lastPathComponent)
|
||||
|
||||
try? fm.removeItem(at: older)
|
||||
try? fm.removeItem(at: newer)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user