"use strict"; const assert = require("assert"); const fs = require("fs"); const path = require("path"); const toDataModuleUrl = (source) => `data:text/javascript;base64,${Buffer.from(source).toString("base64")}`; const run = async () => { const rawSource = fs.readFileSync(path.join(__dirname, "../utils/api.js"), "utf8"); const moduleBody = rawSource.slice(rawSource.indexOf("const successCodes")); const savedTokens = []; const clearedSessions = []; const relaunchedRoutes = []; const prelude = ` const currentUser = {}; const genealogies = []; const publicGenealogies = []; const treeMembers = []; const notifications = []; const joinApplications = []; const listFamilyFeedFixtures = () => []; const listFamilyArticleFixtures = () => []; const listFamilyAlbumFixtures = () => []; const listCeremonyFixtures = () => []; const listGrowthRecordFixtures = () => []; const listNotificationFixtures = () => []; const runtimeConfig = { mode: "remote", baseUrl: "https://backend-api.ddxcjp.cn", clientId: "client-1", tenantId: "000000", }; const hasRemoteConfig = () => true; const resolveRuntimeMode = () => "remote"; const AUTH_VERIFICATION_OPERATION = Object.freeze({ PASSWORD_LOGIN: "password-login", SMS_LOGIN: "sms-login", REGISTER: "register", FORGOT_PASSWORD: "forgot-password", }); const assertSmsCode = (value) => { if (typeof value !== "string" || !/^\\d{4}$/.test(value)) throw new Error("请输入 4 位短信验证码"); return value; }; const GENEALOGY_ACCESS_PRESET = Object.freeze({ MEMBER_ONLY: "MEMBER_ONLY" }); const session = { getToken: () => "session-1", saveToken: (token) => globalThis.__savedTokens.push(token), clear: () => globalThis.__clearedSessions.push("cleared"), }; `; globalThis.__savedTokens = savedTokens; globalThis.__clearedSessions = clearedSessions; globalThis.__relaunchedRoutes = relaunchedRoutes; const requests = []; let nextResponse = null; let holdResponse = false; globalThis.uni = { reLaunch(options) { globalThis.__relaunchedRoutes.push(options.url); options.complete?.(); }, request(options) { requests.push(options); const task = { aborted: false, abort() { this.aborted = true; options.fail({ errMsg: "request:fail abort" }); }, }; options.__task = task; if (!holdResponse) queueMicrotask(() => options.success(nextResponse)); return task; }, }; const { appApi, createRequestController } = await import( toDataModuleUrl(`${prelude}\n${moduleBody}`) ); const respond = (response) => { nextResponse = response; }; const sendSms = () => appApi.sendSmsCode({ operationCode: "register", phone: "13800138000", validToken: "ticket-1", }); for (const invalid of [ { statusCode: 200, data: null }, { statusCode: 200, data: "ok" }, { statusCode: 200, data: {} }, { statusCode: 200, data: { code: "200", data: null } }, { statusCode: 200, data: { code: null, data: null } }, { statusCode: 200, data: { code: false, data: null } }, { statusCode: 204, data: { code: 200, msg: "成功", data: null } }, ]) { respond(invalid); await assert.rejects(sendSms(), /认证服务|响应|200/); } respond({ statusCode: 500, data: null }); await assert.rejects(sendSms(), /500/); respond({ statusCode: 200, data: { code: 500, msg: "业务拒绝", data: null } }); await assert.rejects(sendSms(), /业务拒绝/); respond({ statusCode: 200, data: { code: 200, msg: "操作成功", data: null } }); assert.strictEqual(await sendSms(), null, "合法 RVoid 必须精确解析为 null"); respond({ statusCode: 200, data: { code: 200 } }); assert.strictEqual(await sendSms(), null, "RVoid 未声明 data 必填,省略 data 仍必须解析为 null"); assert.strictEqual(requests.at(-1).header.clientid, "client-1"); assert.strictEqual(requests.at(-1).header.tenantId, "000000"); assert.strictEqual( requests.at(-1).url, "https://backend-api.ddxcjp.cn/genealogy/app/auth/sms/register/code", ); assert.deepStrictEqual(requests.at(-1).data, { tenantId: "000000", grantType: "sms", phone: "13800138000", validToken: "ticket-1", }); respond({ statusCode: 200, data: { code: 200 } }); await appApi.sendSmsCode({ operationCode: "register", phone: "13800138000", }); assert.deepStrictEqual(requests.at(-1).data, { tenantId: "000000", grantType: "sms", phone: "13800138000", }, "策略关闭时发码请求不得伪造 validToken"); respond({ statusCode: 200, data: { code: 200 } }); assert.strictEqual( await appApi.changePhone({ phone: "13900139000", smsCode: "1234" }), null, "手机号换绑的 RVoid 响应必须解析为 null", ); assert.strictEqual(requests.at(-1).url, "https://backend-api.ddxcjp.cn/genealogy/app/auth/phone"); assert.strictEqual(requests.at(-1).method, "PUT"); assert.strictEqual(requests.at(-1).header.Authorization, "Bearer session-1"); assert.deepStrictEqual(requests.at(-1).data, { phone: "13900139000", smsCode: "1234" }); // 已鉴权读取收到业务 401 时,只清理失效的本地会话;请求仍向调用方失败返回。 respond({ statusCode: 200, data: { code: 401, msg: "认证失败", data: null } }); await assert.rejects( appApi.getProfile(), (error) => error.code === "BUSINESS_ERROR" && error.businessCode === 401, ); assert.deepStrictEqual(clearedSessions, ["cleared"]); assert.deepStrictEqual(relaunchedRoutes, ["/pages/auth/a01-entry"]); respond({ statusCode: 200, data: null }); await assert.rejects( appApi.resetPassword({ phone: "13800138000", passwordHash: "a".repeat(32), smsCode: "1234", }), /认证服务|响应/, ); respond({ statusCode: 200, data: { code: 200 } }); assert.strictEqual( await appApi.resetPassword({ phone: "13800138000", passwordHash: "a".repeat(32), smsCode: "1234", }), null, "找回密码的 RVoid 省略 data 时仍必须解析为 null", ); assert.deepStrictEqual(requests.at(-1).data, { tenantId: "000000", grantType: "password", phone: "13800138000", newPassword: "a".repeat(32), smsCode: "1234", }); respond({ statusCode: 200, data: { code: 200, msg: "操作成功", data: { access_token: "token-1" } }, }); const login = await appApi.loginWithSms({ phone: "13800138000", smsCode: "1234" }); assert.strictEqual(login.access_token, "token-1"); assert.deepStrictEqual(requests.at(-1).data, { tenantId: "000000", grantType: "sms", phone: "13800138000", smsCode: "1234", }); assert.strictEqual(requests.at(-1).header.Authorization, undefined); assert.deepStrictEqual(savedTokens, ["token-1"]); respond({ statusCode: 200, data: { code: 200, msg: "操作成功", data: { access_token: "token-password" } }, }); const passwordLogin = await appApi.loginWithPassword({ phone: "13800138000", passwordHash: "a".repeat(32), validToken: "ticket-password", }); assert.strictEqual(passwordLogin.access_token, "token-password"); const passwordRequest = requests.at(-1); assert.strictEqual(passwordRequest.url, "https://backend-api.ddxcjp.cn/genealogy/app/auth/login"); assert.strictEqual(passwordRequest.method, "POST"); assert.strictEqual(passwordRequest.header.clientid, "client-1"); assert.strictEqual(passwordRequest.header.Authorization, undefined); assert.deepStrictEqual(passwordRequest.data, { tenantId: "000000", grantType: "password", phone: "13800138000", password: "a".repeat(32), validToken: "ticket-password", }); assert.deepStrictEqual(savedTokens, ["token-1", "token-password"]); // 登录接口不携带既有会话,错误凭据不能借由 401 清理其他页面的本地会话。 respond({ statusCode: 200, data: { code: 401, msg: "密码错误", data: null } }); await assert.rejects( appApi.loginWithPassword({ phone: "13800138000", passwordHash: "a".repeat(32), }), (error) => error.code === "BUSINESS_ERROR" && error.businessCode === 401, ); assert.deepStrictEqual(clearedSessions, ["cleared"]); assert.deepStrictEqual(relaunchedRoutes, ["/pages/auth/a01-entry"]); respond({ statusCode: 200, data: { code: 200, msg: "操作成功", data: { access_token: "token-register" } }, }); const registration = await appApi.registerWithPassword({ phone: "13800138000", nickName: " 联调昵称 ", passwordHash: "a".repeat(32), smsCode: "1234", }); assert.strictEqual(registration.access_token, "token-register"); assert.deepStrictEqual(requests.at(-1).data, { tenantId: "000000", grantType: "password", phone: "13800138000", password: "a".repeat(32), smsCode: "1234", nickName: "联调昵称", }); respond({ statusCode: 200, data: { code: 200, msg: "操作成功", data: { access_token: "token-register-empty" } }, }); await appApi.registerWithPassword({ phone: "13800138000", nickName: " ", passwordHash: "a".repeat(32), smsCode: "1234", }); assert.strictEqual(Object.hasOwn(requests.at(-1).data, "nickName"), false); respond({ statusCode: 200, data: { code: 200 } }); assert.strictEqual(await appApi.deactivateAccount({ smsCode: "1234" }), null); assert.strictEqual(requests.at(-1).url, "https://backend-api.ddxcjp.cn/genealogy/app/auth/account/deactivate"); assert.strictEqual(requests.at(-1).header.Authorization, "Bearer session-1"); assert.deepStrictEqual(requests.at(-1).data, { smsCode: "1234" }); holdResponse = true; const requestController = createRequestController(); const cancelled = appApi.sendSmsCode( { operationCode: "register", phone: "13800138000", validToken: "ticket-1", }, { requestController }, ); const activeRequest = requests.at(-1); const activeTask = activeRequest.__task; assert.strictEqual(activeRequest.timeout, 15000, "认证请求必须限制弱网等待时间"); requestController.abort(); await assert.rejects(cancelled, (error) => error.code === "REQUEST_CANCELLED"); assert.strictEqual(activeTask.aborted, true, "页面离开时必须真正中止 RequestTask"); delete globalThis.uni; delete globalThis.__savedTokens; delete globalThis.__clearedSessions; delete globalThis.__relaunchedRoutes; process.stdout.write("AUTH-API-RUNTIME-SMOKE PASS\n"); }; run().catch((error) => { process.stderr.write(`${error.stack || error.message}\n`); process.exit(1); });