"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 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 = () => globalThis.__runtimeMode === "remote"; const resolveRuntimeMode = () => { if (globalThis.__runtimeMode === "mock") return "mock"; if (globalThis.__runtimeMode === "remote") return "remote"; throw new Error("运行模式配置无效:只允许 mock 或配置完整的 remote"); }; const AUTH_TAC_SCENE = Object.freeze({ SMS_LOGIN: "APP_SMS_LOGIN", REGISTER: "APP_REGISTER", FORGOT_PASSWORD: "APP_FORGOT_PASSWORD", }); const assertSmsCode = (value) => value; const GENEALOGY_ACCESS_PRESET = Object.freeze({ MEMBER_ONLY: "MEMBER_ONLY" }); const session = { getToken: () => "session-token", saveToken: () => {}, }; `; globalThis.__runtimeMode = "remote"; const requests = []; let nextResponse = null; let nextFailure = null; let holdResponse = false; globalThis.uni = { 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(() => { if (nextFailure) options.fail(nextFailure); else options.success(nextResponse); }); } return task; }, }; const { appApi, createRequestController, isRequestCancelled } = await import( toDataModuleUrl(`${prelude}\n${moduleBody}`), ); const submit = (payload = { feedbackType: "bug", feedbackContent: "上传照片时出现异常", contactInfo: "user@example.com", }) => appApi.submitFeedback(payload); for (const invalid of [ null, [], {}, { feedbackContent: " " }, { feedbackContent: 123 }, { feedbackContent: "内容", feedbackType: 1 }, { feedbackContent: "内容", feedbackType: "feature" }, { feedbackContent: "内容", contactInfo: false }, { feedbackContent: "内容", type: "旧字段" }, Object.assign(Object.create({ feedbackType: "继承字段" }), { feedbackContent: "内容" }), ]) { await assert.rejects(submit(invalid), /反馈|字段|对象|字符串|feedbackType/); } nextResponse = { statusCode: 200, data: { code: 200, msg: "成功", data: { accepted: true } } }; const contentOnlyResult = await submit({ feedbackContent: " 仅反馈内容 " }); assert.deepStrictEqual(contentOnlyResult, { accepted: true }); assert.deepStrictEqual(requests.at(-1).data, { feedbackContent: "仅反馈内容" }); nextResponse = { statusCode: 200, data: { code: 200, msg: "成功", data: { feedbackId: 1 } } }; const result = await submit({ feedbackContent: " 有效反馈 ", feedbackType: " advice ", contactInfo: " " }); assert.deepStrictEqual(result, { feedbackId: 1 }); assert.deepStrictEqual(requests.at(-1).data, { feedbackContent: "有效反馈", feedbackType: "advice", }); assert.strictEqual(requests.at(-1).url, "https://backend-api.ddxcjp.cn/genealogy/app/feedback"); assert.strictEqual(requests.at(-1).method, "POST"); assert.strictEqual(requests.at(-1).timeout, 15000); assert.strictEqual(requests.at(-1).header.clientid, "client-1"); assert.strictEqual(requests.at(-1).header.Authorization, "Bearer session-token"); nextResponse = { statusCode: 200, data: { code: 500, msg: "业务拒绝", data: null } }; await assert.rejects(submit(), (error) => error.code === "BUSINESS_ERROR" && error.businessCode === 500, ); nextResponse = { statusCode: 401, data: { code: 401, msg: "登录失效", data: null } }; await assert.rejects(submit(), (error) => error.code === "HTTP_ERROR" && error.httpStatus === 401, ); for (const statusCode of [201, 204]) { nextResponse = { statusCode, data: { code: 200, msg: "成功", data: null } }; await assert.rejects(submit(), (error) => error.code === "HTTP_ERROR" && error.httpStatus === statusCode, ); } for (const invalidResponse of [ { statusCode: 200, data: null }, { statusCode: 200, data: {} }, { statusCode: 200, data: { code: "200", data: null } }, { statusCode: 200, data: { code: null, data: null } }, { statusCode: 200, data: { code: false, data: null } }, ]) { nextResponse = invalidResponse; await assert.rejects(submit(), (error) => error.code === "RESPONSE_INVALID"); } nextResponse = { statusCode: 200, data: { code: 200, msg: "成功" } }; assert.deepStrictEqual( await submit({ feedbackContent: "响应无需 data" }), { code: 200, msg: "成功" }, "反馈响应未声明 data 必填,不得把合法成功误报为未知", ); const requestCountBeforeUnsafeOptions = requests.length; await assert.rejects( appApi.submitFeedback( { feedbackContent: "不得关闭认证头" }, { authenticated: false }, ), /请求选项|字段/, ); assert.strictEqual(requests.length, requestCountBeforeUnsafeOptions); nextFailure = { errMsg: "request:fail timeout" }; await assert.rejects(submit(), (error) => error.code === "REQUEST_TIMEOUT"); nextFailure = null; holdResponse = true; const controller = createRequestController(); const cancelled = appApi.submitFeedback( { feedbackContent: "离页取消" }, { requestController: controller }, ); const activeTask = requests.at(-1).__task; controller.abort(); await assert.rejects(cancelled, (error) => isRequestCancelled(error)); assert.strictEqual(activeTask.aborted, true); holdResponse = false; globalThis.__runtimeMode = "mock"; const requestCount = requests.length; await assert.rejects( appApi.submitFeedback({ feedbackContent: "本地不得伪提交" }), (error) => error.code === "WRITE_UNAVAILABLE", ); assert.strictEqual(requests.length, requestCount, "mock 模式不得发送或伪造反馈请求"); globalThis.__runtimeMode = "invalid"; await assert.rejects( appApi.submitFeedback({ feedbackContent: "非法环境不得降级成 mock" }), /运行模式配置无效/, ); assert.strictEqual(requests.length, requestCount, "非法环境不得发送请求或伪装成本地预览"); delete globalThis.uni; delete globalThis.__runtimeMode; process.stdout.write("FEEDBACK-API-RUNTIME-SMOKE PASS\n"); }; run().catch((error) => { process.stderr.write(`${error.stack || error.message}\n`); process.exit(1); });