mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 22:21:37 +00:00
Matrix-js: sync with main plugin-loading standards
This commit is contained in:
347
extensions/matrix-js/src/matrix/sdk/verification-manager.test.ts
Normal file
347
extensions/matrix-js/src/matrix/sdk/verification-manager.test.ts
Normal file
@@ -0,0 +1,347 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import {
|
||||
VerificationPhase,
|
||||
VerificationRequestEvent,
|
||||
} from "matrix-js-sdk/lib/crypto-api/verification.js";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
MatrixVerificationManager,
|
||||
type MatrixShowQrCodeCallbacks,
|
||||
type MatrixShowSasCallbacks,
|
||||
type MatrixVerificationRequestLike,
|
||||
type MatrixVerifierLike,
|
||||
} from "./verification-manager.js";
|
||||
|
||||
class MockVerifier extends EventEmitter implements MatrixVerifierLike {
|
||||
constructor(
|
||||
private readonly sasCallbacks: MatrixShowSasCallbacks | null,
|
||||
private readonly qrCallbacks: MatrixShowQrCodeCallbacks | null,
|
||||
private readonly verifyImpl: () => Promise<void> = async () => {},
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
verify(): Promise<void> {
|
||||
return this.verifyImpl();
|
||||
}
|
||||
|
||||
cancel(_e: Error): void {
|
||||
void _e;
|
||||
}
|
||||
|
||||
getShowSasCallbacks(): MatrixShowSasCallbacks | null {
|
||||
return this.sasCallbacks;
|
||||
}
|
||||
|
||||
getReciprocateQrCodeCallbacks(): MatrixShowQrCodeCallbacks | null {
|
||||
return this.qrCallbacks;
|
||||
}
|
||||
}
|
||||
|
||||
class MockVerificationRequest extends EventEmitter implements MatrixVerificationRequestLike {
|
||||
transactionId?: string;
|
||||
roomId?: string;
|
||||
initiatedByMe = false;
|
||||
otherUserId = "@alice:example.org";
|
||||
otherDeviceId?: string;
|
||||
isSelfVerification = false;
|
||||
phase = VerificationPhase.Requested;
|
||||
pending = true;
|
||||
accepting = false;
|
||||
declining = false;
|
||||
methods: string[] = ["m.sas.v1"];
|
||||
chosenMethod?: string | null;
|
||||
cancellationCode?: string | null;
|
||||
verifier?: MatrixVerifierLike;
|
||||
|
||||
constructor(init?: Partial<MockVerificationRequest>) {
|
||||
super();
|
||||
Object.assign(this, init);
|
||||
}
|
||||
|
||||
accept = vi.fn(async () => {
|
||||
this.phase = VerificationPhase.Ready;
|
||||
});
|
||||
|
||||
cancel = vi.fn(async () => {
|
||||
this.phase = VerificationPhase.Cancelled;
|
||||
});
|
||||
|
||||
startVerification = vi.fn(async (_method: string) => {
|
||||
if (!this.verifier) {
|
||||
throw new Error("verifier not configured");
|
||||
}
|
||||
this.phase = VerificationPhase.Started;
|
||||
return this.verifier;
|
||||
});
|
||||
|
||||
scanQRCode = vi.fn(async (_qrCodeData: Uint8ClampedArray) => {
|
||||
if (!this.verifier) {
|
||||
throw new Error("verifier not configured");
|
||||
}
|
||||
this.phase = VerificationPhase.Started;
|
||||
return this.verifier;
|
||||
});
|
||||
|
||||
generateQRCode = vi.fn(async () => new Uint8ClampedArray([1, 2, 3]));
|
||||
}
|
||||
|
||||
describe("MatrixVerificationManager", () => {
|
||||
it("handles rust verification requests whose methods getter throws", () => {
|
||||
const manager = new MatrixVerificationManager();
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-rust-methods",
|
||||
phase: VerificationPhase.Requested,
|
||||
});
|
||||
Object.defineProperty(request, "methods", {
|
||||
get() {
|
||||
throw new Error("not implemented");
|
||||
},
|
||||
});
|
||||
|
||||
const summary = manager.trackVerificationRequest(request);
|
||||
|
||||
expect(summary.id).toBeTruthy();
|
||||
expect(summary.methods).toEqual([]);
|
||||
expect(summary.phase).toBe(VerificationPhase.Requested);
|
||||
});
|
||||
|
||||
it("reuses the same tracked id for repeated transaction IDs", () => {
|
||||
const manager = new MatrixVerificationManager();
|
||||
const first = new MockVerificationRequest({
|
||||
transactionId: "txn-1",
|
||||
phase: VerificationPhase.Requested,
|
||||
});
|
||||
const second = new MockVerificationRequest({
|
||||
transactionId: "txn-1",
|
||||
phase: VerificationPhase.Ready,
|
||||
pending: false,
|
||||
chosenMethod: "m.sas.v1",
|
||||
});
|
||||
|
||||
const firstSummary = manager.trackVerificationRequest(first);
|
||||
const secondSummary = manager.trackVerificationRequest(second);
|
||||
|
||||
expect(secondSummary.id).toBe(firstSummary.id);
|
||||
expect(secondSummary.phase).toBe(VerificationPhase.Ready);
|
||||
expect(secondSummary.pending).toBe(false);
|
||||
expect(secondSummary.chosenMethod).toBe("m.sas.v1");
|
||||
});
|
||||
|
||||
it("starts SAS verification and exposes SAS payload/callback flow", async () => {
|
||||
const confirm = vi.fn(async () => {});
|
||||
const mismatch = vi.fn();
|
||||
const verifier = new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: [111, 222, 333],
|
||||
emoji: [
|
||||
["cat", "cat"],
|
||||
["dog", "dog"],
|
||||
["fox", "fox"],
|
||||
],
|
||||
},
|
||||
confirm,
|
||||
mismatch,
|
||||
cancel: vi.fn(),
|
||||
},
|
||||
null,
|
||||
async () => {},
|
||||
);
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-2",
|
||||
verifier,
|
||||
});
|
||||
const manager = new MatrixVerificationManager();
|
||||
const tracked = manager.trackVerificationRequest(request);
|
||||
|
||||
const started = await manager.startVerification(tracked.id, "sas");
|
||||
expect(started.hasSas).toBe(true);
|
||||
expect(started.sas?.decimal).toEqual([111, 222, 333]);
|
||||
expect(started.sas?.emoji?.length).toBe(3);
|
||||
|
||||
const sas = manager.getVerificationSas(tracked.id);
|
||||
expect(sas.decimal).toEqual([111, 222, 333]);
|
||||
expect(sas.emoji?.length).toBe(3);
|
||||
|
||||
await manager.confirmVerificationSas(tracked.id);
|
||||
expect(confirm).toHaveBeenCalledTimes(2);
|
||||
|
||||
manager.mismatchVerificationSas(tracked.id);
|
||||
expect(mismatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
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 request = new MockVerificationRequest({
|
||||
transactionId: "txn-incoming-change",
|
||||
verifier: undefined,
|
||||
});
|
||||
const manager = new MatrixVerificationManager();
|
||||
const tracked = manager.trackVerificationRequest(request);
|
||||
|
||||
request.verifier = verifier;
|
||||
request.emit(VerificationRequestEvent.Change);
|
||||
|
||||
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]);
|
||||
});
|
||||
|
||||
it("auto-starts 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-auto-start-sas",
|
||||
initiatedByMe: false,
|
||||
verifier: undefined,
|
||||
});
|
||||
request.startVerification = vi.fn(async (_method: string) => {
|
||||
request.phase = VerificationPhase.Started;
|
||||
request.verifier = verifier;
|
||||
return verifier;
|
||||
});
|
||||
const manager = new MatrixVerificationManager();
|
||||
const tracked = manager.trackVerificationRequest(request);
|
||||
|
||||
request.phase = VerificationPhase.Ready;
|
||||
request.emit(VerificationRequestEvent.Change);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(request.startVerification).toHaveBeenCalledWith("m.sas.v1");
|
||||
});
|
||||
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]);
|
||||
});
|
||||
|
||||
it("auto-confirms inbound SAS when callbacks are available", async () => {
|
||||
const confirm = vi.fn(async () => {});
|
||||
const verifier = new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: [6158, 1986, 3513],
|
||||
emoji: [
|
||||
["gift", "Gift"],
|
||||
["globe", "Globe"],
|
||||
["horse", "Horse"],
|
||||
],
|
||||
},
|
||||
confirm,
|
||||
mismatch: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
},
|
||||
null,
|
||||
async () => {},
|
||||
);
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-auto-confirm",
|
||||
initiatedByMe: false,
|
||||
verifier,
|
||||
});
|
||||
const manager = new MatrixVerificationManager();
|
||||
manager.trackVerificationRequest(request);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(confirm).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not auto-confirm SAS for verifications initiated by this device", async () => {
|
||||
vi.useFakeTimers();
|
||||
const confirm = vi.fn(async () => {});
|
||||
const verifier = new MockVerifier(
|
||||
{
|
||||
sas: {
|
||||
decimal: [111, 222, 333],
|
||||
emoji: [
|
||||
["cat", "Cat"],
|
||||
["dog", "Dog"],
|
||||
["fox", "Fox"],
|
||||
],
|
||||
},
|
||||
confirm,
|
||||
mismatch: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
},
|
||||
null,
|
||||
async () => {},
|
||||
);
|
||||
const request = new MockVerificationRequest({
|
||||
transactionId: "txn-no-auto-confirm",
|
||||
initiatedByMe: true,
|
||||
verifier,
|
||||
});
|
||||
try {
|
||||
const manager = new MatrixVerificationManager();
|
||||
manager.trackVerificationRequest(request);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(20);
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("prunes stale terminal sessions during list operations", () => {
|
||||
const now = new Date("2026-02-08T15:00:00.000Z").getTime();
|
||||
const nowSpy = vi.spyOn(Date, "now");
|
||||
nowSpy.mockReturnValue(now);
|
||||
|
||||
const manager = new MatrixVerificationManager();
|
||||
manager.trackVerificationRequest(
|
||||
new MockVerificationRequest({
|
||||
transactionId: "txn-old-done",
|
||||
phase: VerificationPhase.Done,
|
||||
pending: false,
|
||||
}),
|
||||
);
|
||||
|
||||
nowSpy.mockReturnValue(now + 24 * 60 * 60 * 1000 + 1);
|
||||
const summaries = manager.listVerifications();
|
||||
|
||||
expect(summaries).toHaveLength(0);
|
||||
nowSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user