import XCTest @testable import MultiSimAdmin final class SettingsViewModelTests: XCTestCase { @MainActor func testLoadsRuntimeAppAndDisabledServerCapabilities() async throws { let store = SettingsMemoryLogStore() let model = SettingsViewModel(store: store) await model.refresh() XCTAssertEqual(model.runtime?.appName, "MultiSimAdmin") XCTAssertEqual(model.runtime?.marketingVersion, "0.1.0") XCTAssertEqual(model.runtime?.buildVersion, "1") XCTAssertEqual(model.runtime?.platform, "iOS") XCTAssertTrue(model.runtime?.deviceModel.isEmpty == false) XCTAssertTrue(model.serverCapabilities.isEmpty) XCTAssertEqual(model.serverCapabilitiesNotice, "定时任务与后台刷新已在 iOS 版停用。") XCTAssertNil(model.errorMessage) } @MainActor func testClearsRuntimeLogsOnlyAndReportsRemovedCount() async throws { let store = SettingsMemoryLogStore() try await store.append( runtime: [Self.runtimeLog()], connections: [Self.connectionLog()] ) let model = SettingsViewModel(store: store) let removed = await model.clearRuntimeLogs() XCTAssertEqual(removed, 1) XCTAssertTrue(store.runtimeStorage.isEmpty) XCTAssertEqual(store.connectionStorage.count, 1) XCTAssertEqual(model.notice, "已清除 1 条运行日志。") XCTAssertNil(model.errorMessage) } @MainActor func testClearFailureSetsErrorWithoutChangingStorage() async throws { let store = SettingsMemoryLogStore() try await store.append( runtime: [Self.runtimeLog()], connections: [Self.connectionLog()] ) let failing = FailingLogStore(base: store) let model = SettingsViewModel(store: failing) let removed = await model.clearRuntimeLogs() XCTAssertEqual(removed, 0) XCTAssertEqual(store.runtimeStorage.count, 1) XCTAssertEqual(store.connectionStorage.count, 1) XCTAssertEqual(model.errorMessage, "清除运行日志失败。") XCTAssertNil(model.notice) } private static func runtimeLog() -> RuntimeLogEntry { RuntimeLogEntry( id: "runtime-1", source: .delivery, level: .info, message: "测试日志", occurredAt: Date() ) } private static func connectionLog() -> ConnectionLogEntry { ConnectionLogEntry( id: "connection-1", instanceID: UUID(), instanceName: "Node", outcome: .success, state: "online", durationMs: 20, observedAt: Date() ) } } private final class SettingsMemoryLogStore: LogStore, @unchecked Sendable { private let lock = NSLock() private var runtimeItems: [RuntimeLogEntry] = [] private var connectionItems: [ConnectionLogEntry] = [] var runtimeStorage: [RuntimeLogEntry] { lock.withLock { runtimeItems } } var connectionStorage: [ConnectionLogEntry] { lock.withLock { connectionItems } } func runtimeLogs() async throws -> [RuntimeLogEntry] { runtimeStorage } func connectionLogs() async throws -> [ConnectionLogEntry] { connectionStorage } func append( runtime: [RuntimeLogEntry], connections: [ConnectionLogEntry] ) async throws { lock.withLock { var mergedRuntime = runtime for existing in runtimeItems where !runtime.contains(where: { $0.id == existing.id }) { mergedRuntime.append(existing) } runtimeItems = mergedRuntime.sorted { $0.occurredAt > $1.occurredAt } var mergedConnections = connections for existing in connectionItems where !connections.contains(where: { $0.id == existing.id }) { mergedConnections.append(existing) } connectionItems = mergedConnections.sorted { $0.observedAt > $1.observedAt } } } func pruneConnections(before _: Date) async throws -> Int { 0 } func clearRuntimeLogs() async throws -> Int { lock.withLock { let removed = runtimeItems.count runtimeItems = [] return removed } } } private struct FailingLogStore: LogStore { let base: SettingsMemoryLogStore func runtimeLogs() async throws -> [RuntimeLogEntry] { try await base.runtimeLogs() } func connectionLogs() async throws -> [ConnectionLogEntry] { try await base.connectionLogs() } func append( runtime: [RuntimeLogEntry], connections: [ConnectionLogEntry] ) async throws { try await base.append(runtime: runtime, connections: connections) } func pruneConnections(before cutoff: Date) async throws -> Int { try await base.pruneConnections(before: cutoff) } func clearRuntimeLogs() async throws -> Int { throw RepositoryError.databaseFailed } }