CI / test (ubuntu-latest, node 22) (push) Failing after 33m10s
CI / test (ubuntu-latest, node 24) (push) Failing after 34m41s
CI / tooling (lint, imports, typecheck) (push) Failing after 36m11s
CI / test (windows-latest, node 22) (push) Canceled after 0s
CI / test (windows-latest, node 24) (push) Canceled after 0s
67 lines
3.2 KiB
JavaScript
67 lines
3.2 KiB
JavaScript
// Provider contract tests use a fake widget and fake proof only; no real captcha or network request is performed.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { setImmediate } from 'node:timers/promises';
|
|
import { solveCaptcha } from '../../public/js/skland/captcha.js';
|
|
|
|
function fakeProvider(proof) {
|
|
const events = {};
|
|
const calls = { shown: 0, destroyed: 0, options: null };
|
|
const widget = {
|
|
onReady(fn) { events.ready = fn; }, onSuccess(fn) { events.success = fn; },
|
|
onClose(fn) { events.close = fn; }, onError(fn) { events.error = fn; },
|
|
getValidate() { return proof; },
|
|
showCaptcha() { calls.shown++; }, verify() { calls.shown++; },
|
|
destroy() { calls.destroyed++; events.close?.(); },
|
|
};
|
|
const init = (options, ready) => { calls.options = options; ready(widget); };
|
|
return { events, calls, scope: { initGeetest4: init, initGeetest: init }, load: async () => {} };
|
|
}
|
|
|
|
test('captcha v4 opens the provider widget and preserves its proof plus the upstream challenge', async () => {
|
|
const proof = { captcha_id: 'TEST', lot_number: 'TEST-LOT', captcha_output: 'TEST-PROOF', pass_token: 'TEST-PASS', gen_time: '1' };
|
|
const provider = fakeProvider(proof);
|
|
const pending = solveCaptcha({ geetestId: 'TEST', challenge: 'TEST-CHALLENGE', riskType: 'slide' }, provider);
|
|
await setImmediate();
|
|
assert.equal(provider.calls.shown, 0, 'initialization does not submit a proof');
|
|
provider.events.ready();
|
|
assert.equal(provider.calls.shown, 1);
|
|
assert.deepEqual(provider.calls.options, { captchaId: 'TEST', riskType: 'slide', product: 'bind', language: 'zho' });
|
|
provider.events.success();
|
|
assert.deepEqual(await pending, { ...proof, challenge: 'TEST-CHALLENGE' });
|
|
assert.equal(provider.calls.destroyed, 1);
|
|
});
|
|
|
|
test('captcha v3 preserves the three provider fields for the SMS retry', async () => {
|
|
const proof = { geetest_challenge: 'TEST-CH', geetest_validate: 'TEST-VALIDATE', geetest_seccode: 'TEST-SEC' };
|
|
const provider = fakeProvider(proof);
|
|
const pending = solveCaptcha({ gt: 'TEST', challenge: 'TEST-CH', new_captcha: true }, provider);
|
|
await setImmediate();
|
|
provider.events.ready();
|
|
provider.events.success();
|
|
assert.deepEqual(await pending, proof);
|
|
assert.equal(provider.calls.options.https, true);
|
|
});
|
|
|
|
test('captcha cancellation never yields a proof, including after a provider retry event', async () => {
|
|
const provider = fakeProvider({ captcha_output: 'UNUSED-TEST-PROOF' });
|
|
const pending = solveCaptcha({ geetestId: 'TEST' }, provider);
|
|
await setImmediate();
|
|
provider.events.error({ error_code: 'error_01' });
|
|
provider.events.close();
|
|
provider.events.success();
|
|
assert.equal(await pending, null);
|
|
assert.equal(provider.calls.destroyed, 1);
|
|
});
|
|
|
|
test('unsupported or failed captchas are rejected so the UI can offer retry', async () => {
|
|
await assert.rejects(() => solveCaptcha({}), /captcha_type/);
|
|
await assert.rejects(() => solveCaptcha({ geetestId: 'TEST' }, { load: async () => { throw new Error('captcha_load'); } }), /captcha_load/);
|
|
const provider = fakeProvider(null);
|
|
const pending = solveCaptcha({ geetestId: 'TEST' }, provider);
|
|
const rejected = assert.rejects(pending, /captcha_validate/);
|
|
await setImmediate();
|
|
provider.events.success();
|
|
await rejected;
|
|
});
|