mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 02:20:30 +00:00
refactor: dedupe test and script helpers
This commit is contained in:
@@ -29,6 +29,97 @@ function createCryptoApi(overrides?: Partial<MatrixCryptoBootstrapApi>): MatrixC
|
||||
};
|
||||
}
|
||||
|
||||
function createVerifiedDeviceStatus(overrides?: {
|
||||
localVerified?: boolean;
|
||||
crossSigningVerified?: boolean;
|
||||
signedByOwner?: boolean;
|
||||
}) {
|
||||
return {
|
||||
isVerified: () => true,
|
||||
localVerified: overrides?.localVerified ?? true,
|
||||
crossSigningVerified: overrides?.crossSigningVerified ?? true,
|
||||
signedByOwner: overrides?.signedByOwner ?? true,
|
||||
};
|
||||
}
|
||||
|
||||
function createBootstrapperHarness(
|
||||
cryptoOverrides?: Partial<MatrixCryptoBootstrapApi>,
|
||||
depsOverrides?: Partial<ReturnType<typeof createBootstrapperDeps>>,
|
||||
) {
|
||||
const deps = {
|
||||
...createBootstrapperDeps(),
|
||||
...depsOverrides,
|
||||
};
|
||||
const crypto = createCryptoApi(cryptoOverrides);
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
);
|
||||
return { deps, crypto, bootstrapper };
|
||||
}
|
||||
|
||||
async function runExplicitSecretStorageRepairScenario(firstError: string) {
|
||||
const bootstrapCrossSigning = vi
|
||||
.fn<() => Promise<void>>()
|
||||
.mockRejectedValueOnce(new Error(firstError))
|
||||
.mockResolvedValueOnce(undefined);
|
||||
const { deps, crypto, bootstrapper } = createBootstrapperHarness({
|
||||
bootstrapCrossSigning,
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
getDeviceVerificationStatus: vi.fn(async () => createVerifiedDeviceStatus()),
|
||||
});
|
||||
|
||||
await bootstrapper.bootstrap(crypto, {
|
||||
strict: true,
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
allowAutomaticCrossSigningReset: false,
|
||||
});
|
||||
|
||||
return { deps, crypto, bootstrapCrossSigning };
|
||||
}
|
||||
|
||||
function expectSecretStorageRepairRetry(
|
||||
deps: ReturnType<typeof createBootstrapperDeps>,
|
||||
crypto: MatrixCryptoBootstrapApi,
|
||||
bootstrapCrossSigning: ReturnType<typeof vi.fn>,
|
||||
) {
|
||||
expect(deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey).toHaveBeenCalledWith(crypto, {
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
forceNewSecretStorage: true,
|
||||
});
|
||||
expect(bootstrapCrossSigning).toHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
async function bootstrapWithVerificationRequestListener(overrides?: {
|
||||
deps?: Partial<ReturnType<typeof createBootstrapperDeps>>;
|
||||
crypto?: Partial<MatrixCryptoBootstrapApi>;
|
||||
}) {
|
||||
const listeners = new Map<string, (...args: unknown[]) => void>();
|
||||
const { deps, bootstrapper, crypto } = createBootstrapperHarness(
|
||||
{
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
})),
|
||||
on: vi.fn((eventName: string, listener: (...args: unknown[]) => void) => {
|
||||
listeners.set(eventName, listener);
|
||||
}),
|
||||
...overrides?.crypto,
|
||||
},
|
||||
overrides?.deps,
|
||||
);
|
||||
|
||||
await bootstrapper.bootstrap(crypto);
|
||||
const listener = Array.from(listeners.entries()).find(([eventName]) =>
|
||||
eventName.toLowerCase().includes("verificationrequest"),
|
||||
)?.[1];
|
||||
expect(listener).toBeTypeOf("function");
|
||||
|
||||
return {
|
||||
deps,
|
||||
listener,
|
||||
};
|
||||
}
|
||||
|
||||
describe("MatrixCryptoBootstrapper", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
@@ -159,40 +250,11 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
});
|
||||
|
||||
it("recreates secret storage and retries cross-signing when explicit bootstrap hits a stale server key", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const bootstrapCrossSigning = vi
|
||||
.fn<() => Promise<void>>()
|
||||
.mockRejectedValueOnce(new Error("getSecretStorageKey callback returned falsey"))
|
||||
.mockResolvedValueOnce(undefined);
|
||||
const crypto = createCryptoApi({
|
||||
bootstrapCrossSigning,
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
localVerified: true,
|
||||
crossSigningVerified: true,
|
||||
signedByOwner: true,
|
||||
})),
|
||||
});
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
const { deps, crypto, bootstrapCrossSigning } = await runExplicitSecretStorageRepairScenario(
|
||||
"getSecretStorageKey callback returned falsey",
|
||||
);
|
||||
|
||||
await bootstrapper.bootstrap(crypto, {
|
||||
strict: true,
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
allowAutomaticCrossSigningReset: false,
|
||||
});
|
||||
|
||||
expect(deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey).toHaveBeenCalledWith(
|
||||
crypto,
|
||||
{
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
forceNewSecretStorage: true,
|
||||
},
|
||||
);
|
||||
expect(bootstrapCrossSigning).toHaveBeenCalledTimes(2);
|
||||
expectSecretStorageRepairRetry(deps, crypto, bootstrapCrossSigning);
|
||||
expect(bootstrapCrossSigning).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
@@ -208,40 +270,11 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
});
|
||||
|
||||
it("recreates secret storage and retries cross-signing when explicit bootstrap hits bad MAC", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const bootstrapCrossSigning = vi
|
||||
.fn<() => Promise<void>>()
|
||||
.mockRejectedValueOnce(new Error("Error decrypting secret m.cross_signing.master: bad MAC"))
|
||||
.mockResolvedValueOnce(undefined);
|
||||
const crypto = createCryptoApi({
|
||||
bootstrapCrossSigning,
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
localVerified: true,
|
||||
crossSigningVerified: true,
|
||||
signedByOwner: true,
|
||||
})),
|
||||
});
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
const { deps, crypto, bootstrapCrossSigning } = await runExplicitSecretStorageRepairScenario(
|
||||
"Error decrypting secret m.cross_signing.master: bad MAC",
|
||||
);
|
||||
|
||||
await bootstrapper.bootstrap(crypto, {
|
||||
strict: true,
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
allowAutomaticCrossSigningReset: false,
|
||||
});
|
||||
|
||||
expect(deps.recoveryKeyStore.bootstrapSecretStorageWithRecoveryKey).toHaveBeenCalledWith(
|
||||
crypto,
|
||||
{
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
forceNewSecretStorage: true,
|
||||
},
|
||||
);
|
||||
expect(bootstrapCrossSigning).toHaveBeenCalledTimes(2);
|
||||
expectSecretStorageRepairRetry(deps, crypto, bootstrapCrossSigning);
|
||||
});
|
||||
|
||||
it("fails in strict mode when cross-signing keys are still unpublished", async () => {
|
||||
@@ -264,9 +297,8 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
});
|
||||
|
||||
it("uses password UIA fallback when null and dummy auth fail", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const bootstrapCrossSigning = vi.fn(async () => {});
|
||||
const crypto = createCryptoApi({
|
||||
const { bootstrapper, crypto } = createBootstrapperHarness({
|
||||
bootstrapCrossSigning,
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
@@ -274,9 +306,6 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
isVerified: () => true,
|
||||
})),
|
||||
});
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
);
|
||||
|
||||
await bootstrapper.bootstrap(crypto);
|
||||
|
||||
@@ -321,12 +350,11 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
});
|
||||
|
||||
it("resets cross-signing when first bootstrap attempt throws", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const bootstrapCrossSigning = vi
|
||||
.fn<() => Promise<void>>()
|
||||
.mockRejectedValueOnce(new Error("first attempt failed"))
|
||||
.mockResolvedValueOnce(undefined);
|
||||
const crypto = createCryptoApi({
|
||||
const { bootstrapper, crypto } = createBootstrapperHarness({
|
||||
bootstrapCrossSigning,
|
||||
isCrossSigningReady: vi.fn(async () => true),
|
||||
userHasCrossSigningKeys: vi.fn(async () => true),
|
||||
@@ -334,9 +362,6 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
isVerified: () => true,
|
||||
})),
|
||||
});
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
);
|
||||
|
||||
await bootstrapper.bootstrap(crypto);
|
||||
|
||||
@@ -418,32 +443,13 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
});
|
||||
|
||||
it("tracks incoming verification requests from other users", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
const listeners = new Map<string, (...args: unknown[]) => void>();
|
||||
const crypto = createCryptoApi({
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
})),
|
||||
on: vi.fn((eventName: string, listener: (...args: unknown[]) => void) => {
|
||||
listeners.set(eventName, listener);
|
||||
}),
|
||||
});
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
);
|
||||
|
||||
await bootstrapper.bootstrap(crypto);
|
||||
|
||||
const { deps, listener } = await bootstrapWithVerificationRequestListener();
|
||||
const verificationRequest = {
|
||||
otherUserId: "@alice:example.org",
|
||||
isSelfVerification: false,
|
||||
initiatedByMe: false,
|
||||
accept: vi.fn(async () => {}),
|
||||
};
|
||||
const listener = Array.from(listeners.entries()).find(([eventName]) =>
|
||||
eventName.toLowerCase().includes("verificationrequest"),
|
||||
)?.[1];
|
||||
expect(listener).toBeTypeOf("function");
|
||||
await listener?.(verificationRequest);
|
||||
|
||||
expect(deps.verificationManager.trackVerificationRequest).toHaveBeenCalledWith(
|
||||
@@ -453,24 +459,20 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
});
|
||||
|
||||
it("does not touch request state when tracking summary throws", async () => {
|
||||
const deps = createBootstrapperDeps();
|
||||
deps.verificationManager.trackVerificationRequest = vi.fn(() => {
|
||||
throw new Error("summary failure");
|
||||
const { listener } = await bootstrapWithVerificationRequestListener({
|
||||
deps: {
|
||||
verificationManager: {
|
||||
trackVerificationRequest: vi.fn(() => {
|
||||
throw new Error("summary failure");
|
||||
}),
|
||||
},
|
||||
},
|
||||
crypto: {
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
})),
|
||||
},
|
||||
});
|
||||
const listeners = new Map<string, (...args: unknown[]) => void>();
|
||||
const crypto = createCryptoApi({
|
||||
getDeviceVerificationStatus: vi.fn(async () => ({
|
||||
isVerified: () => true,
|
||||
})),
|
||||
on: vi.fn((eventName: string, listener: (...args: unknown[]) => void) => {
|
||||
listeners.set(eventName, listener);
|
||||
}),
|
||||
});
|
||||
const bootstrapper = new MatrixCryptoBootstrapper(
|
||||
deps as unknown as MatrixCryptoBootstrapperDeps<MatrixRawEvent>,
|
||||
);
|
||||
|
||||
await bootstrapper.bootstrap(crypto);
|
||||
|
||||
const verificationRequest = {
|
||||
otherUserId: "@alice:example.org",
|
||||
@@ -478,10 +480,6 @@ describe("MatrixCryptoBootstrapper", () => {
|
||||
initiatedByMe: false,
|
||||
accept: vi.fn(async () => {}),
|
||||
};
|
||||
const listener = Array.from(listeners.entries()).find(([eventName]) =>
|
||||
eventName.toLowerCase().includes("verificationrequest"),
|
||||
)?.[1];
|
||||
expect(listener).toBeTypeOf("function");
|
||||
await listener?.(verificationRequest);
|
||||
|
||||
expect(verificationRequest.accept).not.toHaveBeenCalled();
|
||||
|
||||
@@ -3,35 +3,69 @@ import { createMatrixCryptoFacade } from "./crypto-facade.js";
|
||||
import type { MatrixRecoveryKeyStore } from "./recovery-key-store.js";
|
||||
import type { MatrixVerificationManager } from "./verification-manager.js";
|
||||
|
||||
type MatrixCryptoFacadeDeps = Parameters<typeof createMatrixCryptoFacade>[0];
|
||||
|
||||
function createVerificationManagerMock(
|
||||
overrides: Partial<MatrixVerificationManager> = {},
|
||||
): MatrixVerificationManager {
|
||||
return {
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
listVerifications: vi.fn(async () => []),
|
||||
ensureVerificationDmTracked: vi.fn(async () => null),
|
||||
requestVerification: vi.fn(),
|
||||
acceptVerification: vi.fn(),
|
||||
cancelVerification: vi.fn(),
|
||||
startVerification: vi.fn(),
|
||||
generateVerificationQr: vi.fn(),
|
||||
scanVerificationQr: vi.fn(),
|
||||
confirmVerificationSas: vi.fn(),
|
||||
mismatchVerificationSas: vi.fn(),
|
||||
confirmVerificationReciprocateQr: vi.fn(),
|
||||
getVerificationSas: vi.fn(),
|
||||
...overrides,
|
||||
} as unknown as MatrixVerificationManager;
|
||||
}
|
||||
|
||||
function createRecoveryKeyStoreMock(
|
||||
summary: ReturnType<MatrixRecoveryKeyStore["getRecoveryKeySummary"]> = null,
|
||||
): MatrixRecoveryKeyStore {
|
||||
return {
|
||||
getRecoveryKeySummary: vi.fn(() => summary),
|
||||
} as unknown as MatrixRecoveryKeyStore;
|
||||
}
|
||||
|
||||
function createFacadeHarness(params?: {
|
||||
client?: Partial<MatrixCryptoFacadeDeps["client"]>;
|
||||
verificationManager?: Partial<MatrixVerificationManager>;
|
||||
recoveryKeySummary?: ReturnType<MatrixRecoveryKeyStore["getRecoveryKeySummary"]>;
|
||||
getRoomStateEvent?: MatrixCryptoFacadeDeps["getRoomStateEvent"];
|
||||
downloadContent?: MatrixCryptoFacadeDeps["downloadContent"];
|
||||
}) {
|
||||
const getRoomStateEvent: MatrixCryptoFacadeDeps["getRoomStateEvent"] =
|
||||
params?.getRoomStateEvent ?? (async () => ({}));
|
||||
const downloadContent: MatrixCryptoFacadeDeps["downloadContent"] =
|
||||
params?.downloadContent ?? (async () => Buffer.alloc(0));
|
||||
const facade = createMatrixCryptoFacade({
|
||||
client: {
|
||||
getRoom: params?.client?.getRoom ?? (() => null),
|
||||
getCrypto: params?.client?.getCrypto ?? (() => undefined),
|
||||
},
|
||||
verificationManager: createVerificationManagerMock(params?.verificationManager),
|
||||
recoveryKeyStore: createRecoveryKeyStoreMock(params?.recoveryKeySummary ?? null),
|
||||
getRoomStateEvent,
|
||||
downloadContent,
|
||||
});
|
||||
return { facade, getRoomStateEvent, downloadContent };
|
||||
}
|
||||
|
||||
describe("createMatrixCryptoFacade", () => {
|
||||
it("detects encrypted rooms from cached room state", async () => {
|
||||
const facade = createMatrixCryptoFacade({
|
||||
const { facade } = createFacadeHarness({
|
||||
client: {
|
||||
getRoom: () => ({
|
||||
hasEncryptionStateEvent: () => true,
|
||||
}),
|
||||
getCrypto: () => undefined,
|
||||
},
|
||||
verificationManager: {
|
||||
requestOwnUserVerification: vi.fn(),
|
||||
listVerifications: vi.fn(async () => []),
|
||||
ensureVerificationDmTracked: vi.fn(async () => null),
|
||||
requestVerification: vi.fn(),
|
||||
acceptVerification: vi.fn(),
|
||||
cancelVerification: vi.fn(),
|
||||
startVerification: vi.fn(),
|
||||
generateVerificationQr: vi.fn(),
|
||||
scanVerificationQr: vi.fn(),
|
||||
confirmVerificationSas: vi.fn(),
|
||||
mismatchVerificationSas: vi.fn(),
|
||||
confirmVerificationReciprocateQr: vi.fn(),
|
||||
getVerificationSas: vi.fn(),
|
||||
} as unknown as MatrixVerificationManager,
|
||||
recoveryKeyStore: {
|
||||
getRecoveryKeySummary: vi.fn(() => null),
|
||||
} as unknown as MatrixRecoveryKeyStore,
|
||||
getRoomStateEvent: vi.fn(async () => ({ algorithm: "m.megolm.v1.aes-sha2" })),
|
||||
downloadContent: vi.fn(async () => Buffer.alloc(0)),
|
||||
});
|
||||
|
||||
await expect(facade.isRoomEncrypted("!room:example.org")).resolves.toBe(true);
|
||||
@@ -41,33 +75,13 @@ describe("createMatrixCryptoFacade", () => {
|
||||
const getRoomStateEvent = vi.fn(async () => ({
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
}));
|
||||
const facade = createMatrixCryptoFacade({
|
||||
const { facade } = createFacadeHarness({
|
||||
client: {
|
||||
getRoom: () => ({
|
||||
hasEncryptionStateEvent: () => false,
|
||||
}),
|
||||
getCrypto: () => undefined,
|
||||
},
|
||||
verificationManager: {
|
||||
requestOwnUserVerification: vi.fn(),
|
||||
listVerifications: vi.fn(async () => []),
|
||||
ensureVerificationDmTracked: vi.fn(async () => null),
|
||||
requestVerification: vi.fn(),
|
||||
acceptVerification: vi.fn(),
|
||||
cancelVerification: vi.fn(),
|
||||
startVerification: vi.fn(),
|
||||
generateVerificationQr: vi.fn(),
|
||||
scanVerificationQr: vi.fn(),
|
||||
confirmVerificationSas: vi.fn(),
|
||||
mismatchVerificationSas: vi.fn(),
|
||||
confirmVerificationReciprocateQr: vi.fn(),
|
||||
getVerificationSas: vi.fn(),
|
||||
} as unknown as MatrixVerificationManager,
|
||||
recoveryKeyStore: {
|
||||
getRecoveryKeySummary: vi.fn(() => null),
|
||||
} as unknown as MatrixRecoveryKeyStore,
|
||||
getRoomStateEvent,
|
||||
downloadContent: vi.fn(async () => Buffer.alloc(0)),
|
||||
});
|
||||
|
||||
await expect(facade.isRoomEncrypted("!room:example.org")).resolves.toBe(true);
|
||||
@@ -92,31 +106,15 @@ describe("createMatrixCryptoFacade", () => {
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
}));
|
||||
const facade = createMatrixCryptoFacade({
|
||||
const { facade } = createFacadeHarness({
|
||||
client: {
|
||||
getRoom: () => null,
|
||||
getCrypto: () => crypto,
|
||||
},
|
||||
verificationManager: {
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
listVerifications: vi.fn(async () => []),
|
||||
ensureVerificationDmTracked: vi.fn(async () => null),
|
||||
requestVerification,
|
||||
acceptVerification: vi.fn(),
|
||||
cancelVerification: vi.fn(),
|
||||
startVerification: vi.fn(),
|
||||
generateVerificationQr: vi.fn(),
|
||||
scanVerificationQr: vi.fn(),
|
||||
confirmVerificationSas: vi.fn(),
|
||||
mismatchVerificationSas: vi.fn(),
|
||||
confirmVerificationReciprocateQr: vi.fn(),
|
||||
getVerificationSas: vi.fn(),
|
||||
} as unknown as MatrixVerificationManager,
|
||||
recoveryKeyStore: {
|
||||
getRecoveryKeySummary: vi.fn(() => ({ keyId: "KEY" })),
|
||||
} as unknown as MatrixRecoveryKeyStore,
|
||||
getRoomStateEvent: vi.fn(async () => ({})),
|
||||
downloadContent: vi.fn(async () => Buffer.alloc(0)),
|
||||
},
|
||||
recoveryKeySummary: { keyId: "KEY" },
|
||||
});
|
||||
|
||||
const result = await facade.requestVerification({
|
||||
@@ -174,32 +172,14 @@ describe("createMatrixCryptoFacade", () => {
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
findVerificationRequestDMInProgress: vi.fn(() => request),
|
||||
};
|
||||
const facade = createMatrixCryptoFacade({
|
||||
const { facade } = createFacadeHarness({
|
||||
client: {
|
||||
getRoom: () => null,
|
||||
getCrypto: () => crypto,
|
||||
},
|
||||
verificationManager: {
|
||||
trackVerificationRequest,
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
listVerifications: vi.fn(async () => []),
|
||||
ensureVerificationDmTracked: vi.fn(async () => null),
|
||||
requestVerification: vi.fn(),
|
||||
acceptVerification: vi.fn(),
|
||||
cancelVerification: vi.fn(),
|
||||
startVerification: vi.fn(),
|
||||
generateVerificationQr: vi.fn(),
|
||||
scanVerificationQr: vi.fn(),
|
||||
confirmVerificationSas: vi.fn(),
|
||||
mismatchVerificationSas: vi.fn(),
|
||||
confirmVerificationReciprocateQr: vi.fn(),
|
||||
getVerificationSas: vi.fn(),
|
||||
} as unknown as MatrixVerificationManager,
|
||||
recoveryKeyStore: {
|
||||
getRecoveryKeySummary: vi.fn(() => null),
|
||||
} as unknown as MatrixRecoveryKeyStore,
|
||||
getRoomStateEvent: vi.fn(async () => ({})),
|
||||
downloadContent: vi.fn(async () => Buffer.alloc(0)),
|
||||
},
|
||||
});
|
||||
|
||||
const summary = await facade.ensureVerificationDmTracked({
|
||||
|
||||
@@ -4,13 +4,85 @@ import path from "node:path";
|
||||
import { encodeRecoveryKey } from "matrix-js-sdk/lib/crypto-api/recovery-key.js";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { MatrixRecoveryKeyStore } from "./recovery-key-store.js";
|
||||
import type { MatrixCryptoBootstrapApi } from "./types.js";
|
||||
import type { MatrixCryptoBootstrapApi, MatrixSecretStorageStatus } from "./types.js";
|
||||
|
||||
function createTempRecoveryKeyPath(): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "matrix-recovery-key-store-"));
|
||||
return path.join(dir, "recovery-key.json");
|
||||
}
|
||||
|
||||
function createGeneratedRecoveryKey(params: {
|
||||
keyId: string;
|
||||
name: string;
|
||||
bytes: number[];
|
||||
encodedPrivateKey: string;
|
||||
}) {
|
||||
return {
|
||||
keyId: params.keyId,
|
||||
keyInfo: { name: params.name },
|
||||
privateKey: new Uint8Array(params.bytes),
|
||||
encodedPrivateKey: params.encodedPrivateKey,
|
||||
};
|
||||
}
|
||||
|
||||
function createBootstrapSecretStorageMock(errorMessage?: string) {
|
||||
return vi.fn(
|
||||
async (opts?: {
|
||||
setupNewSecretStorage?: boolean;
|
||||
createSecretStorageKey?: () => Promise<unknown>;
|
||||
}) => {
|
||||
if (opts?.setupNewSecretStorage || !errorMessage) {
|
||||
await opts?.createSecretStorageKey?.();
|
||||
return;
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function createRecoveryKeyCrypto(params: {
|
||||
bootstrapSecretStorage: ReturnType<typeof vi.fn>;
|
||||
createRecoveryKeyFromPassphrase: ReturnType<typeof vi.fn>;
|
||||
status: MatrixSecretStorageStatus;
|
||||
}): MatrixCryptoBootstrapApi {
|
||||
return {
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage: params.bootstrapSecretStorage,
|
||||
createRecoveryKeyFromPassphrase: params.createRecoveryKeyFromPassphrase,
|
||||
getSecretStorageStatus: vi.fn(async () => params.status),
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
} as unknown as MatrixCryptoBootstrapApi;
|
||||
}
|
||||
|
||||
async function runSecretStorageBootstrapScenario(params: {
|
||||
generated: ReturnType<typeof createGeneratedRecoveryKey>;
|
||||
status: MatrixSecretStorageStatus;
|
||||
allowSecretStorageRecreateWithoutRecoveryKey?: boolean;
|
||||
firstBootstrapError?: string;
|
||||
}) {
|
||||
const recoveryKeyPath = createTempRecoveryKeyPath();
|
||||
const store = new MatrixRecoveryKeyStore(recoveryKeyPath);
|
||||
const createRecoveryKeyFromPassphrase = vi.fn(async () => params.generated);
|
||||
const bootstrapSecretStorage = createBootstrapSecretStorageMock(params.firstBootstrapError);
|
||||
const crypto = createRecoveryKeyCrypto({
|
||||
bootstrapSecretStorage,
|
||||
createRecoveryKeyFromPassphrase,
|
||||
status: params.status,
|
||||
});
|
||||
|
||||
await store.bootstrapSecretStorageWithRecoveryKey(crypto, {
|
||||
allowSecretStorageRecreateWithoutRecoveryKey:
|
||||
params.allowSecretStorageRecreateWithoutRecoveryKey ?? false,
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
createRecoveryKeyFromPassphrase,
|
||||
bootstrapSecretStorage,
|
||||
};
|
||||
}
|
||||
|
||||
describe("MatrixRecoveryKeyStore", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
@@ -65,30 +137,16 @@ describe("MatrixRecoveryKeyStore", () => {
|
||||
});
|
||||
|
||||
it("creates and persists a recovery key when secret storage is missing", async () => {
|
||||
const recoveryKeyPath = createTempRecoveryKeyPath();
|
||||
const store = new MatrixRecoveryKeyStore(recoveryKeyPath);
|
||||
const generated = {
|
||||
keyId: "GENERATED",
|
||||
keyInfo: { name: "generated" },
|
||||
privateKey: new Uint8Array([5, 6, 7, 8]),
|
||||
encodedPrivateKey: "encoded-generated-key", // pragma: allowlist secret
|
||||
};
|
||||
const createRecoveryKeyFromPassphrase = vi.fn(async () => generated);
|
||||
const bootstrapSecretStorage = vi.fn(
|
||||
async (opts?: { createSecretStorageKey?: () => Promise<unknown> }) => {
|
||||
await opts?.createSecretStorageKey?.();
|
||||
},
|
||||
);
|
||||
const crypto = {
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage,
|
||||
createRecoveryKeyFromPassphrase,
|
||||
getSecretStorageStatus: vi.fn(async () => ({ ready: false, defaultKeyId: null })),
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
} as unknown as MatrixCryptoBootstrapApi;
|
||||
|
||||
await store.bootstrapSecretStorageWithRecoveryKey(crypto);
|
||||
const { store, createRecoveryKeyFromPassphrase, bootstrapSecretStorage } =
|
||||
await runSecretStorageBootstrapScenario({
|
||||
generated: createGeneratedRecoveryKey({
|
||||
keyId: "GENERATED",
|
||||
name: "generated",
|
||||
bytes: [5, 6, 7, 8],
|
||||
encodedPrivateKey: "encoded-generated-key", // pragma: allowlist secret
|
||||
}),
|
||||
status: { ready: false, defaultKeyId: null },
|
||||
});
|
||||
|
||||
expect(createRecoveryKeyFromPassphrase).toHaveBeenCalledTimes(1);
|
||||
expect(bootstrapSecretStorage).toHaveBeenCalledWith(
|
||||
@@ -138,30 +196,16 @@ describe("MatrixRecoveryKeyStore", () => {
|
||||
});
|
||||
|
||||
it("recreates secret storage when default key exists but is not usable locally", async () => {
|
||||
const recoveryKeyPath = createTempRecoveryKeyPath();
|
||||
const store = new MatrixRecoveryKeyStore(recoveryKeyPath);
|
||||
const generated = {
|
||||
keyId: "RECOVERED",
|
||||
keyInfo: { name: "recovered" },
|
||||
privateKey: new Uint8Array([1, 1, 2, 3]),
|
||||
encodedPrivateKey: "encoded-recovered-key", // pragma: allowlist secret
|
||||
};
|
||||
const createRecoveryKeyFromPassphrase = vi.fn(async () => generated);
|
||||
const bootstrapSecretStorage = vi.fn(
|
||||
async (opts?: { createSecretStorageKey?: () => Promise<unknown> }) => {
|
||||
await opts?.createSecretStorageKey?.();
|
||||
},
|
||||
);
|
||||
const crypto = {
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage,
|
||||
createRecoveryKeyFromPassphrase,
|
||||
getSecretStorageStatus: vi.fn(async () => ({ ready: false, defaultKeyId: "LEGACY" })),
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
} as unknown as MatrixCryptoBootstrapApi;
|
||||
|
||||
await store.bootstrapSecretStorageWithRecoveryKey(crypto);
|
||||
const { store, createRecoveryKeyFromPassphrase, bootstrapSecretStorage } =
|
||||
await runSecretStorageBootstrapScenario({
|
||||
generated: createGeneratedRecoveryKey({
|
||||
keyId: "RECOVERED",
|
||||
name: "recovered",
|
||||
bytes: [1, 1, 2, 3],
|
||||
encodedPrivateKey: "encoded-recovered-key", // pragma: allowlist secret
|
||||
}),
|
||||
status: { ready: false, defaultKeyId: "LEGACY" },
|
||||
});
|
||||
|
||||
expect(createRecoveryKeyFromPassphrase).toHaveBeenCalledTimes(1);
|
||||
expect(bootstrapSecretStorage).toHaveBeenCalledWith(
|
||||
@@ -176,43 +220,22 @@ describe("MatrixRecoveryKeyStore", () => {
|
||||
});
|
||||
|
||||
it("recreates secret storage during explicit bootstrap when the server key exists but no local recovery key is available", async () => {
|
||||
const recoveryKeyPath = createTempRecoveryKeyPath();
|
||||
const store = new MatrixRecoveryKeyStore(recoveryKeyPath);
|
||||
const generated = {
|
||||
keyId: "REPAIRED",
|
||||
keyInfo: { name: "repaired" },
|
||||
privateKey: new Uint8Array([7, 7, 8, 9]),
|
||||
encodedPrivateKey: "encoded-repaired-key", // pragma: allowlist secret
|
||||
};
|
||||
const createRecoveryKeyFromPassphrase = vi.fn(async () => generated);
|
||||
const bootstrapSecretStorage = vi.fn(
|
||||
async (opts?: {
|
||||
setupNewSecretStorage?: boolean;
|
||||
createSecretStorageKey?: () => Promise<unknown>;
|
||||
}) => {
|
||||
if (opts?.setupNewSecretStorage) {
|
||||
await opts.createSecretStorageKey?.();
|
||||
return;
|
||||
}
|
||||
throw new Error("getSecretStorageKey callback returned falsey");
|
||||
},
|
||||
);
|
||||
const crypto = {
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage,
|
||||
createRecoveryKeyFromPassphrase,
|
||||
getSecretStorageStatus: vi.fn(async () => ({
|
||||
ready: true,
|
||||
defaultKeyId: "LEGACY",
|
||||
secretStorageKeyValidityMap: { LEGACY: true },
|
||||
})),
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
} as unknown as MatrixCryptoBootstrapApi;
|
||||
|
||||
await store.bootstrapSecretStorageWithRecoveryKey(crypto, {
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
});
|
||||
const { store, createRecoveryKeyFromPassphrase, bootstrapSecretStorage } =
|
||||
await runSecretStorageBootstrapScenario({
|
||||
generated: createGeneratedRecoveryKey({
|
||||
keyId: "REPAIRED",
|
||||
name: "repaired",
|
||||
bytes: [7, 7, 8, 9],
|
||||
encodedPrivateKey: "encoded-repaired-key", // pragma: allowlist secret
|
||||
}),
|
||||
status: {
|
||||
ready: true,
|
||||
defaultKeyId: "LEGACY",
|
||||
secretStorageKeyValidityMap: { LEGACY: true },
|
||||
},
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
firstBootstrapError: "getSecretStorageKey callback returned falsey",
|
||||
});
|
||||
|
||||
expect(createRecoveryKeyFromPassphrase).toHaveBeenCalledTimes(1);
|
||||
expect(bootstrapSecretStorage).toHaveBeenCalledTimes(2);
|
||||
@@ -228,43 +251,22 @@ describe("MatrixRecoveryKeyStore", () => {
|
||||
});
|
||||
|
||||
it("recreates secret storage during explicit bootstrap when decrypting a stored secret fails with bad MAC", async () => {
|
||||
const recoveryKeyPath = createTempRecoveryKeyPath();
|
||||
const store = new MatrixRecoveryKeyStore(recoveryKeyPath);
|
||||
const generated = {
|
||||
keyId: "REPAIRED",
|
||||
keyInfo: { name: "repaired" },
|
||||
privateKey: new Uint8Array([7, 7, 8, 9]),
|
||||
encodedPrivateKey: "encoded-repaired-key", // pragma: allowlist secret
|
||||
};
|
||||
const createRecoveryKeyFromPassphrase = vi.fn(async () => generated);
|
||||
const bootstrapSecretStorage = vi.fn(
|
||||
async (opts?: {
|
||||
setupNewSecretStorage?: boolean;
|
||||
createSecretStorageKey?: () => Promise<unknown>;
|
||||
}) => {
|
||||
if (opts?.setupNewSecretStorage) {
|
||||
await opts.createSecretStorageKey?.();
|
||||
return;
|
||||
}
|
||||
throw new Error("Error decrypting secret m.cross_signing.master: bad MAC");
|
||||
},
|
||||
);
|
||||
const crypto = {
|
||||
on: vi.fn(),
|
||||
bootstrapCrossSigning: vi.fn(async () => {}),
|
||||
bootstrapSecretStorage,
|
||||
createRecoveryKeyFromPassphrase,
|
||||
getSecretStorageStatus: vi.fn(async () => ({
|
||||
ready: true,
|
||||
defaultKeyId: "LEGACY",
|
||||
secretStorageKeyValidityMap: { LEGACY: true },
|
||||
})),
|
||||
requestOwnUserVerification: vi.fn(async () => null),
|
||||
} as unknown as MatrixCryptoBootstrapApi;
|
||||
|
||||
await store.bootstrapSecretStorageWithRecoveryKey(crypto, {
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
});
|
||||
const { createRecoveryKeyFromPassphrase, bootstrapSecretStorage } =
|
||||
await runSecretStorageBootstrapScenario({
|
||||
generated: createGeneratedRecoveryKey({
|
||||
keyId: "REPAIRED",
|
||||
name: "repaired",
|
||||
bytes: [7, 7, 8, 9],
|
||||
encodedPrivateKey: "encoded-repaired-key", // pragma: allowlist secret
|
||||
}),
|
||||
status: {
|
||||
ready: true,
|
||||
defaultKeyId: "LEGACY",
|
||||
secretStorageKeyValidityMap: { LEGACY: true },
|
||||
},
|
||||
allowSecretStorageRecreateWithoutRecoveryKey: true,
|
||||
firstBootstrapError: "Error decrypting secret m.cross_signing.master: bad MAC",
|
||||
});
|
||||
|
||||
expect(createRecoveryKeyFromPassphrase).toHaveBeenCalledTimes(1);
|
||||
expect(bootstrapSecretStorage).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -86,6 +86,65 @@ class MockVerificationRequest extends EventEmitter implements MatrixVerification
|
||||
generateQRCode = vi.fn(async () => new Uint8ClampedArray([1, 2, 3]));
|
||||
}
|
||||
|
||||
function createSasVerifierFixture(params: {
|
||||
decimal: [number, number, number];
|
||||
emoji: [string, string][];
|
||||
verifyImpl?: () => Promise<void>;
|
||||
}) {
|
||||
const confirm = vi.fn(async () => {});
|
||||
const mismatch = vi.fn();
|
||||
const cancel = vi.fn();
|
||||
const verify = vi.fn(params.verifyImpl ?? (async () => {}));
|
||||
return {
|
||||
confirm,
|
||||
mismatch,
|
||||
verify,
|
||||
verifier: new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: params.decimal,
|
||||
emoji: params.emoji,
|
||||
},
|
||||
confirm,
|
||||
mismatch,
|
||||
cancel,
|
||||
},
|
||||
null,
|
||||
verify,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function createReadyRequestWithoutVerifier(params: {
|
||||
transactionId: string;
|
||||
isSelfVerification: boolean;
|
||||
verifier: MatrixVerifierLike;
|
||||
}) {
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: params.transactionId,
|
||||
initiatedByMe: false,
|
||||
isSelfVerification: params.isSelfVerification,
|
||||
verifier: undefined,
|
||||
});
|
||||
request.startVerification = vi.fn(async (_method: string) => {
|
||||
request.phase = VerificationPhase.Started;
|
||||
request.verifier = params.verifier;
|
||||
return params.verifier;
|
||||
});
|
||||
return request;
|
||||
}
|
||||
|
||||
function expectTrackedSas(
|
||||
manager: MatrixVerificationManager,
|
||||
trackedId: string,
|
||||
decimal: [number, number, number],
|
||||
) {
|
||||
const summary = manager.listVerifications().find((item) => item.id === trackedId);
|
||||
expect(summary?.hasSas).toBe(true);
|
||||
expect(summary?.sas?.decimal).toEqual(decimal);
|
||||
expect(manager.getVerificationSas(trackedId).decimal).toEqual(decimal);
|
||||
}
|
||||
|
||||
describe("MatrixVerificationManager", () => {
|
||||
it("handles rust verification requests whose methods getter throws", () => {
|
||||
const manager = new MatrixVerificationManager();
|
||||
@@ -173,24 +232,14 @@ describe("MatrixVerificationManager", () => {
|
||||
});
|
||||
|
||||
it("auto-starts an incoming verifier exposed via request change events", async () => {
|
||||
const verify = vi.fn(async () => {});
|
||||
const verifier = new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: [6158, 1986, 3513],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["globe", "Globe"],
|
||||
["horse", "Horse"],
|
||||
],
|
||||
},
|
||||
confirm: vi.fn(async () => {}),
|
||||
mismatch: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
},
|
||||
null,
|
||||
verify,
|
||||
);
|
||||
const { verifier, verify } = createSasVerifierFixture({
|
||||
decimal: [6158, 1986, 3513],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["globe", "Globe"],
|
||||
["horse", "Horse"],
|
||||
],
|
||||
});
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-incoming-change",
|
||||
verifier: undefined,
|
||||
@@ -204,31 +253,18 @@ describe("MatrixVerificationManager", () => {
|
||||
await vi.waitFor(() => {
|
||||
expect(verify).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const summary = manager.listVerifications().find((item) => item.id === tracked.id);
|
||||
expect(summary?.hasSas).toBe(true);
|
||||
expect(summary?.sas?.decimal).toEqual([6158, 1986, 3513]);
|
||||
expect(manager.getVerificationSas(tracked.id).decimal).toEqual([6158, 1986, 3513]);
|
||||
expectTrackedSas(manager, tracked.id, [6158, 1986, 3513]);
|
||||
});
|
||||
|
||||
it("emits summary updates when SAS becomes available", async () => {
|
||||
const verify = vi.fn(async () => {});
|
||||
const verifier = new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: [6158, 1986, 3513],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["globe", "Globe"],
|
||||
["horse", "Horse"],
|
||||
],
|
||||
},
|
||||
confirm: vi.fn(async () => {}),
|
||||
mismatch: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
},
|
||||
null,
|
||||
verify,
|
||||
);
|
||||
const { verifier } = createSasVerifierFixture({
|
||||
decimal: [6158, 1986, 3513],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["globe", "Globe"],
|
||||
["horse", "Horse"],
|
||||
],
|
||||
});
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-summary-listener",
|
||||
roomId: "!dm:example.org",
|
||||
@@ -257,34 +293,18 @@ describe("MatrixVerificationManager", () => {
|
||||
});
|
||||
|
||||
it("does not auto-start non-self inbound SAS when request becomes ready without a verifier", async () => {
|
||||
const verify = vi.fn(async () => {});
|
||||
const verifier = new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: [1234, 5678, 9012],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["rocket", "Rocket"],
|
||||
["butterfly", "Butterfly"],
|
||||
],
|
||||
},
|
||||
confirm: vi.fn(async () => {}),
|
||||
mismatch: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
},
|
||||
null,
|
||||
verify,
|
||||
);
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-no-auto-start-dm-sas",
|
||||
initiatedByMe: false,
|
||||
isSelfVerification: false,
|
||||
verifier: undefined,
|
||||
const { verifier, verify } = createSasVerifierFixture({
|
||||
decimal: [1234, 5678, 9012],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["rocket", "Rocket"],
|
||||
["butterfly", "Butterfly"],
|
||||
],
|
||||
});
|
||||
request.startVerification = vi.fn(async (_method: string) => {
|
||||
request.phase = VerificationPhase.Started;
|
||||
request.verifier = verifier;
|
||||
return verifier;
|
||||
const request = createReadyRequestWithoutVerifier({
|
||||
transactionId: "txn-no-auto-start-dm-sas",
|
||||
isSelfVerification: false,
|
||||
verifier,
|
||||
});
|
||||
const manager = new MatrixVerificationManager();
|
||||
const tracked = manager.trackVerificationRequest(request);
|
||||
@@ -303,34 +323,18 @@ describe("MatrixVerificationManager", () => {
|
||||
});
|
||||
|
||||
it("auto-starts self verification SAS when request becomes ready without a verifier", async () => {
|
||||
const verify = vi.fn(async () => {});
|
||||
const verifier = new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: [1234, 5678, 9012],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["rocket", "Rocket"],
|
||||
["butterfly", "Butterfly"],
|
||||
],
|
||||
},
|
||||
confirm: vi.fn(async () => {}),
|
||||
mismatch: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
},
|
||||
null,
|
||||
verify,
|
||||
);
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-auto-start-self-sas",
|
||||
initiatedByMe: false,
|
||||
isSelfVerification: true,
|
||||
verifier: undefined,
|
||||
const { verifier, verify } = createSasVerifierFixture({
|
||||
decimal: [1234, 5678, 9012],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["rocket", "Rocket"],
|
||||
["butterfly", "Butterfly"],
|
||||
],
|
||||
});
|
||||
request.startVerification = vi.fn(async (_method: string) => {
|
||||
request.phase = VerificationPhase.Started;
|
||||
request.verifier = verifier;
|
||||
return verifier;
|
||||
const request = createReadyRequestWithoutVerifier({
|
||||
transactionId: "txn-auto-start-self-sas",
|
||||
isSelfVerification: true,
|
||||
verifier,
|
||||
});
|
||||
const manager = new MatrixVerificationManager();
|
||||
const tracked = manager.trackVerificationRequest(request);
|
||||
@@ -344,10 +348,7 @@ describe("MatrixVerificationManager", () => {
|
||||
await vi.waitFor(() => {
|
||||
expect(verify).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const summary = manager.listVerifications().find((item) => item.id === tracked.id);
|
||||
expect(summary?.hasSas).toBe(true);
|
||||
expect(summary?.sas?.decimal).toEqual([1234, 5678, 9012]);
|
||||
expect(manager.getVerificationSas(tracked.id).decimal).toEqual([1234, 5678, 9012]);
|
||||
expectTrackedSas(manager, tracked.id, [1234, 5678, 9012]);
|
||||
});
|
||||
|
||||
it("auto-accepts incoming verification requests only once per transaction", async () => {
|
||||
|
||||
Reference in New Issue
Block a user