Skip to content

test(tabs): run a transfer end to end instead of inspecting the gate - #453

Merged
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:test/transfer-executes-the-protocol
Aug 4, 2026
Merged

test(tabs): run a transfer end to end instead of inspecting the gate#453
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:test/transfer-executes-the-protocol

Conversation

@PathGao

@PathGao PathGao commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Test-only. No product behaviour changes, and nothing here is urgent — it can sit until after v2.7.0 is out.

#366 was mine, and it broke "Move to Window ▸ (an open window)" outright. It never reached a release, and you found and fixed it in #452 before it could. This PR is not a second fix; it is the test that would have caught it, and the reason the suite did not.

The gate and the path it closed

#366 added, to all three transfer commands:

if !is_destination_label(window.label(), &token) {
    return Err(format!("TRANSFER_FORBIDDEN: …"));
}

is_destination_label is caller == format!("window-{token}"). That label exists for exactly one window: the one create_transfer_window just built for this token.

There are two ways a tab reaches another window, and only one of them ends there:

entry point destination label passed the #366 gate
create_transfer_window(token) — Move to New Window window-<token> yes
offer_tab_to_window(target_label, token) (#214, shipped in v2.6.13) — Move to an existing window main, or window-<some other token> never

The second is not a corner: an already-open window's label was assigned by an earlier, unrelated transfer, so it cannot equal window-<token> for the token now in flight. main cannot either. 100% failure, both platforms.

Why 150 Rust tests and 596 script tests stayed green

Two tests point at this code. Neither could see it.

  • only_the_matching_destination_window_may_claim asserted is_destination_label behaves as written. It did. The gate was self-consistently wrong — the predicate and its test agreed with each other and both disagreed with the product.
  • windowOrganization.test.ts::'moving to an existing window uses the acknowledged transfer protocol' is five assert.match calls looking for invoke('offer_tab_to_window', { targetLabel, token }), invoke('complete_detached_tab', { token }), acceptOfferedTransfer, onTransferClaimed(tabId) and async function transfer( in MarkdownViewer.svelte and windowSession.svelte.ts. All five still matched. fix(transfer): never leave a moved tab open in both windows #366 touched neither file.

Between them they proved that an implementation exists and that it is internally consistent. A reachable path becoming unreachable disturbs neither property. Nothing in either suite ever performed a transfer.

What is added

Four tests at the bottom of tab_transfer.rs's mod tests, driving the real broker from stage to complete.

test what it runs what it pins
a_transfer_to_an_existing_window_runs_end_to_end stage from mainset_target_label (what offer_tab_to_window does) → claim as that window → complete the path that was dead; red if the target_label arm goes away
a_transfer_to_a_new_window_runs_end_to_end stage → claim as window-<token>, no target recorded → complete #452 did not trade one path for the other
a_third_party_window_is_refused_at_every_step a window that is neither source, nor window-<token>, nor the recorded target, at claim, complete and cancel widening the gate to the recorded target did not widen it to everyone — and the refusals are inert: the real destination can still claim afterwards
the_recorded_destination_may_release_its_own_claim stage → target → claim → cancel as the target a claim makes the source's timeout a no-op, so the holder is the only party that can end it; refusing here strands the tab in both windows

The existing tests keep testing the parts. These test the composition, because the composition is what was broken.

The one source change

The gate lived inside three #[tauri::command] bodies, so it was reachable only through a tauri::Window, which a unit test cannot construct — which is why it was only ever tested through the free predicate it was written against. The decision moves onto the broker as claim_as / complete_as / cancel_as, taking the caller's label as &str:

#[tauri::command]
pub fn claim_detached_tab(window, state, token) -> Result<Option<String>, String> {
    state.claim_as(window.label(), &token, Instant::now())
}

Same peek, same order of operations, same error strings, same TRANSFER_NOT_FOUND race branch in complete. All three commands are one line. Nothing else in src-tauri/ is touched, and no .svelte or .ts file is touched at all.

Falsification

A test that cannot fail is the failure mode this PR exists to close, so it is checked directly. is_destination_authority reverted to its #366 body, tests left in place:

-fn is_destination_authority(caller_label: &str, token: &str, target_label: Option<&str>) -> bool {
-    is_destination_label(caller_label, token) || target_label == Some(caller_label)
+fn is_destination_authority(caller_label: &str, token: &str, _target_label: Option<&str>) -> bool {
+    is_destination_label(caller_label, token)
 }

That alone turns five tests red, but two of them — only_the_matching_destination_window_may_claim and cancel_authority_rejects_third_party_windows — only fail because #452 amended them alongside the fix. They did not exist in that form on the day #366 merged, so they are not evidence. Restoring those two to their #366 wording as well reconstructs the suite exactly as it stood while the path was dead:

running 154 tests
test tab_transfer::tests::a_third_party_window_is_refused_at_every_step ... FAILED
test tab_transfer::tests::a_transfer_to_an_existing_window_runs_end_to_end ... FAILED
test tab_transfer::tests::the_recorded_destination_may_release_its_own_claim ... FAILED

failures:

---- tab_transfer::tests::a_third_party_window_is_refused_at_every_step stdout ----

thread 'tab_transfer::tests::a_third_party_window_is_refused_at_every_step' panicked at src/tab_transfer.rs:713:18:
the real destination must not be affected by the refusals: "TRANSFER_FORBIDDEN: window 'window-1f0e9d8c7b6a5948372615043f2e1d0c' may not claim this tab transfer"

---- tab_transfer::tests::a_transfer_to_an_existing_window_runs_end_to_end stdout ----

thread 'tab_transfer::tests::a_transfer_to_an_existing_window_runs_end_to_end' panicked at src/tab_transfer.rs:646:14:
the window offer_tab_to_window named is a destination and may claim: "TRANSFER_FORBIDDEN: window 'window-1f0e9d8c7b6a5948372615043f2e1d0c' may not claim this tab transfer"

---- tab_transfer::tests::the_recorded_destination_may_release_its_own_claim stdout ----

thread 'tab_transfer::tests::the_recorded_destination_may_release_its_own_claim' panicked at src/tab_transfer.rs:731:14:
the recorded target may claim: "TRANSFER_FORBIDDEN: window 'window-1f0e9d8c7b6a5948372615043f2e1d0c' may not claim this tab transfer"

test result: FAILED. 151 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.64s

Three red, and all three are these tests. a_transfer_to_a_new_window_runs_end_to_end stays green throughout, which is the whole point of having it: the mutation removes one path and leaves the other alone, and the suite now says so. The fix was restored and the suite re-run green afterwards.

Verification

command result
cd src-tauri && cargo test 154 passed, 0 failed (150 on master)
npm test 596 passed, 0 failed — unchanged; no script or component file is touched
npm run check 638 files, 0 errors, 0 warnings
cargo clippy --all-targets no diagnostic in tab_transfer.rs; the three pre-existing warnings elsewhere (EXE_NAME unused, a push_str, a collapsible if) are as on master
cargo fmt --check tab_transfer.rs goes from 4 pre-existing diffs to 1, incidentally, by rewriting the blocks the remaining three were in. Nothing was reformatted deliberately and the last one is left alone.

Notes, not changes

  • scripts/windowOrganization.test.ts is the other half of this hole and is deliberately untouched. Its 'moving to an existing window uses the acknowledged transfer protocol' still asserts that five identifiers appear in two source files, which is what it was able to do — node --test cannot import a .svelte file (test(scroll-sync): cover the split-view mapping by running it #442's finding). It stayed green through fix(transfer): never leave a moved tab open in both windows #366 and would stay green through the same defect again. Closing that needs the handler extracted the way test(scroll-sync): cover the split-view mapping by running it #442 extracted the scroll-sync mapping, and it belongs in a scripts/ PR, not this one.
  • offer_tab_to_window's own wiring has no test. window_runtime::offer_tab_to_window checks the window exists, calls set_target_label, then emits tab-transfer-offer. The broker half is now covered; the get_webview_window / emit_to half needs an AppHandle and is not reachable from a unit test.
  • set_target_label is pub and last-write-wins. It is called once per transfer, immediately after stage, by the one caller that has the token. A second call would silently redirect a staged tab. Worth pinning to "only before the first claim" if the offer path ever grows a retry — no bug today, so nothing is changed.

#366 gated claim/complete/cancel on the caller being window-<token>.
That label exists only for a window create_transfer_window just built,
so offer_tab_to_window's destination -- an already-open window labelled
main or window-<some other token> -- was refused 100% of the time. #452
fixed it by recording the target on the pending entry and accepting
either label. It reached master, not a release.

Nothing in the suite failed. The Rust test asserted the predicate
behaves as written, and it did: the gate was self-consistently wrong.
The script test asserted that the invoke('offer_tab_to_window', ...)
call appears in the source, and it still appeared. Both confirmed an
implementation exists and is internally consistent, which is precisely
what a reachable path becoming unreachable leaves undisturbed.

Four tests now drive the broker from stage to complete:

  * a transfer to an existing window -- stage from main, record the
    target the way offer_tab_to_window does, claim as that window,
    complete. This is the path that was dead.
  * a transfer to a new window -- claim as window-<token> with no
    target recorded, so the fix cannot trade one path for the other.
  * a bystander window refused at claim, complete and cancel, with the
    transfer left intact for its real destination afterwards.
  * the recorded target releasing its own claim, the only rollback a
    claimed transfer has: the source's timeout is deliberately inert
    once a claim exists, so a refusal here would strand the tab in
    both windows.

The authorisation decision moves out of the three #[tauri::command]
bodies onto the broker as claim_as/complete_as/cancel_as, which take
the caller's label as &str; each command is now one line passing
window.label(). A unit test cannot build a tauri::Window, so the gate
was otherwise reachable only through the predicate it was written
against. Same peek, same order, same error strings.

Reverting the recorded-target arm of is_destination_authority and
restoring the two predicate tests to their #366 wording leaves the
suite at 3 failed / 151 passed -- all three of them these tests.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao force-pushed the test/transfer-executes-the-protocol branch from 63a3751 to 1378038 Compare August 4, 2026 23:18
@PathGao
PathGao merged commit 85fe8d9 into sftwrdotdev:master Aug 4, 2026
4 checks passed
@PathGao
PathGao deleted the test/transfer-executes-the-protocol branch August 4, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant