import test from "node:test"; import assert from "node:assert/strict"; import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { registerHooks } from "node:module"; const functionUrl = new URL("../functions/wucaiTransfer.ts", import.meta.url); const configUrl = new URL("../functions/wucaiTransfer.yaml", import.meta.url); const migrationUrl = new URL("../functions/wucaiMigration.ts", import.meta.url); const state = { user: null, source: null, sourceFails: false, fetchCalls: 0, collections: [], updates: [], }; globalThis.__wucaiTransferCloudMock = { database() { return { collection(name) { state.collections.push(name); return { where(query) { return { async getOne() { return { data: state.user }; }, async update(payload) { if (query.wucai_type !== undefined && Number(state.user?.wucai_type) !== Number(query.wucai_type)) { return { updated: 0 }; } if (query.wucai_reward_popup_pending !== undefined && state.user?.wucai_reward_popup_pending !== query.wucai_reward_popup_pending) { return { updated: 0 }; } state.updates.push(payload); Object.assign(state.user, payload); return { updated: 1 }; }, }; }, }; }, }; }, }; globalThis.__wucaiTransferUtilsMock = { checkToken(received, expected) { return received === expected; }, }; globalThis.fetch = async () => { state.fetchCalls++; await new Promise(resolve => setImmediate(resolve)); if (state.sourceFails) { throw new Error("source unavailable"); } return { ok: true, async json() { return { data: state.source }; }, }; }; registerHooks({ resolve(specifier, context, nextResolve) { if (specifier === "@lafjs/cloud") { return { url: "data:text/javascript,export default globalThis.__wucaiTransferCloudMock", shortCircuit: true, }; } if (specifier === "@/Utils") { return { url: "data:text/javascript,export default globalThis.__wucaiTransferUtilsMock", shortCircuit: true, }; } if (specifier === "./wucaiMigration") { return { url: migrationUrl.href, shortCircuit: true }; } return nextResolve(specifier, context); }, }); const { default: wucaiTransfer } = await import(functionUrl); function reset(user, source = null) { state.user = { ...user }; state.source = source; state.sourceFails = false; state.fetchCalls = 0; state.collections.length = 0; state.updates.length = 0; } test("wucai transfer cloud function is POST only", async () => { assert.equal(existsSync(functionUrl), true); assert.equal(existsSync(configUrl), true); const yaml = await readFile(configUrl, "utf8"); assert.match(yaml, /^name:\s*wucaiTransfer$/m); assert.match(yaml, /methods:\s*\n\s*- POST/m); assert.doesNotMatch(yaml, /- GET/); }); test("choose_wucai uses the latest source and adds the reward once", async () => { reset({ _id: "u1", token: "secret", unionid: "union-1", wucai_type: 1, coinAmount: 999, }, { levelAmount: 8, coinAmount: 100, healthAmount: 2, freezeAmount: 1, hammerAmount: 2, magicAmount: 3, monthCardTime: 999, pay_user: true, }); const result = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "choose_wucai" }, }); assert.equal(result.code, 1); assert.equal(state.fetchCalls, 1); assert.equal(state.user.levelAmount, 8); assert.equal(state.user.coinAmount, 2100); assert.equal(state.user.freezeAmount, 4); assert.equal(state.user.hammerAmount, 5); assert.equal(state.user.magicAmount, 6); assert.equal(state.user.pay_user, true); assert.equal(state.user.wucai_type, 2); assert.equal(state.user.wucai_reward_popup_pending, true); assert.equal(state.user.forcedUpdate, true); }); test("claim_reward is idempotent", async () => { reset({ _id: "u1", token: "secret", wucai_type: 0, coinAmount: 10, freezeAmount: 1, hammerAmount: 2, magicAmount: 3, }); const first = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "claim_reward" }, }); const retry = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "claim_reward" }, }); assert.equal(first.code, 1); assert.equal(retry.code, 1); assert.deepEqual( { coinAmount: first.data.coinAmount, freezeAmount: first.data.freezeAmount, hammerAmount: first.data.hammerAmount, magicAmount: first.data.magicAmount, }, { coinAmount: 2010, freezeAmount: 4, hammerAmount: 5, magicAmount: 6 } ); assert.deepEqual( { coinAmount: retry.data.coinAmount, freezeAmount: retry.data.freezeAmount, hammerAmount: retry.data.hammerAmount, magicAmount: retry.data.magicAmount, }, { coinAmount: 2010, freezeAmount: 4, hammerAmount: 5, magicAmount: 6 } ); assert.equal(state.updates.length, 1); assert.equal(state.user.coinAmount, 2010); assert.equal(state.user.freezeAmount, 4); assert.equal(state.user.wucai_type, 2); assert.equal(state.user.wucai_reward_popup_pending, false); }); test("choose_current keeps current progress and adds the reward", async () => { reset({ _id: "u1", token: "secret", unionid: "union-1", wucai_type: 1, levelAmount: 22, coinAmount: 50, freezeAmount: 5, hammerAmount: 6, magicAmount: 7, }); const result = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "choose_current" }, }); assert.equal(result.code, 1); assert.equal(state.fetchCalls, 0); assert.equal(state.user.levelAmount, 22); assert.equal(state.user.coinAmount, 2050); assert.equal(state.user.wucai_type, 3); assert.equal(state.user.wucai_reward_popup_pending, true); assert.equal(state.user.forcedUpdate, true); }); test("reward popup acknowledgement is idempotent and never grants resources", async () => { reset({ _id: "u1", token: "secret", wucai_type: 3, wucai_reward_popup_pending: true, coinAmount: 2050, freezeAmount: 8, hammerAmount: 9, magicAmount: 10, }); const first = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "ack_reward_popup" }, }); const retry = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "ack_reward_popup" }, }); assert.equal(first.code, 1); assert.equal(retry.code, 1); assert.equal(state.updates.length, 1); assert.equal(state.user.wucai_reward_popup_pending, false); assert.equal(state.user.coinAmount, 2050); assert.equal(state.user.freezeAmount, 8); assert.equal(state.user.hammerAmount, 9); assert.equal(state.user.magicAmount, 10); }); test("source failure leaves a pending choice unchanged", async () => { reset({ _id: "u1", token: "secret", unionid: "union-1", wucai_type: 1, }); state.sourceFails = true; const result = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "choose_wucai" }, }); assert.equal(result.code, 0); assert.equal(result.errorCode, "WUCAI_SOURCE_UNAVAILABLE"); assert.equal(state.user.wucai_type, 1); assert.deepEqual(state.updates, []); }); test("strict token validation and usersAd rejection happen before writes", async () => { reset({ _id: "u1", token: "secret", wucai_type: 0 }); const tokenResult = await wucaiTransfer({ body: { uid: "u1", action: "claim_reward" }, }); const iaaResult = await wucaiTransfer({ body: { uid: "u1", token: "secret", action: "claim_reward", gameName: "iaa" }, }); assert.equal(tokenResult.errorCode, "TOKEN_INVALID"); assert.equal(iaaResult.errorCode, "WUCAI_UNSUPPORTED_GAME"); assert.deepEqual(state.updates, []); }); test("concurrent conflicting choices produce only one terminal state", async () => { reset({ _id: "u1", token: "secret", unionid: "union-1", wucai_type: 1, coinAmount: 10, freezeAmount: 3, hammerAmount: 3, magicAmount: 3, }, { levelAmount: 0, coinAmount: 100, freezeAmount: 1, hammerAmount: 1, magicAmount: 1, }); const [wucaiResult, currentResult] = await Promise.all([ wucaiTransfer({ body: { uid: "u1", token: "secret", action: "choose_wucai" } }), wucaiTransfer({ body: { uid: "u1", token: "secret", action: "choose_current" } }), ]); assert.equal(currentResult.code, 1); assert.equal(wucaiResult.errorCode, "WUCAI_STATE_CHANGED"); assert.equal(state.user.wucai_type, 3); assert.equal(state.updates.length, 1); });