- 删除分组实体:分组视图/编辑器/仓储与 groupId 全部移除,升级首启清理 instance-groups.json 与 fleet.groupMode - 新增批量标签表:三态覆盖度(未贴 / N/M / 全部),整批加减与新建标签 - 配色重构,全应用零蓝色:石墨墨色承担主操作与选中态,颜色只留给状态语义; 浅色改中性冷灰,深色改低明度石墨三层 - 选中态改实心主色 + onPrimary,替换不可读的低透明度色雾(主题块、图例胶囊) - 应用图标重做为扁平石墨底 + 黄铜 SIM 面板,参数化光栅化脚本与参考稿 - 运行概览:检测进度并入副标题,读数栅格固定八格两行并保留不可用占位 - 实例详情:仪表改两列发丝线栅格,缺数据留轨道,修掉头部高度回弹; 补齐设备名称/存储总量/可用存储/使用率四个字段名 - 版本号显示去掉括号,设置页只显示 0.2.2 - 文档:新增 docs/ios-native-0.2.2.md,同步 ios-native.md 与两处 README
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.2")
|
|
XCTAssertEqual(model.runtime?.buildVersion, "7")
|
|
XCTAssertEqual(model.runtime?.platform, "iOS")
|
|
XCTAssertTrue(model.runtime?.systemVersion.hasPrefix("iOS") ?? 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
|
|
}
|
|
}
|