Files
Termind/FrameworkTests/SFTPNotificationTests.swift
T

144 lines
6.3 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
import TermindApplication
import TermindDomain
import TermindPresentation
import Testing
@Suite @MainActor
struct SFTPNotificationTests {
@Test func clearingFinishedHistoryPreservesThePendingBatchSummary() async {
let fixture = Fixture()
var removed = Set<UUID>()
fixture.store.sftpTransfersDidRemove = { [weak coordinator = fixture.coordinator] ids in
removed.formUnion(ids)
coordinator?.forgetFinishedTransfers(ids)
}
let first = fixture.enqueue()
let second = fixture.enqueue()
fixture.store.finishSFTPTransfer(first, state: .completed)
fixture.store.removeFinishedSFTPTransfer(first)
#expect(removed == [first])
fixture.store.finishSFTPTransfer(second, state: .failed("failure"))
fixture.store.clearFinishedSFTPTransfers()
#expect(removed == [first, second])
await settle()
#expect(fixture.sender.sent.map(\.body) == ["1/1"])
}
@Test func stoppingCancelsASuspendedDelivery() async {
let sender = NotificationDeliveryRecorder()
let coordinator = SFTPNotificationCoordinator(
service: sender, preferences: { .init() }, isFocused: { false }, content: { _, _ in ("", "") })
coordinator.receive(SFTPTransfer(localPath: "local", remotePath: "remote", state: .completed))
_ = await sender.nextRequest()
coordinator.stop()
await sender.finishDelivery()
#expect(sender.sent.isEmpty)
}
@Test func allTransfersFinishBeforeOneSummaryAndPausedWorkIsExcluded() async {
let fixture = Fixture()
let first = fixture.enqueue()
let second = fixture.enqueue()
let paused = fixture.enqueue()
fixture.store.setSFTPTransferState(.paused, for: paused)
fixture.store.finishSFTPTransfer(first, state: .completed)
await settle()
#expect(fixture.sender.sent.isEmpty)
fixture.store.finishSFTPTransfer(second, state: .failed("private reason"))
await settle()
#expect(fixture.sender.sent.count == 1)
#expect(fixture.sender.sent[0].body == "1/1")
#expect(fixture.sender.sent[0].target == .transfers)
fixture.store.finishSFTPTransfer(second, state: .failed("duplicate"))
fixture.store.setSFTPTransferState(.running, for: paused)
fixture.store.finishSFTPTransfer(paused, state: .completed)
await settle()
#expect(fixture.sender.sent.map(\.body) == ["1/1", "1/0"])
}
@Test func pausingOrCancellingAloneNeverNotifiesAndHistoryIsNotReplayed() async {
let fixture = Fixture()
let id = fixture.enqueue()
fixture.store.setSFTPTransferState(.paused, for: id)
fixture.store.finishSFTPTransfer(id, state: .cancelled)
await settle()
#expect(fixture.sender.sent.isEmpty)
let history = SFTPTransfer(localPath: "old", remotePath: "old", state: .completed)
let coordinator = SFTPNotificationCoordinator(
service: fixture.sender, initialTransfers: [history], preferences: { fixture.preferences },
isFocused: { false }, content: { _, _ in ("Transfers", "old") })
coordinator.receive(history)
await settle()
#expect(fixture.sender.sent.isEmpty)
}
@Test(arguments: NotificationPreferences.Mode.allCases, [false, true])
func modeUsesWhetherAnyWindowIsInTheForeground(_ mode: NotificationPreferences.Mode, foreground: Bool) async {
let fixture = Fixture()
fixture.preferences.sftpMode = mode
fixture.foreground = foreground
fixture.store.finishSFTPTransfer(fixture.enqueue(), state: .failed("failure"))
await settle()
#expect(fixture.sender.sent.count == (mode.allows(isFocused: foreground) ? 1 : 0))
let count = fixture.sender.sent.count
fixture.preferences.isEnabled = false
fixture.store.finishSFTPTransfer(fixture.enqueue(), state: .completed)
await settle()
#expect(fixture.sender.sent.count == count)
fixture.preferences.isEnabled = true
fixture.foreground = false
await settle()
#expect(fixture.sender.sent.count == count)
}
@Test func transfersTargetRoundTripsWithoutAnEntityID() {
#expect(NotificationTarget(kind: NotificationTarget.transfers.kind, id: "") == .transfers)
#expect(NotificationTarget.transfers.id == nil)
#expect(!SFTPNotificationContent.make(succeeded: 2, failed: 1).body.isEmpty)
}
@Test(arguments: [false, true])
func stoppingOrDisablingNotificationsBeforeDeliverySuppressesTheResult(stop: Bool) async {
let fixture = Fixture()
fixture.store.finishSFTPTransfer(fixture.enqueue(), state: .completed)
if stop { fixture.coordinator.stop() } else { fixture.preferences.isEnabled = false }
await settle()
#expect(fixture.sender.sent.isEmpty)
}
private func settle() async { for _ in 0..<20 { await Task.yield() } }
private final class Fixture {
let sender = Sender()
let store = HostLibraryModel()
var preferences = NotificationPreferences()
var foreground = false
var coordinator: SFTPNotificationCoordinator!
init() {
preferences.sftpMode = .always
coordinator = SFTPNotificationCoordinator(
service: sender, preferences: { [weak self] in self?.preferences ?? .init() },
isFocused: { [weak self] in self?.foreground ?? true }, content: { ("Transfers", "\($0)/\($1)") })
store.sftpTransferStateDidChange = { [weak coordinator] in coordinator?.receive($0) }
}
func enqueue() -> UUID {
let transfer = SFTPTransfer(localPath: "local", remotePath: "remote")
store.enqueueSFTPTransfer(transfer)
return transfer.id
}
}
private final class Sender: NotificationSending {
var sent: [AppNotification] = []
func authorization(request: Bool) async -> NotificationAuthorization { .allowed }
func send(_ notification: AppNotification, ifCurrent: @escaping @MainActor () -> Bool) async {
if ifCurrent() { sent.append(notification) }
}
func remove(_ identifiers: [String]) {}
}
}