Skip to content

Commit 20462bf

Browse files
committed
test(remote): a proxied lease that expires tears its session down and a reacquired lease starts clean
#2198 acceptance: lease expiry, session cleanup, and device reacquisition do not reuse prior capture or comparison state. The parity world takes a clock-driven LeaseRegistry; a leased session behind the proxy captures a diff baseline, its lease lapses past the proxy TTL, the next request is refused as UNAUTHORIZED/LEASE_NOT_FOUND with the session torn down, and a freshly allocated lease reopens and reports baselineInitialized on its first diff.
1 parent f0a5889 commit 20462bf

1 file changed

Lines changed: 98 additions & 2 deletions

File tree

‎test/integration/provider-scenarios/remote-proxy-parity.test.ts‎

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import fs from 'node:fs';
33
import os from 'node:os';
44
import path from 'node:path';
55
import { test } from 'vitest';
6+
import { DEFAULT_PROXY_LEASE_TTL_MS } from '../../../src/core/lease-scope.ts';
67
import { sendToDaemon } from '../../../src/daemon/client/daemon-client.ts';
8+
import { LeaseRegistry } from '../../../src/daemon/lease-registry.ts';
79
import { createDaemonHttpServer } from '../../../src/daemon/server/http-server.ts';
810
import { resolveSessionRequestLogPath } from '../../../src/daemon/session-store.ts';
911
import type { DaemonRequest, DaemonResponse } from '../../../src/daemon/types.ts';
@@ -91,7 +93,9 @@ function scriptedTree() {
9193
};
9294
}
9395

94-
async function createParityWorld(): Promise<ParityWorld> {
96+
type ParityWorldOptions = { leaseRegistry?: LeaseRegistry };
97+
98+
async function createParityWorld(options: ParityWorldOptions = {}): Promise<ParityWorld> {
9599
const runnerTranscript = createProviderTranscript([
96100
{
97101
command: 'ios.runner.snapshot',
@@ -107,6 +111,7 @@ async function createParityWorld(): Promise<ParityWorld> {
107111
]),
108112
});
109113
const daemon = await createProviderScenarioHarness({
114+
...(options.leaseRegistry ? { leaseRegistry: options.leaseRegistry } : {}),
110115
platformRuntime: true,
111116
appleRunnerProvider: () =>
112117
createAppleRunnerProviderFromTranscript(runnerTranscript, 'ios.runner'),
@@ -224,8 +229,9 @@ async function runLeg(leg: ScenarioLeg, legName: string): Promise<DaemonResponse
224229

225230
async function withProxiedWorld<T>(
226231
run: (context: { world: ParityWorld; proxied: ScenarioLeg }) => Promise<T>,
232+
options: ParityWorldOptions = {},
227233
): Promise<T> {
228-
const world = await createParityWorld();
234+
const world = await createParityWorld(options);
229235
const upstream = await createDaemonHttpServer({
230236
token: world.daemon.token,
231237
handleRequest: world.daemon.handleRequest,
@@ -396,3 +402,93 @@ test(
396402
},
397403
PARALLEL_PROVIDER_SCENARIO_TIMEOUT_MS,
398404
);
405+
406+
const LEASE_SCOPE = {
407+
tenantId: 'team-a',
408+
runId: 'run-a',
409+
clientId: 'client-a',
410+
deviceKey: SIM.id,
411+
leaseBackend: 'ios-simulator',
412+
} as const;
413+
414+
test(
415+
'Provider-backed integration proxy lease expiry tears the session down and a reacquired lease starts with no comparison state',
416+
async (t) => {
417+
if (await skipWhenLoopbackUnavailable(t, 'daemon proxy parity coverage')) return;
418+
419+
let now = 1_000_000;
420+
const leaseRegistry = new LeaseRegistry({ now: () => now });
421+
await withProxiedWorld(
422+
async ({ world, proxied }) => {
423+
const session = 'leased';
424+
const flags = { platform: 'ios', udid: SIM.id } as const;
425+
const allocate = async (): Promise<string> => {
426+
const response = await proxied({
427+
session,
428+
command: 'lease_allocate',
429+
positionals: [],
430+
flags: {},
431+
meta: LEASE_SCOPE,
432+
});
433+
assert.equal(response.ok, true, JSON.stringify(response));
434+
const leaseId = (response.ok ? response.data : {})?.lease as { leaseId?: string };
435+
assert.equal(typeof leaseId?.leaseId, 'string');
436+
return leaseId.leaseId!;
437+
};
438+
const run = async (
439+
command: string,
440+
positionals: string[],
441+
leaseId: string,
442+
stepFlags: DaemonRequest['flags'] = flags,
443+
) =>
444+
await proxied({
445+
session,
446+
command,
447+
positionals,
448+
flags: stepFlags,
449+
meta: { ...LEASE_SCOPE, leaseId },
450+
});
451+
452+
const firstLease = await allocate();
453+
assert.equal((await run('open', [APP], firstLease)).ok, true);
454+
assert.equal(
455+
(await run('snapshot', [], firstLease, { snapshotInteractiveOnly: true })).ok,
456+
true,
457+
);
458+
assert.equal(
459+
baselineInitialized(
460+
await run('diff', ['snapshot'], firstLease, { snapshotInteractiveOnly: true }),
461+
),
462+
false,
463+
'the leased session holds comparison state before it expires',
464+
);
465+
466+
// The lease lapses without a heartbeat; the next request through the proxy finds it expired.
467+
now += DEFAULT_PROXY_LEASE_TTL_MS + 1;
468+
const expired = await run('diff', ['snapshot'], firstLease, {
469+
snapshotInteractiveOnly: true,
470+
});
471+
assert.equal(expired.ok, false);
472+
if (expired.ok) return;
473+
assert.equal(expired.error.code, 'UNAUTHORIZED');
474+
assert.equal(expired.error.details?.reason, 'LEASE_NOT_FOUND');
475+
assert.equal(typeof expired.error.hint, 'string');
476+
assert.equal(world.daemon.session(session), undefined, 'expiry tears the session down');
477+
478+
const secondLease = await allocate();
479+
assert.notEqual(secondLease, firstLease);
480+
assert.equal((await run('open', [APP], secondLease)).ok, true);
481+
assert.equal(
482+
baselineInitialized(
483+
await run('diff', ['snapshot'], secondLease, { snapshotInteractiveOnly: true }),
484+
),
485+
true,
486+
'a reacquired lease must not compare against the expired session tree',
487+
);
488+
assert.equal((await run('close', [], secondLease, {})).ok, true);
489+
},
490+
{ leaseRegistry },
491+
);
492+
},
493+
PARALLEL_PROVIDER_SCENARIO_TIMEOUT_MS,
494+
);

0 commit comments

Comments
 (0)