Files
Termind/FrameworkTests/ConnectionHistoryMenuTests.swift
T

134 lines
7.2 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
import TermindApplication
import TermindDomain
import Testing
import struct TermindDomain.Host
@Suite @MainActor
struct ConnectionHistoryMenuTests {
private let now = Date(timeIntervalSince1970: 1_791_288_000)
private var calendar: Calendar {
var value = Calendar(identifier: .gregorian)
value.timeZone = TimeZone(secondsFromGMT: 0)!
return value
}
@Test func todayIncludesSyncedEventsAndDoesNotChangeOnRestart() throws {
let host = Host(name: "Server", host: "server.example")
let local = UUID()
let repository = InMemoryConnectionHistoryRepository()
let history = ConnectionHistoryModel(repository: repository, sourceDeviceID: local)
let library = HostLibraryModel(hosts: [host])
let first = try #require(history.record(kind: .ssh, for: host.id, at: now))
let second = try #require(history.record(kind: .sftp, for: host.id, at: now.addingTimeInterval(1)))
let synced = ConnectionHistoryEntry(
hostID: host.id, kind: .ssh, connectedAt: now.addingTimeInterval(2), sourceDeviceID: UUID())
try repository.insert(synced, limit: ConnectionHistoryModel.limit)
history.reload()
let menu = ConnectionHistoryMenuSnapshot(
history: history, library: library, now: now, calendar: calendar)
#expect(menu.recentToday.map(\.id) == [synced.id, second.id, first.id])
#expect(menu.days.isEmpty)
let restarted = ConnectionHistoryModel(repository: repository, sourceDeviceID: local)
let afterRestart = ConnectionHistoryMenuSnapshot(
history: restarted, library: library, now: now, calendar: calendar)
#expect(afterRestart.recentToday.map(\.id) == [synced.id, second.id, first.id])
#expect(afterRestart.days.isEmpty)
}
@Test func todayOverflowMovesIntoDateGroupsWithoutLosingOrDuplicatingRecords() throws {
let host = Host(name: "Server", host: "server.example")
let history = ConnectionHistoryModel()
let today = calendar.startOfDay(for: now)
let yesterday = try #require(history.record(kind: .ssh, for: host.id, at: today.addingTimeInterval(-60)))
let entries = try (0..<18).map { index in
try #require(history.record(kind: .ssh, for: host.id, at: today.addingTimeInterval(Double(index + 1))))
}
let menu = ConnectionHistoryMenuSnapshot(
history: history, library: HostLibraryModel(hosts: [host]),
now: today.addingTimeInterval(3600), calendar: calendar)
#expect(menu.recentToday.map(\.id) == entries.suffix(15).reversed().map(\.id))
#expect(menu.days.map(\.isToday) == [true, false])
#expect(menu.days.first?.rows.map(\.id) == entries.prefix(3).reversed().map(\.id))
#expect(menu.days.last?.rows.map(\.id) == [yesterday.id])
}
@Test func dateGroupsFollowLocalDaysAndOmitMissingHosts() {
let host = Host(name: "Server", host: "server.example")
let today = calendar.startOfDay(for: now)
let recent = ConnectionHistoryEntry(hostID: host.id, kind: .ssh, connectedAt: today.addingTimeInterval(60))
let earlier = ConnectionHistoryEntry(hostID: host.id, kind: .sftp, connectedAt: today.addingTimeInterval(-60))
let missing = ConnectionHistoryEntry(
hostID: UUID(), kind: .ssh, connectedAt: today.addingTimeInterval(-200_000))
let history = ConnectionHistoryModel(entries: [earlier, missing, recent])
let library = HostLibraryModel(hosts: [host])
let menu = ConnectionHistoryMenuSnapshot(
history: history, library: library, now: now, calendar: calendar)
#expect(menu.recentToday.map(\.id) == [recent.id])
#expect(menu.days.map(\.isToday) == [false])
#expect(menu.days.flatMap(\.rows).map(\.id) == [earlier.id])
var shifted = calendar
shifted.timeZone = TimeZone(secondsFromGMT: 8 * 3600)!
let localMenu = ConnectionHistoryMenuSnapshot(
history: history, library: library, now: today.addingTimeInterval(120), calendar: shifted)
#expect(localMenu.days.isEmpty)
#expect(localMenu.recentToday.map(\.id) == [recent.id, earlier.id])
let tomorrow = ConnectionHistoryMenuSnapshot(
history: history, library: library,
now: today.addingTimeInterval(86_400), calendar: calendar)
#expect(tomorrow.recentToday.isEmpty)
#expect(tomorrow.days.allSatisfy { !$0.isToday })
}
@Test(arguments: [DateComponents(year: 2026, month: 3, day: 10), DateComponents(year: 2026, month: 11, day: 3)])
func menuIncludesOnlySevenLocalCalendarDaysWithoutDeletingOlderHistory(components: DateComponents) throws {
var local = Calendar(identifier: .gregorian)
local.timeZone = try #require(TimeZone(identifier: "America/Los_Angeles"))
let today = try #require(local.date(from: components))
let firstDay = try #require(local.date(byAdding: .day, value: -6, to: today))
let tomorrow = try #require(local.date(byAdding: .day, value: 1, to: today))
let host = Host(name: "Server", host: "server.example")
let oldestIncluded = ConnectionHistoryEntry(hostID: host.id, kind: .ssh, connectedAt: firstDay)
let excluded = ConnectionHistoryEntry(
hostID: host.id, kind: .ssh, connectedAt: firstDay.addingTimeInterval(-1))
let future = ConnectionHistoryEntry(hostID: host.id, kind: .ssh, connectedAt: tomorrow)
let todayEntries = (0..<16).map {
ConnectionHistoryEntry(hostID: host.id, kind: .ssh, connectedAt: today.addingTimeInterval(Double($0)))
}
let history = ConnectionHistoryModel(entries: todayEntries + [oldestIncluded, excluded, future])
let menu = ConnectionHistoryMenuSnapshot(
history: history, library: HostLibraryModel(hosts: [host]), now: today, calendar: local)
#expect(menu.recentToday.map(\.id) == todayEntries.suffix(15).reversed().map(\.id))
#expect(menu.days.map(\.date) == [today, firstDay])
#expect(menu.days.flatMap(\.rows).map(\.id) == [todayEntries[0].id, oldestIncluded.id])
#expect(history.entries().contains { $0.id == excluded.id })
}
@Test func unavailableProtocolsStayVisibleAndClearRemovesBothSections() throws {
var host = Host(name: "Server", host: "server.example")
host.sshEnabled = false
let old = ConnectionHistoryEntry(
hostID: host.id, kind: .sftp, connectedAt: calendar.startOfDay(for: now).addingTimeInterval(-60))
let history = ConnectionHistoryModel(entries: [old])
let library = HostLibraryModel(hosts: [host])
_ = try #require(history.record(kind: .ssh, for: host.id, at: now))
let before = ConnectionHistoryMenuSnapshot(
history: history, library: library, now: now, calendar: calendar)
#expect(before.recentToday.first?.canOpen == false)
#expect(before.days.first?.rows.first?.canOpen == false)
history.clearAll()
let after = ConnectionHistoryMenuSnapshot(
history: history, library: library, now: now, calendar: calendar)
#expect(after.recentToday.isEmpty && after.days.isEmpty)
#expect(library.host(host.id) != nil)
}
}