Move diagnostics into settings, remove the app-local notification module, unify the global theme switcher, refine privacy copy and the instance detail experience, and rebuild the app icon.
162 lines
4.8 KiB
Swift
162 lines
4.8 KiB
Swift
import XCTest
|
|
|
|
@testable import MultiSimAdmin
|
|
|
|
final class SettingsViewModelTests: XCTestCase {
|
|
@MainActor
|
|
func testLoadsRuntimeWithoutServerCapabilities() async throws {
|
|
let store = SettingsMemoryLogStore()
|
|
let model = SettingsViewModel(store: store)
|
|
|
|
await model.refresh()
|
|
|
|
XCTAssertEqual(model.runtime?.appName, "MultiSimAdmin")
|
|
XCTAssertEqual(model.runtime?.marketingVersion, "0.2.0")
|
|
XCTAssertEqual(model.runtime?.buildVersion, "2")
|
|
XCTAssertEqual(model.runtime?.platform, "iOS")
|
|
XCTAssertTrue(model.runtime?.deviceModel.isEmpty == false)
|
|
|
|
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
|
|
}
|
|
}
|