import Foundation import Testing import ShotdeckCore @Test func resolveWithoutStoredKeysReturnsDesktopAndDownloads() throws { let suite = try makeDefaultsSuite() defer { tearDown(suite) } let resolved = FolderSettings.resolve(defaults: suite.defaults) let expectedOutbox = try #require( FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first ) let expectedWatch = try #require( FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first ) #expect(resolved.outbox.path == expectedOutbox.path) #expect(resolved.watch.path == expectedWatch.path) } @Test func setOutboxToAnExistingDirectoryIsReturnedByResolve() throws { let suite = try makeDefaultsSuite() defer { tearDown(suite) } let outbox = try makeTemporaryDirectory(prefix: "shotdeck-outbox-set") defer { try? FileManager.default.removeItem(at: outbox) } FolderSettings.setOutbox(outbox, defaults: suite.defaults) let resolved = FolderSettings.resolve(defaults: suite.defaults) let expectedWatch = try #require( FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first ) #expect(resolved.outbox.path == outbox.path) #expect(resolved.watch.path == expectedWatch.path) #expect(FolderSettings.storedOutboxPath(defaults: suite.defaults) == outbox.path) } @Test func resolveFallsBackWhenTheStoredOutboxDirectoryIsGone() throws { let suite = try makeDefaultsSuite() defer { tearDown(suite) } let outbox = try makeTemporaryDirectory(prefix: "shotdeck-outbox-gone") FolderSettings.setOutbox(outbox, defaults: suite.defaults) try FileManager.default.removeItem(at: outbox) let resolved = FolderSettings.resolve(defaults: suite.defaults) let expectedOutbox = try #require( FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first ) #expect(resolved.outbox.path == expectedOutbox.path) } @Test func setWatchFolderMirrorsOutboxOverrideAndFallbackIndependently() throws { let suite = try makeDefaultsSuite() defer { tearDown(suite) } let watch = try makeTemporaryDirectory(prefix: "shotdeck-watch-set") let expectedOutbox = try #require( FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first ) FolderSettings.setWatchFolder(watch, defaults: suite.defaults) let withOverride = FolderSettings.resolve(defaults: suite.defaults) #expect(withOverride.watch.path == watch.path) #expect(withOverride.outbox.path == expectedOutbox.path) try FileManager.default.removeItem(at: watch) let afterDelete = FolderSettings.resolve(defaults: suite.defaults) let expectedWatch = try #require( FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first ) #expect(afterDelete.watch.path == expectedWatch.path) #expect(afterDelete.outbox.path == expectedOutbox.path) } @Test func resetOutboxClearsTheOverride() throws { let suite = try makeDefaultsSuite() defer { tearDown(suite) } let outbox = try makeTemporaryDirectory(prefix: "shotdeck-outbox-reset") defer { try? FileManager.default.removeItem(at: outbox) } FolderSettings.setOutbox(outbox, defaults: suite.defaults) FolderSettings.resetOutbox(defaults: suite.defaults) let resolved = FolderSettings.resolve(defaults: suite.defaults) let expectedOutbox = try #require( FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first ) #expect(resolved.outbox.path == expectedOutbox.path) #expect(FolderSettings.storedOutboxPath(defaults: suite.defaults) == nil) } @Test func resolvedAppSupportPathsUsesTheOutboxOverrideAndCreatesSpoolArchive() throws { let suite = try makeDefaultsSuite() defer { tearDown(suite) } let root = try makeTemporaryDirectory(prefix: "shotdeck-paths-root") let outbox = try makeTemporaryDirectory(prefix: "shotdeck-paths-outbox") defer { try? FileManager.default.removeItem(at: root) try? FileManager.default.removeItem(at: outbox) } FolderSettings.setOutbox(outbox, defaults: suite.defaults) let paths = try FolderSettings.resolvedAppSupportPaths(root: root, defaults: suite.defaults) #expect(paths.outbox.path == outbox.path) #expect(directoryExists(paths.spool)) #expect(directoryExists(paths.archive)) #expect(paths.spool.deletingLastPathComponent().path == root.path) #expect(paths.archive.deletingLastPathComponent().path == root.path) } @Test func userDefaultsSuitesDoNotLeakFolderOverrides() throws { let suiteA = try makeDefaultsSuite() let suiteB = try makeDefaultsSuite() defer { tearDown(suiteA) tearDown(suiteB) } let outbox = try makeTemporaryDirectory(prefix: "shotdeck-suite-a-outbox") defer { try? FileManager.default.removeItem(at: outbox) } FolderSettings.setOutbox(outbox, defaults: suiteA.defaults) let resolvedA = FolderSettings.resolve(defaults: suiteA.defaults) let resolvedB = FolderSettings.resolve(defaults: suiteB.defaults) let expectedOutbox = try #require( FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first ) #expect(resolvedA.outbox.path == outbox.path) #expect(resolvedB.outbox.path == expectedOutbox.path) } private struct DefaultsSuite { let name: String let defaults: UserDefaults } private func makeDefaultsSuite() throws -> DefaultsSuite { let name = "shotdeck-test-\(UUID().uuidString)" let defaults = try #require(UserDefaults(suiteName: name)) defaults.removePersistentDomain(forName: name) return DefaultsSuite(name: name, defaults: defaults) } private func tearDown(_ suite: DefaultsSuite) { suite.defaults.removePersistentDomain(forName: suite.name) } private func makeTemporaryDirectory(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 } private func directoryExists(_ url: URL) -> Bool { var isDirectory: ObjCBool = false let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) return exists && isDirectory.boolValue }