Files
Termind/FrameworkTests/MetricsSyncTests.swift
T

145 lines
5.9 KiB
Swift

import CloudKit
import Foundation
import SwiftData
import TermindApplication
import TermindDomain
import TermindInfrastructure
import TermindPersistence
import Testing
@Suite @MainActor
struct MetricsSyncTests {
@Test
func legacySelectionsMigrateOnceWithCompleteConfigurationAndOrder() throws {
let defaults = UserDefaults(suiteName: "MetricsSyncTests.\(UUID())")!
let first = MetricsSelection(id: UUID(), enabled: false, disks: ["sda"], interfaces: [])
let second = MetricsSelection(id: UUID(), interfaces: ["eth0"])
defaults.set(try JSONEncoder().encode([first]), forKey: "metrics.servers")
defaults.set(try JSONEncoder().encode([first, second]), forKey: "mobile.metrics.v1")
let container = try TermindPersistence.makeContainer(inMemory: true)
let store = MetricsSyncPersistence(context: container.mainContext, defaults: defaults)
try store.prepare()
#expect(try store.load() == [first, second])
#expect(try store.journal.pendingEnvelopes().count == 2)
defaults.set(try JSONEncoder().encode([] as [MetricsSelection]), forKey: "metrics.servers")
try store.prepare()
#expect(try store.load() == [first, second])
}
@Test
func recordsRoundTripAndRemoteChangesMergePerHost() throws {
let (sourceContainer, source) = try fixture()
let (destinationContainer, destination) = try fixture()
defer { withExtendedLifetime((sourceContainer, destinationContainer)) {} }
let first = MetricsSelection(id: UUID(), enabled: false, disks: ["nvme0n1"])
let second = MetricsSelection(id: UUID(), interfaces: ["en0"])
try source.save([first, second])
let outgoing = try source.journal.pendingEnvelopes()
let applied = try destination.applyRemote(outgoing)
#expect(applied.snapshot.selections == [first, second])
let envelope = try #require(outgoing.first)
let data = try MetricsSyncCodec.encode(envelope)
#expect(try MetricsSyncCodec.decode(data) == envelope)
let record = try MetricsSyncCoordinator.makeRecord(
MetricsOutgoingEntry(envelope: envelope),
recordID: CKRecord.ID(
recordName: envelope.key.recordName,
zoneID: CKRecordZone.ID(zoneName: MetricsSyncCoordinator.zoneName)))
#expect(try MetricsSyncCoordinator.envelope(from: record) == envelope)
try source.save([second, first])
let reordered = try destination.applyRemote(source.journal.pendingEnvelopes())
#expect(reordered.snapshot.selections == [second, first])
try source.save([second])
let deleted = try destination.applyRemote(source.journal.pendingEnvelopes())
#expect(deleted.snapshot.selections == [second])
}
@Test
func missingHostsStayConfiguredAndMaterializeLater() {
let host = TermindDomain.Host(name: "Later", host: "example.invalid")
let preferences = TestMetricsPreferences(values: [MetricsSelection(id: host.id, enabled: false)])
let library = HostLibraryModel()
let model = ServerMetricsModel(
hosts: library, preferences: preferences,
makeSampler: { TestMetricsSampler() })
model.start()
model.suspend()
#expect(model.servers.isEmpty)
#expect(model.selections.map(\.id) == [host.id])
library.upsert(host)
model.reconcile()
#expect(model.servers.map(\.id) == [host.id])
#expect(!model.servers[0].selection.enabled)
}
@Test
func failedPersistenceDoesNotChangeRuntimeConfiguration() {
let host = TermindDomain.Host(name: "Stable", host: "example.invalid")
let preferences = TestMetricsPreferences(values: [MetricsSelection(id: host.id)])
let library = HostLibraryModel()
library.upsert(host)
let model = ServerMetricsModel(
hosts: library, preferences: preferences,
makeSampler: { TestMetricsSampler() })
model.start()
model.suspend()
preferences.failSaves = true
model.setEnabled(false, for: model.servers[0])
#expect(model.servers[0].selection.enabled)
#expect(model.storageError == TestMetricsError.failed.localizedDescription)
}
@Test
func metricsCategoryDefaultsOnWithoutChangingTheMasterPreference() {
let defaults = UserDefaults(suiteName: "MetricsSyncDefaults.\(UUID())")!
defaults.set(false, forKey: SettingsKey.iCloudSyncEnabled)
SettingsKey.migrateSyncPreferences(in: defaults)
#expect(SettingsKey.defaultMetricsSyncEnabled)
#expect(!defaults.bool(forKey: SettingsKey.iCloudSyncEnabled))
#expect(defaults.object(forKey: SettingsKey.metricsSyncEnabled) == nil)
}
private func fixture() throws -> (ModelContainer, MetricsSyncPersistence) {
let container = try TermindPersistence.makeContainer(inMemory: true)
let defaults = UserDefaults(suiteName: "MetricsSyncFixture.\(UUID())")!
let store = MetricsSyncPersistence(context: container.mainContext, defaults: defaults)
try store.prepare()
return (container, store)
}
}
private enum TestMetricsError: LocalizedError {
case failed
var errorDescription: String? { "Metrics persistence failed." }
}
@MainActor
private final class TestMetricsPreferences: MetricsConfigurationStoring {
var values: [MetricsSelection]
var failSaves = false
init(values: [MetricsSelection]) { self.values = values }
func load() -> [MetricsSelection] { values }
func save(_ selections: [MetricsSelection]) throws {
if failSaves { throw TestMetricsError.failed }
values = selections
}
}
nonisolated private struct TestMetricsSampler: MetricsSampling {
func connect(_ configuration: SSHConnectionConfiguration) async throws {}
func sample() async throws -> MetricsSample { throw CancellationError() }
func gpu() async throws -> NvidiaSnapshot? { nil }
func disconnect() async {}
}