Files
knotchy/Tests/KnotchyCoreTests/RuntimeInstallerTests.swift
2026-09-28 18:28:32 +05:30

84 lines
3.8 KiB
Swift

import CryptoKit
import Foundation
import Testing
@testable import KnotchyCore
// Serialized: each download clears leftover download folders, which other tests would still be using.
@Suite(.serialized)
struct RuntimeInstallerTests {
private func release(for data: Data, url: URL) -> ContainerRuntime.Release {
let hash = SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined()
return ContainerRuntime.Release(version: "1.4.1", url: url, sha256: hash, size: Int64(data.count))
}
private func source(_ data: Data) throws -> URL {
let file = FileManager.default.temporaryDirectory.appendingPathComponent("source-\(UUID().uuidString).pkg")
try data.write(to: file)
return file
}
private static func downloadDirectories() -> Set<String> {
let names = (try? FileManager.default.contentsOfDirectory(atPath: NSTemporaryDirectory())) ?? []
return Set(names.filter { $0.hasPrefix("knotchy-runtime-") })
}
@Test func aMatchingDownloadIsKeptUntilDiscarded() async throws {
let data = Data("package".utf8)
let url = try source(data)
defer { try? FileManager.default.removeItem(at: url) }
let installer = SystemRuntimeInstaller()
let package = try await installer.download(release(for: data, url: url)) { _ in }
#expect(try Data(contentsOf: package.file) == data)
#expect(package.file.lastPathComponent == url.lastPathComponent)
installer.discard(package)
#expect(!FileManager.default.fileExists(atPath: package.directory.path))
}
@Test func aMismatchedDownloadIsDeleted() async throws {
let url = try source(Data("tampered".utf8))
defer { try? FileManager.default.removeItem(at: url) }
let expected = release(for: Data("package".utf8), url: url)
await #expect(throws: DownloadError.mismatch) {
_ = try await SystemRuntimeInstaller().download(expected) { _ in }
}
#expect(Self.downloadDirectories().isEmpty)
}
@Test func aNewDownloadClearsLeftovers() async throws {
let leftover = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("knotchy-runtime-LEFTOVER")
try FileManager.default.createDirectory(at: leftover, withIntermediateDirectories: true)
let data = Data("package".utf8)
let url = try source(data)
defer { try? FileManager.default.removeItem(at: url) }
let installer = SystemRuntimeInstaller()
let package = try await installer.download(release(for: data, url: url)) { _ in }
#expect(!FileManager.default.fileExists(atPath: leftover.path))
installer.discard(package)
}
@Test func aFailedDownloadSaysSo() async {
let missing = URL(fileURLWithPath: "/nonexistent/container.pkg")
await #expect(throws: DownloadError.self) {
_ = try await SystemRuntimeInstaller().download(release(for: Data(), url: missing)) { _ in }
}
}
// Only this path of the real `openInInstaller` may run in tests: any other would open Installer.
@MainActor
@Test func aChangedPackageIsNotOpened() async throws {
let data = Data("package".utf8)
let url = try source(data)
defer { try? FileManager.default.removeItem(at: url) }
let installer = SystemRuntimeInstaller()
let expected = release(for: data, url: url)
let package = try await installer.download(expected) { _ in }
try Data("pAckage".utf8).write(to: package.file) // Same size, so the hash has to catch it.
guard case .changed = await installer.openInInstaller(package, release: expected) else {
Issue.record("Expected the changed package not to be opened")
installer.discard(package)
return
}
#expect(!FileManager.default.fileExists(atPath: package.directory.path))
}
}