Revision control

Copy as Markdown

Other Tools

/* Any copyright is dedicated to the Public Domain.
const { startVerification } = ChromeUtils.importESModule(
);
const { VerificationMethod } = ChromeUtils.importESModule(
);
const { sinon } = ChromeUtils.importESModule(
);
const { VerificationPhase, VerifierEvent } = MatrixSDK.Crypto;
const sandbox = sinon.createSandbox();
registerCleanupFunction(() => sandbox.restore());
const CHANGE_EVENT = MatrixSDK.Crypto.VerificationRequestEvent.Change;
const EMOJI_SAS = {
emoji: [
["🐶", "dog"],
["🐱", "cat"],
],
};
const DECIMAL_SAS = {
decimal: [1234, 5678, 9012],
};
/**
* Mock of the SAS verifier, which doubles as the ShowSas event.
*/
class MockVerifier extends MatrixSDK.TypedEventEmitter {
constructor(sas) {
super();
this.sas = sas;
this.verify = sandbox.spy(() => {
Promise.resolve().then(() => {
this.emit(VerifierEvent.ShowSas, this);
});
});
this.confirm = sandbox.spy();
this.mismatch = sandbox.spy();
this.cancel = sandbox.spy();
}
}
/**
* Create a mocked verification request.
*
* @param {object} [options] - Options.
* @param {boolean} [options.initiatedByMe=false] - Did we initiate the request.
* @param {number} [options.phase=VerificationPhase.Requested] - Initial phase.
* @param {MockVerifier?} [options.verifier=null] - Initial verifier.
* @param {boolean} [options.supportsMethod=true] - Does the other party
* support SAS verification.
* @returns {object} Mocked verification request.
*/
function makeRequest({
initiatedByMe = false,
phase = VerificationPhase.Requested,
verifier = null,
supportsMethod = true,
} = {}) {
const request = new MatrixSDK.TypedEventEmitter();
request.initiatedByMe = initiatedByMe;
request.phase = phase;
request.verifier = verifier;
request.accept = sandbox.stub().callsFake(async () => {
request.phase = VerificationPhase.Ready;
});
request.otherPartySupportsMethod = sandbox
.stub()
.callsFake(() => supportsMethod);
request.startVerification = sandbox.stub().callsFake(async () => {
request.verifier = new MockVerifier(EMOJI_SAS);
request.phase = VerificationPhase.Started;
request.emit(CHANGE_EVENT);
});
return request;
}
add_task(async function test_incoming_sas() {
const request = makeRequest({ initiatedByMe: false });
const result = await startVerification(request);
deepEqual(
request.startVerification.firstCall.args,
[VerificationMethod.Sas],
"SAS is chosen for incoming requests."
);
ok(request.verifier.verify.calledOnce, "The verifier was started.");
equal(result.challenge, "🐶 🐱", "The emoji challenge is joined as text.");
equal(
result.challengeDescription,
"dog cat",
"The emoji descriptions are joined."
);
result.handleResult(true);
ok(request.verifier.confirm.calledOnce, "The challenge match was confirmed.");
result.handleResult(false);
ok(request.verifier.mismatch.calledOnce, "The mismatch was reported.");
result.cancel();
ok(request.verifier.cancel.calledOnce, "Cancellation was forwarded.");
});
add_task(async function test_incoming_cancelled_during_accept() {
const request = makeRequest({ initiatedByMe: false });
request.accept = sandbox.stub().callsFake(async () => {
request.phase = VerificationPhase.Cancelled;
request.emit(CHANGE_EVENT);
});
await Assert.rejects(
startVerification(request),
/verification aborted/,
"Cancelling during accept aborts the verification."
);
});
add_task(async function test_incoming_unsupported_method() {
const request = makeRequest({ initiatedByMe: false, supportsMethod: false });
await Assert.rejects(
startVerification(request),
/Unable to negotiate a verification method/,
"An unsupported method is rejected."
);
});
add_task(async function test_outgoing_waits_for_start() {
const request = makeRequest({ initiatedByMe: true });
// Simulate the other device starting the verification.
Promise.resolve().then(() => {
request.verifier = new MockVerifier(EMOJI_SAS);
request.phase = VerificationPhase.Started;
request.emit(CHANGE_EVENT);
}, 10);
const result = await startVerification(request);
equal(result.challenge, "🐶 🐱", "The emoji challenge is joined as text.");
ok(request.startVerification.notCalled, "The method is never chosen.");
});
add_task(async function test_outgoing_cancelled_while_waiting() {
const request = makeRequest({ initiatedByMe: true });
Promise.resolve().then(() => {
request.phase = VerificationPhase.Cancelled;
request.emit(CHANGE_EVENT);
});
await Assert.rejects(
startVerification(request),
/verification aborted/,
"Cancelling while waiting aborts the verification."
);
});
add_task(async function test_outgoing_already_cancelled() {
// Already cancelled requests must reject instead of waiting forever.
const request = makeRequest({
initiatedByMe: true,
phase: VerificationPhase.Cancelled,
});
await Assert.rejects(
startVerification(request),
/verification aborted/,
"An already cancelled request is rejected."
);
});
add_task(async function test_decimal_challenge() {
const request = makeRequest({
initiatedByMe: true,
phase: VerificationPhase.Started,
verifier: new MockVerifier(DECIMAL_SAS),
});
const result = await startVerification(request);
equal(
result.challenge,
"1234 5678 9012",
"The decimal challenge is joined as text."
);
equal(
result.challengeDescription,
undefined,
"No description for a decimal challenge."
);
});
add_task(async function test_unknown_method() {
const verifier = new MockVerifier({});
const request = makeRequest({
initiatedByMe: true,
phase: VerificationPhase.Started,
verifier,
});
await Assert.rejects(
startVerification(request),
/unknown verification method/,
"An unknown method is rejected."
);
ok(verifier.cancel.calledOnce, "The unknown method cancels the verifier.");
});