From 8d68c836cd98307096cb57bad72f45ba49eadb88 Mon Sep 17 00:00:00 2001 From: kua-agent Date: Sat, 5 Sep 2026 09:55:22 +0400 Subject: [PATCH] =?UTF-8?q?test(selftest):=20ONEDRIVE-SELFTEST=20sub-step?= =?UTF-8?q?=201c=20=E2=80=94=20toggle=20immediately=20followed=20by=20Send?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp --- Sources/Shotdeck/PickerSelfTest.swift | 55 ++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Sources/Shotdeck/PickerSelfTest.swift b/Sources/Shotdeck/PickerSelfTest.swift index 97024ae..7b34186 100644 --- a/Sources/Shotdeck/PickerSelfTest.swift +++ b/Sources/Shotdeck/PickerSelfTest.swift @@ -223,7 +223,7 @@ enum PickerSelfTest { 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 { 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 /// 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 /// 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, @@ -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. let relaunchAppSupportRoot = fm.temporaryDirectory .appendingPathComponent("shotdeck-onedrive-relaunch-\(UUID().uuidString)", isDirectory: true)