Files
Termind/FrameworkTests/HistoryRenderingSnapshotTests.swift
T

194 lines
7.0 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
import Observation
import TermindApplication
import TermindDomain
import Testing
@Suite @MainActor
struct HistoryRenderingSnapshotTests {
@Test func failedClearKeepsHistoryReadable() throws {
let repository = HistoryRepository()
let model = ConnectionHistoryModel(repository: repository)
let entry = try #require(model.record(kind: .ssh, for: UUID()))
repository.failsClear = true
model.clearAll()
#expect(model.entries().map(\.id) == [entry.id])
#expect(model.storageError != nil)
repository.failsClear = false
model.clearAll()
#expect(model.entries().isEmpty)
#expect(model.storageError == nil)
}
@Test func failedInitialTrimDoesNotHideReadableHistory() throws {
let repository = HistoryRepository()
let entry = ConnectionHistoryEntry(hostID: UUID(), kind: .ssh)
try repository.insert(entry, limit: 10)
repository.failsTrim = true
let model = ConnectionHistoryModel(repository: repository)
#expect(model.entries().map(\.id) == [entry.id])
#expect(model.storageError != nil)
model.reload()
#expect(model.entries().map(\.id) == [entry.id])
#expect(model.storageError == nil)
}
@Test func failedInitialRegistrationDoesNotHideReadableDeviceNames() throws {
let repository = DeviceRepository()
let remote = DeviceRecord(id: UUID(), displayName: "Remote")
try repository.upsert(remote)
repository.failsWrites = true
let model = DeviceDirectoryModel(repository: repository, currentDeviceName: "Local")
#expect(model.name(for: remote.id) == "Remote")
#expect(model.name(for: model.currentDeviceID) == "Local")
#expect(model.storageError != nil)
model.reload()
#expect(model.name(for: remote.id) == "Remote")
#expect(model.storageError == nil)
}
@Test(arguments: [false, true])
func historyReadsDoNotFetchOrPublishDuringRendering(fails: Bool) {
let repository = HistoryRepository()
repository.fails = fails
let model = ConnectionHistoryModel(repository: repository)
let hostID = UUID()
let reads = repository.reads
withObservationTracking {
_ = model.storageError
_ = model.entries()
} onChange: {
Issue.record("Reading history must not publish observable changes")
}
for _ in 0..<2 {
_ = model.entries()
_ = model.entries(for: hostID)
_ = model.mostRecentDate(for: hostID)
_ = model.localEntries()
}
#expect(repository.reads == reads)
#expect((model.storageError != nil) == fails)
}
@Test func historyReloadPublishesCommittedRowsAndRecoversFromReadFailure() throws {
let repository = HistoryRepository()
let model = ConnectionHistoryModel(repository: repository)
let entry = ConnectionHistoryEntry(hostID: UUID(), kind: .ssh)
try repository.insert(entry, limit: 10)
model.reload()
#expect(model.entries().map(\.id) == [entry.id])
repository.fails = true
model.reload()
let failedReads = repository.reads
#expect(model.entries().isEmpty)
#expect(model.storageError != nil)
#expect(repository.reads == failedReads)
repository.fails = false
model.reload()
#expect(model.mostRecentDate(for: entry.hostID) == entry.connectedAt)
#expect(model.storageError == nil)
model.delete(entry.id)
#expect(model.entries().isEmpty)
}
@Test(arguments: [false, true])
func deviceReadsDoNotFetchOrPublishDuringRendering(fails: Bool) {
let repository = DeviceRepository()
repository.fails = fails
let model = DeviceDirectoryModel(repository: repository, currentDeviceName: "Local")
let reads = repository.reads
withObservationTracking {
_ = model.storageError
_ = model.devices()
} onChange: {
Issue.record("Reading device names must not publish observable changes")
}
for _ in 0..<2 {
_ = model.devices()
#expect(model.name(for: model.currentDeviceID) == "Local")
#expect(model.name(for: UUID()) == nil)
}
#expect(repository.reads == reads)
#expect((model.storageError != nil) == fails)
}
@Test func deviceReloadPublishesCommittedNamesAndRecoversFromReadFailure() throws {
let repository = DeviceRepository()
let model = DeviceDirectoryModel(repository: repository, currentDeviceName: "Local")
let remote = DeviceRecord(id: UUID(), displayName: "Remote")
try repository.upsert(remote)
model.reload()
#expect(model.name(for: remote.id) == "Remote")
repository.fails = true
model.reload()
let failedReads = repository.reads
#expect(model.devices().isEmpty)
#expect(model.storageError != nil)
#expect(repository.reads == failedReads)
repository.fails = false
model.reload()
#expect(model.name(for: remote.id) == "Remote")
#expect(model.storageError == nil)
model.updateCurrentDeviceName("Renamed")
#expect(model.name(for: model.currentDeviceID) == "Renamed")
}
private enum ReadFailure: Error { case unavailable }
private final class HistoryRepository: ConnectionHistoryRepository {
let storage = InMemoryConnectionHistoryRepository()
var reads = 0
var fails = false
var failsTrim = false
var failsClear = false
func entries() throws -> [ConnectionHistoryEntry] {
reads += 1
if fails { throw ReadFailure.unavailable }
return try storage.entries()
}
func trim(to limit: Int) throws {
if failsTrim { throw ReadFailure.unavailable }
try storage.trim(to: limit)
}
func insert(_ entry: ConnectionHistoryEntry, limit: Int) throws { try storage.insert(entry, limit: limit) }
func end(_ id: UUID, at date: Date) throws { try storage.end(id, at: date) }
func delete(_ id: UUID) throws { try storage.delete(id) }
func clear(for hostID: UUID) throws { try storage.clear(for: hostID) }
func clearAll() throws {
if failsClear { throw ReadFailure.unavailable }
try storage.clearAll()
}
}
private final class DeviceRepository: DeviceDirectoryRepository {
let storage = InMemoryDeviceDirectoryRepository()
var reads = 0
var fails = false
var failsWrites = false
func devices() throws -> [DeviceRecord] {
reads += 1
if fails { throw ReadFailure.unavailable }
return try storage.devices()
}
func upsert(_ device: DeviceRecord) throws {
if failsWrites { throw ReadFailure.unavailable }
try storage.upsert(device)
}
}
}