test: OneDrive transport settings/locator unit tests + ReturnWatcher recordUncommended coverage
TransportSettingsTests: default airDrop, set/get round-trip, garbage stored value falls back to airDrop, oneDriveFolder store/reset, effectiveFolders for both transports, oneDriveFolderUnavailable's errorDescription contains the path. OneDriveLocatorTests: fake home tree under Library/CloudStorage — syncRoots returns only real OneDrive-* directories (ignores a same-named plain file and a GoogleDrive-* one), MMD-named root sorts first; no CloudStorage dir means empty roots and a nil defaultRedlineFolder; resolveOneDriveFolder prefers an existing stored override and falls back to the default when the stored path no longer exists. All against temp dirs, never the real home. ReturnWatcherTests: recordUncommended defaults to true and still records an unmarked PDF (existing AirDrop tests are unaffected); with it set false, an unmarked PDF is neither recorded nor returned by scanNow, and marking it up in place with a real PDFKit ink annotation then re-scanning does record it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import ShotdeckCore
|
||||
|
||||
@Test
|
||||
func syncRootsFindsOneDriveDirsMMDFirstIgnoresNonDirsAndOtherProviders() throws {
|
||||
let home = try makeFakeHome()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
let cloudStorage = home.appendingPathComponent("Library/CloudStorage", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: cloudStorage, withIntermediateDirectories: true)
|
||||
|
||||
try FileManager.default.createDirectory(
|
||||
at: cloudStorage.appendingPathComponent("OneDrive-Flowmaster", isDirectory: true),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
try FileManager.default.createDirectory(
|
||||
at: cloudStorage.appendingPathComponent("OneDrive-MMDGROUP", isDirectory: true),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
try FileManager.default.createDirectory(
|
||||
at: cloudStorage.appendingPathComponent("GoogleDrive-x", isDirectory: true),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
// A plain FILE (not a directory) named like a OneDrive root must be ignored.
|
||||
FileManager.default.createFile(
|
||||
atPath: cloudStorage.appendingPathComponent("OneDrive-notadir").path,
|
||||
contents: Data("not a directory".utf8)
|
||||
)
|
||||
|
||||
let roots = OneDriveLocator.syncRoots(home: home, fileManager: .default)
|
||||
|
||||
#expect(roots.map(\.lastPathComponent) == ["OneDrive-MMDGROUP", "OneDrive-Flowmaster"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func syncRootsEmptyAndDefaultFolderNilWithNoCloudStorageDirectory() throws {
|
||||
let home = try makeFakeHome()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
// No Library/CloudStorage created at all.
|
||||
|
||||
let roots = OneDriveLocator.syncRoots(home: home, fileManager: .default)
|
||||
#expect(roots.isEmpty)
|
||||
|
||||
let defaultFolder = OneDriveLocator.defaultRedlineFolder(home: home, fileManager: .default)
|
||||
#expect(defaultFolder == nil)
|
||||
}
|
||||
|
||||
@Test
|
||||
func defaultRedlineFolderIsFirstSyncRootPlusRedline() throws {
|
||||
let home = try makeFakeHome()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
let cloudStorage = home.appendingPathComponent("Library/CloudStorage", isDirectory: true)
|
||||
try FileManager.default.createDirectory(
|
||||
at: cloudStorage.appendingPathComponent("OneDrive-MMDGROUP", isDirectory: true),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
try FileManager.default.createDirectory(
|
||||
at: cloudStorage.appendingPathComponent("OneDrive-Flowmaster", isDirectory: true),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
|
||||
let defaultFolder = try #require(
|
||||
OneDriveLocator.defaultRedlineFolder(home: home, fileManager: .default)
|
||||
)
|
||||
// Derive "expected" from syncRoots() itself (already covered by its own dedicated
|
||||
// test) rather than hand-building the path string — FileManager's directory
|
||||
// enumeration can canonicalize /var -> /private/var and the two constructions
|
||||
// otherwise disagree on that even for a URL that already exists.
|
||||
let expectedRoot = try #require(OneDriveLocator.syncRoots(home: home, fileManager: .default).first)
|
||||
let expected = expectedRoot.appendingPathComponent("Redline", isDirectory: true)
|
||||
#expect(defaultFolder.path == expected.path)
|
||||
}
|
||||
|
||||
@Test
|
||||
func resolveOneDriveFolderPrefersAnExistingStoredOverride() throws {
|
||||
let home = try makeFakeHome()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
let cloudStorage = home.appendingPathComponent("Library/CloudStorage", isDirectory: true)
|
||||
try FileManager.default.createDirectory(
|
||||
at: cloudStorage.appendingPathComponent("OneDrive-MMDGROUP", isDirectory: true),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
|
||||
let suite = try makeTransportDefaultsSuite()
|
||||
defer { tearDownTransportSuite(suite) }
|
||||
let override = try makeTransportTemporaryDirectory(prefix: "shotdeck-onedrive-override")
|
||||
defer { try? FileManager.default.removeItem(at: override) }
|
||||
TransportSettings.setOneDriveFolder(override, defaults: suite.defaults)
|
||||
|
||||
let resolved = OneDriveLocator.resolveOneDriveFolder(
|
||||
defaults: suite.defaults, home: home, fileManager: .default
|
||||
)
|
||||
#expect(resolved?.path == override.path)
|
||||
}
|
||||
|
||||
@Test
|
||||
func resolveOneDriveFolderIgnoresAStoredPathThatNoLongerExists() throws {
|
||||
let home = try makeFakeHome()
|
||||
defer { try? FileManager.default.removeItem(at: home) }
|
||||
let cloudStorage = home.appendingPathComponent("Library/CloudStorage", isDirectory: true)
|
||||
try FileManager.default.createDirectory(
|
||||
at: cloudStorage.appendingPathComponent("OneDrive-MMDGROUP", isDirectory: true),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
|
||||
let suite = try makeTransportDefaultsSuite()
|
||||
defer { tearDownTransportSuite(suite) }
|
||||
let goneOverride = try makeTransportTemporaryDirectory(prefix: "shotdeck-onedrive-gone")
|
||||
TransportSettings.setOneDriveFolder(goneOverride, defaults: suite.defaults)
|
||||
try FileManager.default.removeItem(at: goneOverride)
|
||||
|
||||
let resolved = OneDriveLocator.resolveOneDriveFolder(
|
||||
defaults: suite.defaults, home: home, fileManager: .default
|
||||
)
|
||||
let expectedRoot = try #require(OneDriveLocator.syncRoots(home: home, fileManager: .default).first)
|
||||
let expected = expectedRoot.appendingPathComponent("Redline", isDirectory: true)
|
||||
#expect(resolved?.path == expected.path)
|
||||
}
|
||||
|
||||
private func makeFakeHome() throws -> URL {
|
||||
let home = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("shotdeck-fake-home-\(UUID().uuidString)", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: home, withIntermediateDirectories: true)
|
||||
return home
|
||||
}
|
||||
@@ -230,3 +230,55 @@ func w27_fsEventsCallbackFiresOnRealArrival() async throws {
|
||||
await watcher.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@Test("recordUncommended defaults to true: an unmarked PDF is still recorded (AirDrop behaviour unchanged)")
|
||||
func recordUncommendedDefaultTrueRecordsAnUnmarkedPDF() async throws {
|
||||
let (paths, cleanup) = try makeCasePaths()
|
||||
defer { try? FileManager.default.removeItem(at: cleanup) }
|
||||
|
||||
let ledger = try ReturnLedger(paths: paths)
|
||||
let watcher = ReturnWatcher(paths: paths, ledger: ledger)
|
||||
let pdfURL = paths.watchFolder.appendingPathComponent("Redline-20260905-090000.pdf")
|
||||
|
||||
try makePDF(at: pdfURL, pageCount: 1, creator: "Redline", annotations: [])
|
||||
|
||||
let found = try await watcher.scanNow()
|
||||
#expect(found.count == 1)
|
||||
#expect(found.first?.isCommented == false)
|
||||
|
||||
let all = try await ledger.all()
|
||||
#expect(all.count == 1)
|
||||
}
|
||||
|
||||
@Test("recordUncommended=false: an unmarked PDF is not recorded or returned; marking it up in place gets it recorded")
|
||||
func recordUncommendedFalseSkipsUnmarkedThenRecordsAfterInPlaceMarkup() async throws {
|
||||
let (paths, cleanup) = try makeCasePaths()
|
||||
defer { try? FileManager.default.removeItem(at: cleanup) }
|
||||
|
||||
let ledger = try ReturnLedger(paths: paths)
|
||||
let watcher = ReturnWatcher(paths: paths, ledger: ledger)
|
||||
await watcher.setRecordUncommented(false)
|
||||
let pdfURL = paths.watchFolder.appendingPathComponent("Redline-20260905-091500.pdf")
|
||||
|
||||
// OneDrive mode: the PDF is freshly written here (by "send"), unmarked so far.
|
||||
try makePDF(at: pdfURL, pageCount: 1, creator: "Redline", annotations: [])
|
||||
|
||||
let beforeMarkup = try await watcher.scanNow()
|
||||
#expect(beforeMarkup.isEmpty)
|
||||
let allBefore = try await ledger.all()
|
||||
#expect(allBefore.isEmpty)
|
||||
|
||||
// What the iPad does: mark it up in place, in the SAME folder, then save.
|
||||
let document = try #require(PDFDocument(url: pdfURL))
|
||||
let page = try #require(document.page(at: 0))
|
||||
page.addAnnotation(makeAnnotation(.ink, bounds: CGRect(x: 100, y: 100, width: 120, height: 50)))
|
||||
#expect(document.write(to: pdfURL))
|
||||
|
||||
let afterMarkup = try await watcher.scanNow()
|
||||
#expect(afterMarkup.count == 1)
|
||||
#expect(afterMarkup.first?.isCommented == true)
|
||||
|
||||
let commented = try await ledger.commented()
|
||||
#expect(commented.count == 1)
|
||||
#expect(commented.first?.fileURL.resolvingSymlinksInPath().path == pdfURL.resolvingSymlinksInPath().path)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user