Redline: OneDrive folder transport as alternative to AirDrop (MMDB-2631) #23

Merged
kua-agent merged 20 commits from feat/redline-onedrive-transport-20260905 into main 2026-09-05 06:17:29 +00:00
2 changed files with 139 additions and 0 deletions
Showing only changes of commit 70231574c9 - Show all commits
@@ -282,3 +282,110 @@ func recordUncommentedFalseSkipsUnmarkedThenRecordsAfterInPlaceMarkup() async th
#expect(commented.count == 1) #expect(commented.count == 1)
#expect(commented.first?.fileURL.resolvingSymlinksInPath().path == pdfURL.resolvingSymlinksInPath().path) #expect(commented.first?.fileURL.resolvingSymlinksInPath().path == pdfURL.resolvingSymlinksInPath().path)
} }
// MARK: - Launch-paths BLOCKER regression (adversarial review, 20260905)
//
// The bug: AppDelegate.makeLaunchModel() built `paths` via the AirDrop-only
// FolderSettings.resolvedAppSupportPaths(), so ReturnWatcher's internal watchFolder
// (seeded from paths.watchFolder in its own init) was the AirDrop folder even when
// OneDrive was the persisted transport, and bootstrap() never reconciled it before
// starting. Net effect: PDFs went to OneDrive but FSEvents kept watching the stale
// AirDrop folder for the whole session marked-up returns were never detected.
// The fix: launch paths now come from TransportSettings.resolvedAppSupportPaths()
// (transport-aware), and AppModel.bootstrap() unconditionally reconciles the watcher's
// folder via updateWatchFolder() before it starts. These two tests characterize the
// bug (still reproducible via the old AirDrop-only construction) and prove the fix
// (the real launch-construction path, end to end).
@Test("Launch regression (fix): OneDrive persisted -> transport-aware launch paths -> bootstrap-style reconcile -> a marked PDF is detected")
func launchStyleConstructionWithOneDriveTransportDetectsAMarkedReturn() async throws {
let suite = try makeTransportDefaultsSuite()
defer { tearDownTransportSuite(suite) }
let oneDriveFolder = try makeTransportTemporaryDirectory(prefix: "shotdeck-launch-onedrive")
defer { try? FileManager.default.removeItem(at: oneDriveFolder) }
let appSupportRoot = try makeTransportTemporaryDirectory(prefix: "shotdeck-launch-approot")
defer { try? FileManager.default.removeItem(at: appSupportRoot) }
TransportSettings.setTransport(.oneDrive, defaults: suite.defaults)
TransportSettings.setOneDriveFolder(oneDriveFolder, defaults: suite.defaults)
// Exactly what AppDelegate.makeLaunchModel() now does: build launch paths from the
// transport-aware resolver the fix, NOT FolderSettings.resolvedAppSupportPaths(),
// which is AirDrop-only and is the root cause the next test characterizes.
let paths = try TransportSettings.resolvedAppSupportPaths(
root: appSupportRoot, defaults: suite.defaults, fileManager: .default
)
#expect(paths.outbox.path == oneDriveFolder.path)
#expect(paths.watchFolder.path == oneDriveFolder.path)
let ledger = try ReturnLedger(paths: paths)
let watcher = ReturnWatcher(paths: paths, ledger: ledger)
// What AppModel.bootstrap() now does, unconditionally, before watcher.start():
await watcher.setRecordUncommented(false) // transport == .oneDrive
try await watcher.updateWatchFolder(paths.watchFolder)
let pdfURL = oneDriveFolder.appendingPathComponent("Redline-20260905-100000.pdf")
try makePDF(
at: pdfURL, pageCount: 1, creator: "Redline",
annotations: [(page: 0, annotation: makeAnnotation(
.ink, bounds: CGRect(x: 100, y: 100, width: 120, height: 50)
))]
)
let found = try await watcher.scanNow()
#expect(found.contains(where: {
$0.fileURL.resolvingSymlinksInPath().path == pdfURL.resolvingSymlinksInPath().path && $0.isCommented
}))
let commented = try await ledger.commented()
#expect(commented.contains(where: {
$0.fileURL.resolvingSymlinksInPath().path == pdfURL.resolvingSymlinksInPath().path
}))
}
@Test("Launch regression (characterizes the bug): AirDrop-only launch paths with no reconcile miss an OneDrive-mode return")
func airDropOnlyLaunchPathsWithoutReconcileMissesAMarkedOneDriveReturn() async throws {
let suite = try makeTransportDefaultsSuite()
defer { tearDownTransportSuite(suite) }
let oneDriveFolder = try makeTransportTemporaryDirectory(prefix: "shotdeck-buggy-onedrive")
defer { try? FileManager.default.removeItem(at: oneDriveFolder) }
// A configured AirDrop watch-folder override, isolated to a temp dir NOT the real
// ~/Downloads, which may already hold real marked-up Redline PDFs from actual use
// and would make this test's "found.isEmpty" assertion depend on the state of
// Ben's real Downloads folder instead of the isolated fixture under test.
let staleAirDropFolder = try makeTransportTemporaryDirectory(prefix: "shotdeck-buggy-airdrop-stale")
defer { try? FileManager.default.removeItem(at: staleAirDropFolder) }
let appSupportRoot = try makeTransportTemporaryDirectory(prefix: "shotdeck-buggy-approot")
defer { try? FileManager.default.removeItem(at: appSupportRoot) }
TransportSettings.setTransport(.oneDrive, defaults: suite.defaults)
TransportSettings.setOneDriveFolder(oneDriveFolder, defaults: suite.defaults)
FolderSettings.setWatchFolder(staleAirDropFolder, defaults: suite.defaults)
// The BUG's exact construction: FolderSettings.resolvedAppSupportPaths() ignores
// the persisted transport entirely and always resolves the AirDrop folders.
let buggyPaths = try FolderSettings.resolvedAppSupportPaths(root: appSupportRoot, defaults: suite.defaults)
#expect(buggyPaths.watchFolder.path == staleAirDropFolder.path)
#expect(buggyPaths.watchFolder.path != oneDriveFolder.path)
let ledger = try ReturnLedger(paths: buggyPaths)
let watcher = ReturnWatcher(paths: buggyPaths, ledger: ledger)
// The old bootstrap(): recordUncommented was set, but there was NO
// updateWatchFolder() call before start() to reconcile the folder.
await watcher.setRecordUncommented(false)
let pdfURL = oneDriveFolder.appendingPathComponent("Redline-20260905-100100.pdf")
try makePDF(
at: pdfURL, pageCount: 1, creator: "Redline",
annotations: [(page: 0, annotation: makeAnnotation(
.ink, bounds: CGRect(x: 100, y: 100, width: 120, height: 50)
))]
)
// The watcher is still pointed at the stale (configured-AirDrop) watch folder, so
// scanning it NOT the OneDrive folder the PDF actually landed in finds nothing.
// This is the exact BLOCKER the fix above closes.
let found = try await watcher.scanNow()
#expect(found.isEmpty)
}
@@ -90,6 +90,38 @@ func oneDriveFolderUnavailableErrorDescriptionContainsThePath() throws {
#expect(description.contains(path)) #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")))
}
struct TransportDefaultsSuite { struct TransportDefaultsSuite {
let name: String let name: String
let defaults: UserDefaults let defaults: UserDefaults