243 lines
6.5 KiB
JavaScript
243 lines
6.5 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const vm = require("vm");
|
|
|
|
const componentSource = fs.readFileSync(
|
|
path.join(__dirname, "../components/TacVerification.vue"),
|
|
"utf8",
|
|
);
|
|
const renderScript = componentSource.match(
|
|
/<script module="tacRenderer" lang="renderjs">([\s\S]*?)<\/script>/,
|
|
);
|
|
assert(renderScript, "必须保留 TacVerification 的 renderjs 桥接入口");
|
|
|
|
let latestXhr = null;
|
|
class FakeXMLHttpRequest {
|
|
static DONE = 4;
|
|
|
|
constructor() {
|
|
this.readyState = 0;
|
|
this.status = 0;
|
|
this.responseText = "";
|
|
this.headers = {};
|
|
this.aborted = false;
|
|
latestXhr = this;
|
|
}
|
|
|
|
open(method, url) {
|
|
this.method = method;
|
|
this.url = url;
|
|
}
|
|
|
|
setRequestHeader(name, value) {
|
|
this.headers[name] = value;
|
|
}
|
|
|
|
send(payload) {
|
|
this.payload = payload;
|
|
}
|
|
|
|
abort() {
|
|
this.aborted = true;
|
|
}
|
|
|
|
respond(status, body) {
|
|
this.status = status;
|
|
this.responseText = body;
|
|
this.readyState = FakeXMLHttpRequest.DONE;
|
|
if (this.onreadystatechange) this.onreadystatechange();
|
|
}
|
|
|
|
timeoutNow() {
|
|
if (this.ontimeout) this.ontimeout();
|
|
}
|
|
}
|
|
|
|
let latestTac = null;
|
|
class FakeCaptchaConfig {
|
|
constructor(options) {
|
|
this.options = options;
|
|
}
|
|
|
|
addRequestChain(chain) {
|
|
this.chain = chain;
|
|
}
|
|
}
|
|
|
|
class FakeTac {
|
|
constructor(config) {
|
|
this.config = config;
|
|
this.destroyCount = 0;
|
|
this.reloadCount = 0;
|
|
latestTac = this;
|
|
}
|
|
|
|
init() {
|
|
this.initialized = true;
|
|
}
|
|
|
|
destroyWindow() {
|
|
this.destroyCount += 1;
|
|
}
|
|
|
|
reloadCaptcha() {
|
|
this.reloadCount += 1;
|
|
}
|
|
}
|
|
|
|
const host = { innerHTML: "rendered" };
|
|
const loadedStyle = { dataset: { jiapuState: "loaded" }, sheet: {} };
|
|
const document = {
|
|
head: { appendChild() {} },
|
|
querySelector(selector) {
|
|
if (selector === "#jiapu-tac-host") return host;
|
|
if (selector.startsWith("link[data-jiapu-tac=")) return loadedStyle;
|
|
return null;
|
|
},
|
|
createElement() {
|
|
throw new Error("本测试中的本地 TAC 资源应已就绪");
|
|
},
|
|
};
|
|
const window = {
|
|
CaptchaConfig: FakeCaptchaConfig,
|
|
TAC: FakeTac,
|
|
JiapuTacAdapter: {
|
|
buildVerifyBody: (data) => data,
|
|
normalizeChallengeResponse: () => ({ challenge: {}, sdkResponse: {} }),
|
|
normalizeVerifyResponse: (response) => response,
|
|
toSdkFailure: (error) => ({ code: 500, msg: error.message }),
|
|
},
|
|
};
|
|
const sandbox = {
|
|
console,
|
|
document,
|
|
window,
|
|
XMLHttpRequest: FakeXMLHttpRequest,
|
|
setTimeout: (callback) => {
|
|
callback();
|
|
return 1;
|
|
},
|
|
clearTimeout() {},
|
|
Promise,
|
|
Error,
|
|
Object,
|
|
};
|
|
sandbox.globalThis = sandbox;
|
|
vm.runInNewContext(
|
|
renderScript[1].replace("export default", "globalThis.__renderModule ="),
|
|
sandbox,
|
|
{ filename: "TacVerification.renderjs.js" },
|
|
);
|
|
|
|
const definition = sandbox.__renderModule;
|
|
assert(definition && definition.methods, "renderjs 必须导出可执行桥接模块");
|
|
const ownerCalls = [];
|
|
const instance = {
|
|
...definition.data(),
|
|
$ownerInstance: {
|
|
callMethod(name, payload) {
|
|
ownerCalls.push({ name, payload });
|
|
},
|
|
},
|
|
};
|
|
for (const [name, method] of Object.entries(definition.methods)) {
|
|
instance[name] = method.bind(instance);
|
|
}
|
|
|
|
const strictRequest = () => instance.sendStrictRequest({
|
|
method: "POST",
|
|
url: "https://backend-api.ddxcjp.cn/genealogy/app/auth/verification/register/verify",
|
|
headers: { clientid: "client-1" },
|
|
data: { challengeId: "challenge-1" },
|
|
});
|
|
|
|
const run = async () => {
|
|
let pending = strictRequest();
|
|
latestXhr.respond(500, "");
|
|
assert.strictEqual((await pending).code, 500, "HTTP 500 空体不得被供应商 SDK 当作成功");
|
|
|
|
pending = strictRequest();
|
|
latestXhr.respond(200, "");
|
|
assert.strictEqual((await pending).code, 502, "HTTP 200 空体必须失败关闭");
|
|
|
|
pending = strictRequest();
|
|
latestXhr.respond(200, "null");
|
|
assert.strictEqual((await pending).code, 502, "HTTP 200 非对象 JSON 必须失败关闭");
|
|
|
|
pending = strictRequest();
|
|
latestXhr.timeoutNow();
|
|
assert.strictEqual((await pending).code, 504, "验证码请求超时必须返回可识别失败码");
|
|
|
|
strictRequest();
|
|
const abortedXhr = latestXhr;
|
|
instance.destroyTac();
|
|
assert.strictEqual(abortedXhr.aborted, true, "关闭验证层必须中止在途请求");
|
|
assert.strictEqual(instance.activeXhr, null);
|
|
|
|
const context = {
|
|
visible: true,
|
|
requestId: "request-1",
|
|
challengeUrl: "https://backend-api.ddxcjp.cn/genealogy/app/auth/verification/register/challenge",
|
|
verifyUrl: "https://backend-api.ddxcjp.cn/genealogy/app/auth/verification/register/verify",
|
|
clientId: "client-1",
|
|
tenantId: "000000",
|
|
operationCode: "register",
|
|
subject: "13800138000",
|
|
};
|
|
instance.requestContext = context;
|
|
instance.generation += 1;
|
|
instance.createTac();
|
|
assert.strictEqual(latestTac.initialized, true);
|
|
assert.strictEqual(typeof latestTac.config.doSendRequest, "function");
|
|
latestTac.config.options.btnRefreshFun(null, latestTac);
|
|
assert.strictEqual(latestTac.reloadCount, 1, "必须保留供应商原生刷新操作");
|
|
|
|
const firstSuccess = latestTac.config.options.validSuccess;
|
|
firstSuccess(
|
|
{ data: { validToken: "ticket-1", expireSeconds: 300 } },
|
|
null,
|
|
latestTac,
|
|
);
|
|
firstSuccess(
|
|
{ data: { validToken: "ticket-duplicate", expireSeconds: 300 } },
|
|
null,
|
|
latestTac,
|
|
);
|
|
assert.strictEqual(
|
|
ownerCalls.filter(({ name }) => name === "handleTacSuccess").length,
|
|
1,
|
|
"同一轮供应商重复成功回调只能向逻辑层提交一次",
|
|
);
|
|
|
|
const staleSuccess = firstSuccess;
|
|
instance.generation += 1;
|
|
instance.requestContext = { ...context, requestId: "request-2" };
|
|
staleSuccess({ data: { validToken: "stale", expireSeconds: 300 } }, null, latestTac);
|
|
assert.strictEqual(ownerCalls.length, 1, "换轮后的陈旧回调不得污染新页面状态");
|
|
|
|
instance.createTac();
|
|
const reopenedTac = latestTac;
|
|
reopenedTac.config.options.btnCloseFun(null, reopenedTac);
|
|
reopenedTac.config.options.btnCloseFun(null, reopenedTac);
|
|
assert.strictEqual(
|
|
ownerCalls.filter(({ name }) => name === "handleTacCancel").length,
|
|
1,
|
|
"关闭按钮重复回调只能提交一次取消",
|
|
);
|
|
|
|
instance.requestContext = { ...context, requestId: "request-3" };
|
|
await instance.onContextChange({ visible: false });
|
|
assert.strictEqual(instance.tac, null, "隐藏或卸载验证层必须销毁 SDK 实例");
|
|
assert.strictEqual(host.innerHTML, "", "隐藏或卸载验证层必须清空宿主节点");
|
|
|
|
console.log("TAC-RENDERJS-RUNTIME-SMOKE PASS");
|
|
};
|
|
|
|
run().catch((error) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|