67 lines
3.0 KiB
Swift
67 lines
3.0 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
import Testing
|
|
|
|
@testable import TermindPresentation
|
|
|
|
@Suite @MainActor
|
|
struct TailscaleExitNodePresentationTests {
|
|
@Test(arguments: [false, true])
|
|
func selectingAnExitNodeRequiresItToBeOnlineAndOnlyChangesTheDraft(online: Bool) async throws {
|
|
let (model, library, group) = try fixture(online: online)
|
|
let editor = TailscaleGroupEditor(model: model, group: group)
|
|
let field = TailscaleExitNodeField(editor: editor)
|
|
field.selection.wrappedValue = "candidate"
|
|
#expect(editor.exitNodeID == (online ? "candidate" : "current"))
|
|
#expect(library.group(group.id)?.tailscale?.exitNodeID == "current")
|
|
field.selection.wrappedValue = "missing"
|
|
#expect(editor.exitNodeID == (online ? "candidate" : "current"))
|
|
field.selection.wrappedValue = ""
|
|
#expect(editor.exitNodeID.isEmpty)
|
|
#expect(library.group(group.id)?.tailscale?.exitNodeID == "current")
|
|
await model.shutdown()
|
|
}
|
|
|
|
@Test func anOfflineCurrentSelectionIsRetainedUntilTheUserChangesIt() async throws {
|
|
let (model, _, group) = try fixture(online: false)
|
|
let editor = TailscaleGroupEditor(model: model, group: group)
|
|
let field = TailscaleExitNodeField(editor: editor)
|
|
#expect(field.selection.wrappedValue == "current")
|
|
field.selection.wrappedValue = "candidate"
|
|
#expect(field.selection.wrappedValue == "current")
|
|
editor.cancel()
|
|
let reopened = TailscaleGroupEditor(model: model, group: group)
|
|
#expect(reopened.exitNodeID == "current")
|
|
await model.shutdown()
|
|
}
|
|
|
|
private func fixture(online: Bool) throws -> (TailscaleModel, HostLibraryModel, HostGroup) {
|
|
var group = HostGroup(name: "Test Tailnet")
|
|
group.tailscale = TailscaleGroupConfiguration(
|
|
network: try TailscaleNetwork(controlURL: TailscaleNetwork.defaultControlURL, dnsSuffix: "test.ts.net"))
|
|
group.tailscale?.exitNodeID = "current"
|
|
let library = HostLibraryModel(groups: [group])
|
|
let store = InMemoryTailscaleLocalStore()
|
|
try store.save(
|
|
TailscaleLocalState(
|
|
groupID: group.id, profileID: UUID(),
|
|
peers: [
|
|
TailscalePeer(
|
|
stableID: "current", name: "Current", dnsName: "current.test.ts.net", online: false,
|
|
sshHostKeys: [], exitNodeOption: true),
|
|
TailscalePeer(
|
|
stableID: "candidate", name: "Candidate", dnsName: "candidate.test.ts.net", online: online,
|
|
sshHostKeys: [], exitNodeOption: true),
|
|
]))
|
|
let model = TailscaleModel(
|
|
library: library, store: store, factory: { _, _, _, _ in throw TailscaleConfigurationError.unavailable },
|
|
profileStorage: .disabled, hostname: { "test-device" }, hasAccess: { false })
|
|
model.start()
|
|
return (model, library, group)
|
|
}
|
|
}
|