native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242import Foundationimport Testing
@testable import Prowl
struct SymlinkPreservingFileWriterTests { private let fileManager = FileManager.default
private func makeTempDir() throws -> URL { let dir = fileManager.temporaryDirectory .appending(path: "symlink-writer-test-\(UUID().uuidString)", directoryHint: .isDirectory) try fileManager.createDirectory(at: dir, withIntermediateDirectories: true) return dir }
private func isSymlink(_ url: URL) -> Bool { (try? fileManager.destinationOfSymbolicLink(atPath: url.path(percentEncoded: false))) != nil }
@Test func writesPlainFileWithContent() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let url = dir.appending(path: "settings.json", directoryHint: .notDirectory) let payload = Data("{\"a\":1}".utf8)
try SymlinkPreservingFileWriter.write(payload, to: url)
#expect(try Data(contentsOf: url) == payload) #expect(!isSymlink(url)) }
@Test func writesOwnerOnlyPermissions() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let url = dir.appending(path: "settings.json", directoryHint: .notDirectory)
try SymlinkPreservingFileWriter.write(Data("{}".utf8), to: url)
let attributes = try fileManager.attributesOfItem(atPath: url.path(percentEncoded: false)) #expect(attributes[.posixPermissions] as? Int == 0o600) }
@Test func createsMissingParentDirectories() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let url = dir.appending(path: "nested/deep/settings.json", directoryHint: .notDirectory) let payload = Data("{}".utf8)
try SymlinkPreservingFileWriter.write(payload, to: url)
#expect(try Data(contentsOf: url) == payload) }
@Test func preservesAbsoluteSymlinkAndWritesThrough() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let target = dir.appending(path: "target.json", directoryHint: .notDirectory) let link = dir.appending(path: "settings.json", directoryHint: .notDirectory) try Data("old".utf8).write(to: target) try fileManager.createSymbolicLink(at: link, withDestinationURL: target) let payload = Data("new".utf8)
try SymlinkPreservingFileWriter.write(payload, to: link)
#expect(isSymlink(link)) #expect(try Data(contentsOf: target) == payload) #expect(try Data(contentsOf: link) == payload) }
@Test func preservesRelativeSymlinkAndWritesThrough() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let prowlDir = dir.appending(path: ".prowl", directoryHint: .isDirectory) let dotfilesDir = dir.appending(path: "dotfiles", directoryHint: .isDirectory) try fileManager.createDirectory(at: prowlDir, withIntermediateDirectories: true) try fileManager.createDirectory(at: dotfilesDir, withIntermediateDirectories: true) let target = dotfilesDir.appending(path: "settings.json", directoryHint: .notDirectory) try Data("old".utf8).write(to: target) let link = prowlDir.appending(path: "settings.json", directoryHint: .notDirectory) try fileManager.createSymbolicLink( atPath: link.path(percentEncoded: false), withDestinationPath: "../dotfiles/settings.json" ) let payload = Data("new".utf8)
try SymlinkPreservingFileWriter.write(payload, to: link)
#expect(isSymlink(link)) #expect(try Data(contentsOf: target) == payload) }
@Test func createsTargetForDanglingSymlinkWhenParentExists() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let prowlDir = dir.appending(path: ".prowl", directoryHint: .isDirectory) let dotfilesDir = dir.appending(path: "dotfiles", directoryHint: .isDirectory) try fileManager.createDirectory(at: prowlDir, withIntermediateDirectories: true) try fileManager.createDirectory(at: dotfilesDir, withIntermediateDirectories: true) let link = prowlDir.appending(path: "settings.json", directoryHint: .notDirectory) try fileManager.createSymbolicLink( atPath: link.path(percentEncoded: false), withDestinationPath: "../dotfiles/settings.json" ) let payload = Data("new".utf8)
try SymlinkPreservingFileWriter.write(payload, to: link)
#expect(isSymlink(link)) let target = dotfilesDir.appending(path: "settings.json", directoryHint: .notDirectory) #expect(try Data(contentsOf: target) == payload) }
@Test func failsAndCreatesNoPhantomDirectoryForDanglingIntoMissingDir() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let prowlDir = dir.appending(path: ".prowl", directoryHint: .isDirectory) try fileManager.createDirectory(at: prowlDir, withIntermediateDirectories: true) let link = prowlDir.appending(path: "settings.json", directoryHint: .notDirectory) try fileManager.createSymbolicLink( atPath: link.path(percentEncoded: false), withDestinationPath: "../missing/settings.json" )
#expect(throws: (any Error).self) { try SymlinkPreservingFileWriter.write(Data("new".utf8), to: link) } let missingDir = dir.appending(path: "missing", directoryHint: .isDirectory) #expect(!fileManager.fileExists(atPath: missingDir.path(percentEncoded: false))) #expect(isSymlink(link)) }
@Test func followsSymlinkChainToFinalTarget() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let real = dir.appending(path: "real.json", directoryHint: .notDirectory) let mid = dir.appending(path: "mid.json", directoryHint: .notDirectory) let link = dir.appending(path: "link.json", directoryHint: .notDirectory) try Data("old".utf8).write(to: real) try fileManager.createSymbolicLink(atPath: mid.path(percentEncoded: false), withDestinationPath: "real.json") try fileManager.createSymbolicLink(atPath: link.path(percentEncoded: false), withDestinationPath: "mid.json") let payload = Data("new".utf8)
try SymlinkPreservingFileWriter.write(payload, to: link)
#expect(isSymlink(link)) #expect(isSymlink(mid)) #expect(try Data(contentsOf: real) == payload) }
@Test func throwsOnSymlinkCycle() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let linkA = dir.appending(path: "a.json", directoryHint: .notDirectory) let linkB = dir.appending(path: "b.json", directoryHint: .notDirectory) try fileManager.createSymbolicLink(atPath: linkA.path(percentEncoded: false), withDestinationPath: "b.json") try fileManager.createSymbolicLink(atPath: linkB.path(percentEncoded: false), withDestinationPath: "a.json")
#expect(throws: SymlinkPreservingFileWriterError.self) { try SymlinkPreservingFileWriter.write(Data("x".utf8), to: linkA) } #expect(isSymlink(linkA)) #expect(isSymlink(linkB)) }
@Test func throwsOnOverlyDeepSymlinkChain() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } // A 40-link chain (link0 -> link1 -> ... -> link40) exceeds the kernel's // symlink-resolution limit, so the loader could never read it back. for hop in 0..<40 { let here = dir.appending(path: "link\(hop).json", directoryHint: .notDirectory) try fileManager.createSymbolicLink( atPath: here.path(percentEncoded: false), withDestinationPath: "link\(hop + 1).json" ) } let head = dir.appending(path: "link0.json", directoryHint: .notDirectory)
#expect(throws: SymlinkPreservingFileWriterError.self) { try SymlinkPreservingFileWriter.write(Data("x".utf8), to: head) } #expect(isSymlink(head)) }
@Test func writeOverwritesExistingRealFile() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let url = dir.appending(path: "settings.json", directoryHint: .notDirectory) try Data("old".utf8).write(to: url) let payload = Data("new".utf8)
try SymlinkPreservingFileWriter.write(payload, to: url)
#expect(try Data(contentsOf: url) == payload) #expect(!isSymlink(url)) }
@Test func writeLeavesNoTemporaryFilesBehind() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let url = dir.appending(path: "settings.json", directoryHint: .notDirectory)
try SymlinkPreservingFileWriter.write(Data("{}".utf8), to: url)
let contents = try fileManager.contentsOfDirectory(atPath: dir.path(percentEncoded: false)) #expect(contents == ["settings.json"]) }
@Test func overwritingLegacyPermissionsEndsOwnerOnly() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let url = dir.appending(path: "settings.json", directoryHint: .notDirectory) try Data("old".utf8).write(to: url) try fileManager.setAttributes( [.posixPermissions: 0o644], ofItemAtPath: url.path(percentEncoded: false) )
try SymlinkPreservingFileWriter.write(Data("new".utf8), to: url)
let attributes = try fileManager.attributesOfItem(atPath: url.path(percentEncoded: false)) #expect(attributes[.posixPermissions] as? Int == 0o600) }
@Test func restrictToOwnerOnlyMigratesLegacyFileThroughSymlink() throws { let dir = try makeTempDir() defer { try? fileManager.removeItem(at: dir) } let target = dir.appending(path: "target.json", directoryHint: .notDirectory) let link = dir.appending(path: "settings.json", directoryHint: .notDirectory) try Data("{}".utf8).write(to: target) try fileManager.setAttributes( [.posixPermissions: 0o644], ofItemAtPath: target.path(percentEncoded: false) ) try fileManager.createSymbolicLink(at: link, withDestinationURL: target)
SymlinkPreservingFileWriter.restrictToOwnerOnly(link)
let attributes = try fileManager.attributesOfItem(atPath: target.path(percentEncoded: false)) #expect(attributes[.posixPermissions] as? Int == 0o600) #expect(isSymlink(link)) }}