diff --git a/extensions/matrix/src/matrix/actions/devices.test.ts b/extensions/matrix/src/matrix/actions/devices.test.ts index 0892c811ad2..578e48471c2 100644 --- a/extensions/matrix/src/matrix/actions/devices.test.ts +++ b/extensions/matrix/src/matrix/actions/devices.test.ts @@ -96,7 +96,7 @@ describe("matrix device actions", () => { }, ], })); - withStartedActionClientMock.mockImplementation(async (_opts, run) => { + withResolvedActionClientMock.mockImplementation(async (_opts, run) => { return await run({ listOwnDevices: vi.fn(async () => [ { @@ -150,5 +150,10 @@ describe("matrix device actions", () => { current: true, }), ]); + expect(withResolvedActionClientMock).toHaveBeenCalledWith( + { accountId: "poe" }, + expect.any(Function), + ); + expect(withStartedActionClientMock).not.toHaveBeenCalled(); }); }); diff --git a/extensions/matrix/src/matrix/actions/devices.ts b/extensions/matrix/src/matrix/actions/devices.ts index 27735fc081f..c64a128712b 100644 --- a/extensions/matrix/src/matrix/actions/devices.ts +++ b/extensions/matrix/src/matrix/actions/devices.ts @@ -1,5 +1,5 @@ import { summarizeMatrixDeviceHealth } from "../device-health.js"; -import { withResolvedActionClient, withStartedActionClient } from "./client.js"; +import { withResolvedActionClient } from "./client.js"; import type { MatrixActionClientOpts } from "./types.js"; export async function listMatrixOwnDevices(opts: MatrixActionClientOpts = {}) { @@ -7,7 +7,7 @@ export async function listMatrixOwnDevices(opts: MatrixActionClientOpts = {}) { } export async function pruneMatrixStaleGatewayDevices(opts: MatrixActionClientOpts = {}) { - return await withStartedActionClient(opts, async (client) => { + return await withResolvedActionClient(opts, async (client) => { const devices = await client.listOwnDevices(); const health = summarizeMatrixDeviceHealth(devices); const staleGatewayDeviceIds = health.staleOpenClawDevices.map((device) => device.deviceId); diff --git a/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts b/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts index b177dbf6126..5577d01dc41 100644 --- a/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts +++ b/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts @@ -81,6 +81,19 @@ type MatrixQaDestructiveSetup = { seededEventId: string; }; +async function cleanupMatrixQaTempDevices( + client: MatrixQaE2eeScenarioClient, + deviceIds: Array, +): Promise { + await client.stop().catch(() => undefined); + const uniqueDeviceIds = [ + ...new Set(deviceIds.filter((deviceId): deviceId is string => !!deviceId)), + ]; + if (uniqueDeviceIds.length > 0) { + await client.deleteOwnDevices(uniqueDeviceIds).catch(() => undefined); + } +} + function requireMatrixQaE2eeOutputDir(context: MatrixQaScenarioContext) { if (!context.outputDir) { throw new Error("Matrix E2EE destructive QA scenarios require an output directory"); @@ -668,8 +681,7 @@ export async function runMatrixQaE2eeStateLossExternalRecoveryKeyScenario( }; } finally { await cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [device.deviceId]); } } @@ -748,8 +760,7 @@ export async function runMatrixQaE2eeStateLossStoredRecoveryKeyScenario( }; } finally { await cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [device.deviceId]); } } @@ -793,8 +804,7 @@ export async function runMatrixQaE2eeStateLossNoRecoveryKeyScenario( }; } finally { await cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [device.deviceId]); } } @@ -863,8 +873,7 @@ export async function runMatrixQaE2eeStaleRecoveryKeyAfterBackupResetScenario( }; } finally { await cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [device.deviceId]); } } @@ -1026,8 +1035,7 @@ export async function runMatrixQaE2eeServerBackupDeletedLocalReuploadRestoresSce }; } finally { await cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [device.deviceId]); } } @@ -1101,8 +1109,7 @@ export async function runMatrixQaE2eeCorruptCryptoIdbSnapshotScenario( }; } finally { await cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [device.deviceId]); } } @@ -1141,6 +1148,7 @@ export async function runMatrixQaE2eeServerDeviceDeletedLocalStateIntactScenario assertMatrixQaCliBackupRestoreSucceeded(restored.payload, "deleted-device preflight"); await setup.owner.deleteOwnDevices([device.deviceId]); const ownerDevicesAfterDelete = await setup.owner.listOwnDevices(); + await setup.owner.stop().catch(() => undefined); const defaultStatus = await runMatrixQaCliJson({ allowNonZero: true, args: ["matrix", "verify", "status", "--account", "deleted-device", "--json"], @@ -1238,6 +1246,7 @@ export async function runMatrixQaE2eeServerDeviceDeletedReloginRecoversScenario( await setup.owner.deleteOwnDevices([deleted.device.deviceId]); const ownerDevicesAfterDelete = await setup.owner.listOwnDevices(); + await setup.owner.stop().catch(() => undefined); const defaultStatus = await runMatrixQaCliJson({ allowNonZero: true, args: ["matrix", "verify", "status", "--account", "deleted-device-recovery", "--json"], @@ -1322,12 +1331,11 @@ export async function runMatrixQaE2eeServerDeviceDeletedReloginRecoversScenario( }; } finally { await replacement?.cli.dispose().catch(() => undefined); - if (replacement?.device.deviceId) { - await setup.owner.deleteOwnDevices([replacement.device.deviceId]).catch(() => undefined); - } await deleted.cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([deleted.device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [ + replacement?.device.deviceId, + deleted.device.deviceId, + ]); } } @@ -1566,6 +1574,7 @@ export async function runMatrixQaE2eeWrongAccountRecoveryKeyScenario( }; } finally { await cli?.dispose().catch(() => undefined); + await observer.stop().catch(() => undefined); if (device) { await observer.deleteOwnDevices([device.deviceId]).catch(() => undefined); } @@ -1627,7 +1636,6 @@ export async function runMatrixQaE2eeHistoryExistsBackupEmptyScenario( }; } finally { await cli.dispose().catch(() => undefined); - await setup.owner.deleteOwnDevices([device.deviceId]).catch(() => undefined); - await setup.owner.stop().catch(() => undefined); + await cleanupMatrixQaTempDevices(setup.owner, [device.deviceId]); } } diff --git a/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee.ts b/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee.ts index 5944f805570..105e0cd98f3 100644 --- a/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee.ts +++ b/extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee.ts @@ -1495,6 +1495,7 @@ export async function runMatrixQaE2eeRecoveryKeyLifecycleScenario( } } await recoveryClient.stop(); + await client.stop().catch(() => undefined); await client.deleteOwnDevices([recoveryDevice.deviceId]).catch(() => undefined); cleanupRecoveryDevice = false; return { @@ -1530,6 +1531,7 @@ export async function runMatrixQaE2eeRecoveryKeyLifecycleScenario( } finally { if (cleanupRecoveryDevice) { await recoveryClient.stop().catch(() => undefined); + await client.stop().catch(() => undefined); await client.deleteOwnDevices([recoveryDevice.deviceId]).catch(() => undefined); } } @@ -1609,6 +1611,7 @@ export async function runMatrixQaE2eeRecoveryOwnerVerificationRequiredScenario( ].join("\n"), }; } finally { + await client.stop().catch(() => undefined); await client.deleteOwnDevices([recoveryDevice.deviceId]).catch(() => undefined); } }, @@ -3136,6 +3139,7 @@ export async function runMatrixQaE2eeStaleDeviceHygieneScenario( if (!before.some((device) => device.deviceId === secondary.deviceId)) { throw new Error("Matrix stale-device list did not include the secondary login"); } + await client.stop().catch(() => undefined); const deleted = await client.deleteOwnDevices([secondary.deviceId]); const remainingDeviceIds = deleted.remainingDevices.map((device) => device.deviceId); if (remainingDeviceIds.includes(secondary.deviceId)) {