test(selftest): ONEDRIVE-SELFTEST sub-step 1c — toggle immediately followed by Send

Adds a third rapid-toggle assertion alongside the existing folder-reconcile
check: chooseTransport(.airDrop) immediately followed by
chooseTransport(.oneDrive), with NO sleep, then an immediate send(anchor: nil)
— proving send() correctly awaits the pending reconcile Task
(SendController.swift/AppModel.swift in this series) rather than racing
ahead with a stale recordUncommented flag. Asserts the freshly-sent,
still-unmarked PDF is never itself reported as an already-returned document.

Also updates composePDFForSend's call site for its new transport: parameter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp
This commit is contained in:
2026-09-05 09:55:22 +04:00
co-authored by Claude Fable 5.1
parent 04d1e73532
commit 8d68c836cd
+54 -1
View File
@@ -223,7 +223,7 @@ enum PickerSelfTest {
sendTruthFail("seeded session was empty") sendTruthFail("seeded session was empty")
} }
let pending = try await model.composePDFForSend(outbox: model.outboxURL) let pending = try await model.composePDFForSend(outbox: model.outboxURL, transport: .airDrop)
guard fm.fileExists(atPath: pending.fileURL.path) else { guard fm.fileExists(atPath: pending.fileURL.path) else {
sendTruthFail("PDF was not written") sendTruthFail("PDF was not written")
} }
@@ -543,6 +543,13 @@ enum PickerSelfTest {
/// does let the last choice win instead of an earlier, superseded call applying its /// does let the last choice win instead of an earlier, superseded call applying its
/// stale folder after a later one already won. /// stale folder after a later one already won.
/// ///
/// Sub-step 1c: the OTHER race a toggle immediately followed by Send, with no
/// sleep at all before send() runs. Proves send() awaits `pendingReconcileTask`
/// before snapshotting transport/folder: a freshly-sent, still-unmarked PDF must
/// never be reported as an already-returned document (which is exactly what would
/// happen if send() raced ahead while recordUncommented was still `true`, stale
/// from the .airDrop leg of the toggle).
///
/// Sub-step 2: relaunch simulation the exact BLOCKER scenario this phase exists to /// 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 /// 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, /// model the same way the real app launches (`AppDelegate.makeLaunchModel()` itself,
@@ -673,6 +680,52 @@ enum PickerSelfTest {
) )
} }
// Sub-step 1c: toggle-then-immediate-send race see the doc comment above
// this function. No sleep here: this IS the exact race window finding #3
// exists to close, so send() itself must wait out the pending reconcile.
let racePNG = try makeTinyPNGData()
_ = try await model.spool.append(
pngData: racePNG, pixelWidth: 64, pixelHeight: 48, scale: 1, capturedAt: Date()
)
model.replaceSession(try await model.spool.currentSession())
guard !model.session.isEmpty else {
throw OneDriveSelfTestError.detail("toggle-then-send: re-seeded session was empty")
}
let knownBeforeToggleSend = Set(
((try? fm.contentsOfDirectory(at: selftestFolder, includingPropertiesForKeys: nil)) ?? [])
.map(\.lastPathComponent)
)
model.chooseTransport(.airDrop)
model.chooseTransport(.oneDrive) // immediately superseding, no sleep before send()
await model.send(anchor: nil)
guard let toggleSendStatus = model.statusLine, toggleSendStatus.hasPrefix("Saved to OneDrive") else {
throw OneDriveSelfTestError.detail(
"toggle-then-send: status was \(model.statusLine ?? "nil"), expected 'Saved to OneDrive'"
)
}
guard model.session.isEmpty else {
throw OneDriveSelfTestError.detail("toggle-then-send: session was not archived")
}
let filesAfterToggleSend = (try? fm.contentsOfDirectory(
at: selftestFolder, includingPropertiesForKeys: nil
)) ?? []
guard let toggleSendPDFURL = filesAfterToggleSend.first(where: {
$0.pathExtension.lowercased() == "pdf" && !knownBeforeToggleSend.contains($0.lastPathComponent)
}) else {
throw OneDriveSelfTestError.detail("toggle-then-send: no new PDF found in \(selftestFolder.path)")
}
// The freshly-sent PDF is UNMARKED. If send() had raced ahead of the pending
// reconcile, recordUncommented could still have been (stale) true, and this
// scan would wrongly report it as already returned.
let scanAfterToggleSend = try await model.watcher.scanNow()
guard !scanAfterToggleSend.contains(where: { $0.fileURL == toggleSendPDFURL }) else {
throw OneDriveSelfTestError.detail(
"toggle-then-send: freshly-sent unmarked PDF at \(toggleSendPDFURL.path) was reported as returned — send() raced ahead of the pending reconcile"
)
}
// Sub-step 2: relaunch simulation see the doc comment above this function. // Sub-step 2: relaunch simulation see the doc comment above this function.
let relaunchAppSupportRoot = fm.temporaryDirectory let relaunchAppSupportRoot = fm.temporaryDirectory
.appendingPathComponent("shotdeck-onedrive-relaunch-\(UUID().uuidString)", isDirectory: true) .appendingPathComponent("shotdeck-onedrive-relaunch-\(UUID().uuidString)", isDirectory: true)