308 lines
8.6 KiB
JavaScript
308 lines
8.6 KiB
JavaScript
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);
|
|
});
|