92 lines
4.6 KiB
Swift
92 lines
4.6 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
import Testing
|
|
|
|
@testable import Termind
|
|
|
|
@Suite @MainActor
|
|
struct VaultEditorProjectionTests {
|
|
@Test func indexedJumpCandidatesMatchAuthoritativeRoutes() {
|
|
let destination = Host(name: "Destination", host: "destination")
|
|
var hosts = (0..<10).map { Host(name: "Hop \($0)", host: "hop\($0)") }
|
|
for index in 1..<hosts.count { hosts[index].jumpHostID = hosts[index - 1].id }
|
|
var cycle = Host(name: "Cycle", host: "cycle")
|
|
cycle.jumpHostID = cycle.id
|
|
var returnsToDestination = Host(name: "Return", host: "return")
|
|
returnsToDestination.jumpHostID = destination.id
|
|
var missing = Host(name: "Missing", host: "missing")
|
|
missing.jumpHostID = UUID()
|
|
var disabled = Host(name: "Disabled", host: "disabled")
|
|
disabled.sshEnabled = false
|
|
var tailscale = Host(name: "Tailscale", host: "tailscale")
|
|
tailscale.tailscale = .init(groupID: UUID(), stableNodeID: "node")
|
|
hosts += [destination, cycle, returnsToDestination, missing, disabled, tailscale]
|
|
let library = HostLibraryModel(hosts: hosts)
|
|
let sections = HostEditorJumpHostProjection.sections(
|
|
for: .init(
|
|
destinationID: destination.id, sshEnabled: true, isTailscaleManaged: false,
|
|
hosts: hosts, groups: []))
|
|
let projected = Set(sections.flatMap(\.hosts).map(\.id))
|
|
for candidate in hosts {
|
|
var proposed = destination
|
|
proposed.jumpHostID = candidate.id
|
|
#expect(projected.contains(candidate.id) == (library.validatedRoute(for: proposed) != nil))
|
|
}
|
|
#expect(projected.contains(hosts[6].id))
|
|
#expect(!projected.contains(hosts[7].id))
|
|
for (sshEnabled, managed) in [(false, false), (true, true)] {
|
|
#expect(
|
|
HostEditorJumpHostProjection.sections(
|
|
for: .init(
|
|
destinationID: destination.id, sshEnabled: sshEnabled, isTailscaleManaged: managed,
|
|
hosts: hosts, groups: [])
|
|
).isEmpty)
|
|
}
|
|
}
|
|
|
|
@Test func jumpSectionsKeepHierarchyAndManualOrder() {
|
|
let first = HostGroup(name: "First", sortIndex: 0)
|
|
let last = HostGroup(name: "Last", sortIndex: 1)
|
|
let child = HostGroup(name: "Child", parentID: first.id)
|
|
var later = Host(name: "Later", host: "later", groupID: first.id)
|
|
later.sortIndex = 2
|
|
var earlier = Host(name: "Earlier", host: "earlier", groupID: first.id)
|
|
earlier.sortIndex = 1
|
|
let nested = Host(name: "Nested", host: "nested", groupID: child.id)
|
|
let final = Host(name: "Final", host: "final", groupID: last.id)
|
|
let ungrouped = Host(name: "Ungrouped", host: "ungrouped")
|
|
let sections = HostEditorJumpHostProjection.sections(
|
|
for: .init(
|
|
destinationID: UUID(), sshEnabled: true, isTailscaleManaged: false,
|
|
hosts: [later, final, nested, earlier, ungrouped], groups: [last, child, first]))
|
|
#expect(sections.map(\.id) == [.group(first.id), .group(child.id), .group(last.id), .ungrouped])
|
|
#expect(sections.first?.hosts.map(\.id) == [earlier.id, later.id])
|
|
#expect(sections[1].title == "First / Child")
|
|
}
|
|
|
|
@Test func passwordRevisionRefreshesSameReferenceWritesButNotMetadataEdits() throws {
|
|
let library = HostLibraryModel()
|
|
let credentials = CredentialLibraryModel(library: library)
|
|
let identity = Identity(name: "Test", username: "user")
|
|
try library.upsert(identity, passwordUpdate: .replace("first"))
|
|
let saved = try #require(library.identity(identity.id))
|
|
let initialRevision = credentials.passwordAvailabilityRevision
|
|
try library.upsert(saved, passwordUpdate: .replace("second"))
|
|
#expect(library.identity(identity.id)?.credentialRef == saved.credentialRef)
|
|
#expect(credentials.passwordAvailabilityRevision > initialRevision)
|
|
let replacementRevision = credentials.passwordAvailabilityRevision
|
|
var renamed = saved
|
|
renamed.name = "Renamed"
|
|
try library.upsert(renamed, passwordUpdate: .keep)
|
|
#expect(credentials.passwordAvailabilityRevision == replacementRevision)
|
|
#expect(credentials.passwordAvailability(for: renamed) == .available)
|
|
try library.upsert(renamed, passwordUpdate: .remove)
|
|
#expect(credentials.passwordAvailabilityRevision > replacementRevision)
|
|
#expect(credentials.passwordAvailability(for: saved) == .missing)
|
|
}
|
|
}
|