fix: MINOR — ONEDRIVE-SELFTEST resolves the real sync root at runtime; adds relaunch + rapid-toggle sub-steps
realOneDriveSyncRoot was a hardcoded /Users/benjaminhippler/... literal. Now resolved via OneDriveLocator.syncRoots().first at runtime (MMD-named root still preferred, matching production); when no OneDrive sync root exists at all, prints "ONEDRIVE-SELFTEST SKIP no OneDrive sync root" and exits non-zero — never a false PASS. Adds two sub-steps to the same phase, both required by the BLOCKER fix's review: (1) rapid transport toggling (chooseTransport(.airDrop) immediately followed by chooseTransport(.oneDrive)) must still end with the watcher watching the OneDrive folder — proves the generation-guarded reconcile in SettingsView.swift really lets the last choice win. (2) relaunch simulation — OneDrive still persisted from sub-step 1, a FRESH model built via the exact same AppDelegate.makeLaunchModel() function real launch uses (now internal + an appSupportRoot override for this purpose, temp-rooted so this never touches the real ~/Library/Application Support/Shotdeck), bootstrapped, then a PDF marked up in place — the relaunched watcher must report it. This is the exact BLOCKER scenario the review flagged, proven end to end. Factored the ink-annotation and unmarked-PDF-writing code into addInkMark(to:)/writeUnmarkedRedlinePDF(to:) so both new sub-steps and the original markup check share it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp
This commit is contained in:
@@ -223,7 +223,7 @@ enum PickerSelfTest {
|
||||
sendTruthFail("seeded session was empty")
|
||||
}
|
||||
|
||||
let pending = try await model.composePDFForSend()
|
||||
let pending = try await model.composePDFForSend(outbox: model.outboxURL)
|
||||
guard fm.fileExists(atPath: pending.fileURL.path) else {
|
||||
sendTruthFail("PDF was not written")
|
||||
}
|
||||
@@ -476,18 +476,13 @@ enum PickerSelfTest {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
/// Real sync root this Mac has; the phase proves the transport against the actual
|
||||
/// OneDrive file provider, never a fake home tree (that is what
|
||||
/// OneDriveLocatorTests in ShotdeckCoreTests are for).
|
||||
private static let realOneDriveSyncRoot = URL(
|
||||
fileURLWithPath: "/Users/benjaminhippler/Library/CloudStorage/OneDrive-MMDGROUP",
|
||||
isDirectory: true
|
||||
)
|
||||
|
||||
/// Phase 5: proves the OneDrive transport end to end against the real sync root.
|
||||
/// Triggered by `SHOTDECK_ONEDRIVE_SELFTEST` when chained after PICKER/SEND-TRUTH/
|
||||
/// UPDATE-SELFTEST — the exact pattern `startUpdateSelfTestIfRequested` uses for its
|
||||
/// own env var. Returns true when the async phase was scheduled (it calls `exit` itself).
|
||||
/// Phase 5: proves the OneDrive transport end to end against a REAL sync root —
|
||||
/// resolved at runtime via `OneDriveLocator.syncRoots()`, never a hardcoded path, so
|
||||
/// this runs correctly on any Mac/account that has OneDrive signed in (MMD-named
|
||||
/// root preferred, same as production). Triggered by `SHOTDECK_ONEDRIVE_SELFTEST`
|
||||
/// when chained after PICKER/SEND-TRUTH/UPDATE-SELFTEST — the exact pattern
|
||||
/// `startUpdateSelfTestIfRequested` uses for its own env var. Returns true when the
|
||||
/// async phase was scheduled (it calls `exit` itself).
|
||||
@discardableResult
|
||||
private static func startOneDriveSelfTestIfRequested() -> Bool {
|
||||
guard ProcessInfo.processInfo.environment["SHOTDECK_ONEDRIVE_SELFTEST"] != nil else {
|
||||
@@ -514,9 +509,16 @@ enum PickerSelfTest {
|
||||
}
|
||||
|
||||
private static func runOneDriveSelfTestAndExit() {
|
||||
// Never a false PASS: no real OneDrive sync root on this machine/account is a
|
||||
// SKIP (still non-zero exit), not silently treated as passing.
|
||||
guard let syncRoot = OneDriveLocator.syncRoots().first else {
|
||||
print("ONEDRIVE-SELFTEST SKIP no OneDrive sync root")
|
||||
fflush(stdout)
|
||||
exit(1)
|
||||
}
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let folder = try await executeOneDriveSelfTest()
|
||||
let folder = try await executeOneDriveSelfTest(syncRoot: syncRoot)
|
||||
print("ONEDRIVE-SELFTEST PASS path=\(folder.path)")
|
||||
fflush(stdout)
|
||||
exit(0)
|
||||
@@ -528,19 +530,30 @@ enum PickerSelfTest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a session, sends it through the OneDrive branch of `send(anchor: nil)`
|
||||
/// against a NEW folder under the real OneDrive sync root, confirms the watcher does
|
||||
/// NOT report the freshly-written unmarked PDF as a return, then adds a real PDFKit
|
||||
/// ink annotation in place (what the iPad does) and confirms the watcher now reports
|
||||
/// it as commented. Never deletes anything under OneDrive — the created folder and
|
||||
/// PDF are left in place for Ben to inspect / for the real iPad round trip.
|
||||
private static func executeOneDriveSelfTest() async throws -> URL {
|
||||
/// Sub-step 1: builds a session, sends it through the OneDrive branch of
|
||||
/// `send(anchor: nil)` against a NEW folder under `syncRoot`, confirms the watcher
|
||||
/// does NOT report the freshly-written unmarked PDF as a return, then adds a real
|
||||
/// PDFKit ink annotation in place (what the iPad does) and confirms the watcher now
|
||||
/// reports it as commented.
|
||||
///
|
||||
/// Sub-step 1b: rapid transport toggling (chooseTransport(.airDrop) immediately
|
||||
/// followed by chooseTransport(.oneDrive), no await between them) must still end
|
||||
/// with the watcher pointed at the OneDrive folder — proves the generation-guarded
|
||||
/// reconcile in chooseTransport/chooseOneDriveFolder (SettingsView.swift) really
|
||||
/// does let the last choice win instead of an earlier, superseded call applying its
|
||||
/// stale folder after a later one already won.
|
||||
///
|
||||
/// Sub-step 2: relaunch simulation — the exact BLOCKER scenario this phase exists to
|
||||
/// catch. OneDrive is still persisted in defaults from sub-step 1; builds a FRESH
|
||||
/// model the same way the real app launches (`AppDelegate.makeLaunchModel()` itself,
|
||||
/// not a reimplementation), bootstraps it, then marks a PDF in the folder in place —
|
||||
/// the relaunched watcher must report it. A temp app-support root keeps this off the
|
||||
/// real ~/Library/Application Support/Shotdeck.
|
||||
///
|
||||
/// Never deletes anything under OneDrive — the created folder and PDFs are left in
|
||||
/// place for Ben to inspect / for the real iPad round trip.
|
||||
private static func executeOneDriveSelfTest(syncRoot: URL) async throws -> URL {
|
||||
let fm = FileManager.default
|
||||
guard fm.fileExists(atPath: realOneDriveSyncRoot.path) else {
|
||||
throw OneDriveSelfTestError.detail(
|
||||
"real OneDrive sync root not found at \(realOneDriveSyncRoot.path)"
|
||||
)
|
||||
}
|
||||
|
||||
// UserDefaults.standard is the ONLY defaults instance send()/TransportSettings
|
||||
// actually read at runtime (there is no defaults-threading through AppModel), so
|
||||
@@ -563,8 +576,7 @@ enum PickerSelfTest {
|
||||
}
|
||||
|
||||
let stamp = DubaiTime.fileStamp(Date())
|
||||
let selftestFolder = realOneDriveSyncRoot
|
||||
.appendingPathComponent("Redline-selftest-\(stamp)", isDirectory: true)
|
||||
let selftestFolder = syncRoot.appendingPathComponent("Redline-selftest-\(stamp)", isDirectory: true)
|
||||
try fm.createDirectory(at: selftestFolder, withIntermediateDirectories: true)
|
||||
|
||||
TransportSettings.setTransport(.oneDrive, defaults: defaults)
|
||||
@@ -629,8 +641,77 @@ enum PickerSelfTest {
|
||||
}
|
||||
|
||||
// What the iPad does: mark it up in place with a real ink annotation, then save.
|
||||
guard let document = PDFDocument(url: pdfURL), let page = document.page(at: 0) else {
|
||||
throw OneDriveSelfTestError.detail("could not reopen \(pdfURL.path) to annotate it")
|
||||
try addInkMark(to: pdfURL)
|
||||
|
||||
let afterMarkup = try await watcher.scanNow()
|
||||
guard let recorded = afterMarkup.first(where: { $0.fileURL == pdfURL }), recorded.isCommented else {
|
||||
throw OneDriveSelfTestError.detail("annotated PDF was not reported as commented by scanNow")
|
||||
}
|
||||
let commentedAfter = try await ledger.commented()
|
||||
guard commentedAfter.contains(where: { $0.fileURL == pdfURL }) else {
|
||||
throw OneDriveSelfTestError.detail("annotated PDF was not recorded in the ledger as commented")
|
||||
}
|
||||
|
||||
// Sub-step 1b: rapid toggle race — see the doc comment above this function.
|
||||
model.chooseTransport(.airDrop)
|
||||
model.chooseTransport(.oneDrive) // immediately superseding the call above
|
||||
// The generation guard itself is what's under test, not this wait — it just
|
||||
// gives the (already-guarded) reconcile Task a moment to settle either way.
|
||||
try await Task.sleep(for: .milliseconds(500))
|
||||
guard model.transport == .oneDrive else {
|
||||
throw OneDriveSelfTestError.detail(
|
||||
"rapid toggle: model.transport ended as \(model.transport), expected .oneDrive"
|
||||
)
|
||||
}
|
||||
let racePDFURL = selftestFolder.appendingPathComponent("Redline-race-\(stamp).pdf")
|
||||
try writeUnmarkedRedlinePDF(to: racePDFURL)
|
||||
try addInkMark(to: racePDFURL)
|
||||
let raceFound = try await model.watcher.scanNow()
|
||||
guard raceFound.first(where: { $0.fileURL == racePDFURL })?.isCommented == true else {
|
||||
throw OneDriveSelfTestError.detail(
|
||||
"rapid toggle: watcher did not end up watching \(selftestFolder.path) — an earlier, superseded chooseTransport call won"
|
||||
)
|
||||
}
|
||||
|
||||
// Sub-step 2: relaunch simulation — see the doc comment above this function.
|
||||
let relaunchAppSupportRoot = fm.temporaryDirectory
|
||||
.appendingPathComponent("shotdeck-onedrive-relaunch-\(UUID().uuidString)", isDirectory: true)
|
||||
defer { try? fm.removeItem(at: relaunchAppSupportRoot) }
|
||||
|
||||
let relaunchModel = AppDelegate.makeLaunchModel(appSupportRoot: relaunchAppSupportRoot)
|
||||
guard relaunchModel.transport == .oneDrive else {
|
||||
throw OneDriveSelfTestError.detail(
|
||||
"relaunch: model transport was \(relaunchModel.transport), expected .oneDrive"
|
||||
)
|
||||
}
|
||||
guard relaunchModel.watchFolderURL.path == selftestFolder.path else {
|
||||
throw OneDriveSelfTestError.detail(
|
||||
"relaunch: model watchFolderURL was \(relaunchModel.watchFolderURL.path), expected \(selftestFolder.path) — this is the exact BLOCKER this phase guards against"
|
||||
)
|
||||
}
|
||||
|
||||
await relaunchModel.bootstrap()
|
||||
|
||||
let relaunchPDFURL = selftestFolder.appendingPathComponent("Redline-relaunch-\(stamp).pdf")
|
||||
try writeUnmarkedRedlinePDF(to: relaunchPDFURL)
|
||||
try addInkMark(to: relaunchPDFURL)
|
||||
|
||||
let relaunchFound = try await relaunchModel.watcher.scanNow()
|
||||
guard relaunchFound.first(where: { $0.fileURL == relaunchPDFURL })?.isCommented == true else {
|
||||
throw OneDriveSelfTestError.detail(
|
||||
"relaunch: watcher did not report the marked PDF at \(relaunchPDFURL.path) as returned — it was watching the wrong folder after relaunch"
|
||||
)
|
||||
}
|
||||
await relaunchModel.watcher.stop()
|
||||
|
||||
return selftestFolder
|
||||
}
|
||||
|
||||
/// Adds a real PDFKit ink annotation to the PDF at `url` in place and saves it —
|
||||
/// exactly what the iPad does when marking up a page.
|
||||
private static func addInkMark(to url: URL) throws {
|
||||
guard let document = PDFDocument(url: url), let page = document.page(at: 0) else {
|
||||
throw OneDriveSelfTestError.detail("could not reopen \(url.path) to annotate it")
|
||||
}
|
||||
let ink = PDFAnnotation(
|
||||
bounds: CGRect(x: 20, y: 20, width: 60, height: 60),
|
||||
@@ -642,20 +723,27 @@ enum PickerSelfTest {
|
||||
stroke.line(to: NSPoint(x: 80, y: 80))
|
||||
ink.add(stroke)
|
||||
page.addAnnotation(ink)
|
||||
guard document.write(to: pdfURL) else {
|
||||
throw OneDriveSelfTestError.detail("could not save the annotated PDF back to \(pdfURL.path)")
|
||||
guard document.write(to: url) else {
|
||||
throw OneDriveSelfTestError.detail("could not save the annotated PDF back to \(url.path)")
|
||||
}
|
||||
}
|
||||
|
||||
let afterMarkup = try await watcher.scanNow()
|
||||
guard let recorded = afterMarkup.first(where: { $0.fileURL == pdfURL }), recorded.isCommented else {
|
||||
throw OneDriveSelfTestError.detail("annotated PDF was not reported as commented by scanNow")
|
||||
/// Writes a fresh, unmarked, single-page "Redline"-creator PDF straight to `url` —
|
||||
/// standing in for a PDF that has just landed in the watch folder, before any
|
||||
/// human mark. Used by the rapid-toggle and relaunch sub-steps, which don't need to
|
||||
/// exercise send()/composePDFForSend() again (sub-step 1 already does).
|
||||
private static func writeUnmarkedRedlinePDF(to url: URL) throws {
|
||||
let document = PDFDocument()
|
||||
let page = PDFPage()
|
||||
page.setBounds(CGRect(x: 0, y: 0, width: 612, height: 792), for: .mediaBox)
|
||||
document.insert(page, at: 0)
|
||||
document.documentAttributes = [
|
||||
PDFDocumentAttribute.creatorAttribute: "Redline",
|
||||
PDFDocumentAttribute.subjectAttribute: UUID().uuidString,
|
||||
]
|
||||
guard document.write(to: url) else {
|
||||
throw OneDriveSelfTestError.detail("could not write \(url.path)")
|
||||
}
|
||||
let commentedAfter = try await ledger.commented()
|
||||
guard commentedAfter.contains(where: { $0.fileURL == pdfURL }) else {
|
||||
throw OneDriveSelfTestError.detail("annotated PDF was not recorded in the ledger as commented")
|
||||
}
|
||||
|
||||
return selftestFolder
|
||||
}
|
||||
|
||||
private static func oneDriveFail(_ detail: String) -> Never {
|
||||
|
||||
Reference in New Issue
Block a user