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 39 additions and 2 deletions
Showing only changes of commit 706726a3d4 - Show all commits
@@ -17,6 +17,15 @@ public actor ReturnWatcher {
/// A document that IS commented is always recorded, regardless of this flag.
public var recordUncommented: Bool = true
/// The folder this watcher is CURRENTLY seeded to scan/watch whatever `init`
/// last set it to, or `updateWatchFolder` since. Exposed so tests can observe the
/// watcher's seeded folder directly (e.g. right after construction, before
/// `start()`/`updateWatchFolder()` ever run) rather than only inferring it
/// indirectly through `scanNow()`'s behavior.
public var currentWatchFolder: URL {
watchFolder
}
/// Watch folder is `paths.watchFolder`, which production constructs from
/// `FolderSettings.resolve().watch`. This type never calls FolderSettings;
/// `updateWatchFolder` is invoked by the UI layer only.
+30 -2
View File
@@ -5,14 +5,26 @@ import Testing
import ShotdeckCore
@testable import Shotdeck
/// Coverage gap closed (adversarial review, round 3): the Core-level regression tests
/// in ShotdeckCoreTests hand-replicate what `AppDelegate.makeLaunchModel()` and
/// Coverage gap closed (adversarial review, rounds 3 and 4): the Core-level regression
/// tests in ShotdeckCoreTests hand-replicate what `AppDelegate.makeLaunchModel()` and
/// `AppModel.bootstrap()` do, rather than calling them so a future revert of
/// `makeLaunchModel()` back to the AirDrop-only resolver, or a dropped
/// `updateWatchFolder` call inside `bootstrap()`, would NOT fail `swift test`. This
/// test goes through the real, unmodified call sites in the `Shotdeck` executable
/// target via `@testable import`, which `ShotdeckCoreTests` cannot reach (it only
/// depends on `ShotdeckCore`) hence this separate `ShotdeckTests` target.
///
/// Round 4 correction: the first version of this test asserted only
/// `model.watchFolderURL`, which `AppModel.init` computes independently via
/// `TransportSettings.effectiveFolders()` so it stayed correct (and the test kept
/// passing) even when `makeLaunchModel()` was reverted to the AirDrop-only resolver,
/// because `bootstrap()`'s own unconditional `updateWatchFolder` reconcile papered
/// over the reverted resolver. That made the "verified this catches the blocker"
/// claim in the previous round's commit message empirically false. This version
/// asserts `model.paths`/the watcher's `currentWatchFolder` BEFORE `bootstrap()` runs,
/// which actually depends on what `makeLaunchModel()` built see this file's git
/// history (or the round-4 commit message) for the verbatim before/after
/// `swift test --filter` output proving it now discriminates correctly.
@MainActor
@Test("Real wiring: AppDelegate.makeLaunchModel() + AppModel.bootstrap() detect a marked OneDrive return")
func realLaunchModelAndBootstrapDetectAMarkedOneDriveReturn() async throws {
@@ -73,6 +85,22 @@ func realLaunchModelAndBootstrapDetectAMarkedOneDriveReturn() async throws {
// combo already taken / the global hotkey table in an unexpected state.
defer { model.hotkeys.unregisterAll() }
// PRE-bootstrap assertions this is the actual proof of the launch RESOLVER
// (AppDelegate.makeLaunchModel() -> TransportSettings.resolvedAppSupportPaths()),
// independent of bootstrap()'s own reconcile. `model.watchFolderURL` alone does
// NOT prove this: AppModel.init computes it separately via
// TransportSettings.effectiveFolders(), so it would read as correct even if
// makeLaunchModel's `paths` were built by the AirDrop-only resolver which is
// exactly how the first version of this test was empirically shown to be vacuous
// for the launch-resolver path (see this commit's message). `model.paths` is
// `internal` on AppModel, so @testable import already exposes it without any
// production API change; `currentWatchFolder` is the one new (internal-facing,
// `public` on the actor) seam added to ReturnWatcher for this purpose.
#expect(model.paths.watchFolder.path == oneDriveFolder.path)
#expect(model.paths.outbox.path == oneDriveFolder.path)
let seededWatchFolder = await model.watcher.currentWatchFolder
#expect(seededWatchFolder.path == oneDriveFolder.path)
#expect(model.transport == .oneDrive)
#expect(model.watchFolderURL.path == oneDriveFolder.path)