isWritableDirectory (permissions bits) is not enough: a OneDrive Files-On-Demand directory whose provider domain is signed out can report as existing and POSIX-writable while an actual write fails. probeWritable writes a small ".redline-probe-<uuid>" file into the folder via AtomicFile.write (open+write+fsync+rename+directory-fsync), then removes it; any failure at write, fsync, or removal means false. Three unit tests: an ordinary writable directory (true, and no probe file left behind), a chmod 500 directory (false; permissions restored in teardown), and a plain file path (false). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp
185 lines
7.4 KiB
Swift
185 lines
7.4 KiB
Swift
import Foundation
|
|
import Testing
|
|
import ShotdeckCore
|
|
|
|
@Test
|
|
func transportDefaultsToAirDropWhenUnset() throws {
|
|
let suite = try makeTransportDefaultsSuite()
|
|
defer { tearDownTransportSuite(suite) }
|
|
|
|
#expect(TransportSettings.transport(defaults: suite.defaults) == .airDrop)
|
|
}
|
|
|
|
@Test
|
|
func setTransportRoundTrips() throws {
|
|
let suite = try makeTransportDefaultsSuite()
|
|
defer { tearDownTransportSuite(suite) }
|
|
|
|
TransportSettings.setTransport(.oneDrive, defaults: suite.defaults)
|
|
#expect(TransportSettings.transport(defaults: suite.defaults) == .oneDrive)
|
|
|
|
TransportSettings.setTransport(.airDrop, defaults: suite.defaults)
|
|
#expect(TransportSettings.transport(defaults: suite.defaults) == .airDrop)
|
|
}
|
|
|
|
@Test
|
|
func garbageStoredTransportFallsBackToAirDrop() throws {
|
|
let suite = try makeTransportDefaultsSuite()
|
|
defer { tearDownTransportSuite(suite) }
|
|
|
|
suite.defaults.set("not-a-real-transport", forKey: TransportSettings.transportDefaultsKey)
|
|
#expect(TransportSettings.transport(defaults: suite.defaults) == .airDrop)
|
|
}
|
|
|
|
@Test
|
|
func oneDriveFolderStoreAndReset() throws {
|
|
let suite = try makeTransportDefaultsSuite()
|
|
defer { tearDownTransportSuite(suite) }
|
|
let folder = try makeTransportTemporaryDirectory(prefix: "shotdeck-onedrive-folder")
|
|
defer { try? FileManager.default.removeItem(at: folder) }
|
|
|
|
#expect(TransportSettings.storedOneDriveFolderPath(defaults: suite.defaults) == nil)
|
|
|
|
TransportSettings.setOneDriveFolder(folder, defaults: suite.defaults)
|
|
#expect(TransportSettings.storedOneDriveFolderPath(defaults: suite.defaults) == folder.path)
|
|
|
|
TransportSettings.resetOneDriveFolder(defaults: suite.defaults)
|
|
#expect(TransportSettings.storedOneDriveFolderPath(defaults: suite.defaults) == nil)
|
|
}
|
|
|
|
@Test
|
|
func effectiveFoldersForAirDropMatchesFolderSettings() throws {
|
|
let suite = try makeTransportDefaultsSuite()
|
|
defer { tearDownTransportSuite(suite) }
|
|
let outbox = try makeTransportTemporaryDirectory(prefix: "shotdeck-effective-outbox")
|
|
defer { try? FileManager.default.removeItem(at: outbox) }
|
|
FolderSettings.setOutbox(outbox, defaults: suite.defaults)
|
|
|
|
let effective = TransportSettings.effectiveFolders(defaults: suite.defaults)
|
|
let expected = FolderSettings.resolve(defaults: suite.defaults)
|
|
|
|
#expect(effective.transport == .airDrop)
|
|
#expect(effective.outbox.path == expected.outbox.path)
|
|
#expect(effective.watch.path == expected.watch.path)
|
|
}
|
|
|
|
@Test
|
|
func effectiveFoldersForOneDriveWithAResolvableFolderUsesItForBoth() throws {
|
|
let suite = try makeTransportDefaultsSuite()
|
|
defer { tearDownTransportSuite(suite) }
|
|
let folder = try makeTransportTemporaryDirectory(prefix: "shotdeck-effective-onedrive")
|
|
defer { try? FileManager.default.removeItem(at: folder) }
|
|
|
|
TransportSettings.setTransport(.oneDrive, defaults: suite.defaults)
|
|
TransportSettings.setOneDriveFolder(folder, defaults: suite.defaults)
|
|
|
|
let effective = TransportSettings.effectiveFolders(defaults: suite.defaults)
|
|
|
|
#expect(effective.transport == .oneDrive)
|
|
#expect(effective.outbox.path == folder.path)
|
|
#expect(effective.watch.path == folder.path)
|
|
#expect(effective.outbox.path == effective.watch.path)
|
|
}
|
|
|
|
@Test
|
|
func oneDriveFolderUnavailableErrorDescriptionContainsThePath() throws {
|
|
let path = "/Users/example/Library/CloudStorage/OneDrive-Example/Redline"
|
|
let error = ShotdeckError.oneDriveFolderUnavailable(path: path)
|
|
let description = try #require(error.errorDescription)
|
|
#expect(!description.isEmpty)
|
|
#expect(description.contains(path))
|
|
}
|
|
|
|
@Test
|
|
func isWritableDirectoryTrueForAnOrdinaryWritableDirectory() throws {
|
|
let dir = try makeTransportTemporaryDirectory(prefix: "shotdeck-writable")
|
|
defer { try? FileManager.default.removeItem(at: dir) }
|
|
#expect(OneDriveLocator.isWritableDirectory(at: dir))
|
|
}
|
|
|
|
@Test
|
|
func isWritableDirectoryFalseForAnExistingButUnwritableDirectory() throws {
|
|
let dir = try makeTransportTemporaryDirectory(prefix: "shotdeck-unwritable")
|
|
defer {
|
|
// Restore perms BEFORE removal — an unwritable dir can't otherwise be cleaned up.
|
|
try? FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: dir.path)
|
|
try? FileManager.default.removeItem(at: dir)
|
|
}
|
|
#expect(OneDriveLocator.isWritableDirectory(at: dir)) // sanity check before chmod
|
|
|
|
try FileManager.default.setAttributes([.posixPermissions: 0o500], ofItemAtPath: dir.path)
|
|
#expect(!OneDriveLocator.isWritableDirectory(at: dir))
|
|
}
|
|
|
|
@Test
|
|
func isWritableDirectoryFalseForAPlainFileAndForANonexistentPath() throws {
|
|
let dir = try makeTransportTemporaryDirectory(prefix: "shotdeck-writable-check-parent")
|
|
defer { try? FileManager.default.removeItem(at: dir) }
|
|
let filePath = dir.appendingPathComponent("plain-file.txt")
|
|
FileManager.default.createFile(atPath: filePath.path, contents: Data("x".utf8))
|
|
|
|
#expect(!OneDriveLocator.isWritableDirectory(at: filePath))
|
|
#expect(!OneDriveLocator.isWritableDirectory(at: dir.appendingPathComponent("does-not-exist")))
|
|
}
|
|
|
|
@Test
|
|
func probeWritableTrueForAnOrdinaryWritableDirectoryAndLeavesNoProbeFileBehind() throws {
|
|
let dir = try makeTransportTemporaryDirectory(prefix: "shotdeck-probe-writable")
|
|
defer { try? FileManager.default.removeItem(at: dir) }
|
|
|
|
#expect(OneDriveLocator.probeWritable(at: dir))
|
|
|
|
let leftovers = try FileManager.default.contentsOfDirectory(atPath: dir.path)
|
|
#expect(leftovers.isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func probeWritableFalseForAChmod500Directory() throws {
|
|
// The File Provider edge case this probe exists for: isWritableDirectory can be
|
|
// true (as verified by the isWritableDirectory tests above) while an actual write
|
|
// still fails. A chmod 500 directory reproduces that "looks writable, isn't"
|
|
// shape closely enough to prove the probe itself does a real write, not just
|
|
// another permissions-bit check.
|
|
let dir = try makeTransportTemporaryDirectory(prefix: "shotdeck-probe-unwritable")
|
|
defer {
|
|
try? FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: dir.path)
|
|
try? FileManager.default.removeItem(at: dir)
|
|
}
|
|
try FileManager.default.setAttributes([.posixPermissions: 0o500], ofItemAtPath: dir.path)
|
|
|
|
#expect(!OneDriveLocator.probeWritable(at: dir))
|
|
}
|
|
|
|
@Test
|
|
func probeWritableFalseForAPlainFilePath() throws {
|
|
let dir = try makeTransportTemporaryDirectory(prefix: "shotdeck-probe-file-parent")
|
|
defer { try? FileManager.default.removeItem(at: dir) }
|
|
let filePath = dir.appendingPathComponent("plain-file.txt")
|
|
FileManager.default.createFile(atPath: filePath.path, contents: Data("x".utf8))
|
|
|
|
#expect(!OneDriveLocator.probeWritable(at: filePath))
|
|
}
|
|
|
|
struct TransportDefaultsSuite {
|
|
let name: String
|
|
let defaults: UserDefaults
|
|
}
|
|
|
|
func makeTransportDefaultsSuite() throws -> TransportDefaultsSuite {
|
|
let name = "shotdeck-transport-test-\(UUID().uuidString)"
|
|
let defaults = try #require(UserDefaults(suiteName: name))
|
|
defaults.removePersistentDomain(forName: name)
|
|
return TransportDefaultsSuite(name: name, defaults: defaults)
|
|
}
|
|
|
|
func tearDownTransportSuite(_ suite: TransportDefaultsSuite) {
|
|
suite.defaults.removePersistentDomain(forName: suite.name)
|
|
}
|
|
|
|
func makeTransportTemporaryDirectory(prefix: String) throws -> URL {
|
|
let url = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("\(prefix)-\(UUID().uuidString)", isDirectory: true)
|
|
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
|
|
return url
|
|
}
|