server/laf-cloud/tests/mini-program-welfare.test.mjs
2026-09-24 17:59:52 +08:00

151 lines
6.2 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { registerHooks } from "node:module";
import { readFile } from "node:fs/promises";
const state = { users: [], calls: [], failUpdate: false };
const clone = value => structuredClone(value);
const valueAt = (row, path) => path.split(".").reduce((value, key) => value?.[key], row);
const matches = (row, query) => Object.entries(query).every(([key, value]) =>
value?.$ne !== undefined ? valueAt(row, key) !== value.$ne : valueAt(row, key) === value);
globalThis.__welfareCloudMock = {
database: () => ({
command: { neq: value => ({ $ne: value }) },
collection(name) {
assert.equal(name, "users");
return {
where(query) {
return {
async update(payload) {
state.calls.push({ query: clone(query), payload: clone(payload) });
// Yield before matching so concurrent callers all reach the database operation.
await Promise.resolve();
if (state.failUpdate) return { ok: false, updated: 0 };
const user = state.users.find(row => matches(row, query));
if (!user) return { ok: true, updated: 0 };
for (const [path, value] of Object.entries(payload)) {
const parts = path.split(".");
const key = parts.pop();
const target = parts.reduce((row, part) => row[part] ??= {}, user);
target[key] = clone(value);
}
return { ok: true, updated: 1 };
},
async getOne() {
return { data: clone(state.users.find(row => matches(row, query)) ?? null) };
},
};
},
};
},
}),
};
registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier === "@lafjs/cloud") {
return { url: "data:text/javascript,export default globalThis.__welfareCloudMock", shortCircuit: true };
}
return nextResolve(specifier, context);
},
});
const { default: record } = await import("../functions/miniProgramWelfare.ts");
function reset(extra = {}) {
state.users = [{
_id: "u1", token: "secret", coinAmount: 123,
freezeAmount: 4, hammerAmount: 5, magicAmount: 6, ...extra,
}, { _id: "u2", token: "other" }];
state.calls = [];
state.failUpdate = false;
}
const call = (extra = {}) => record({ body: { uid: "u1", token: "secret", type: "favorite", ...extra } });
test("legacy user: record each reward independently without awarding resources", async () => {
for (const firstType of ["favorite", "desktop"]) {
reset();
const before = clone(state.users);
const first = await call({ type: firstType });
assert.equal(first.code, 1);
assert.deepEqual(first.data, {
miniProgramWelfare: { favorite: firstType === "favorite", desktop: firstType === "desktop" },
changed: true,
});
const second = await call({ type: firstType === "favorite" ? "desktop" : "favorite" });
assert.deepEqual(second.data, { miniProgramWelfare: { favorite: true, desktop: true }, changed: true });
const { miniProgramWelfare, ...unchanged } = state.users[0];
assert.deepEqual(miniProgramWelfare, { [firstType]: true, [firstType === "favorite" ? "desktop" : "favorite"]: true });
assert.deepEqual(unchanged, before[0]);
assert.deepEqual(state.users[1], before[1]);
}
});
test("repeat calls succeed without resetting either reward or accepting client state", async () => {
reset({ miniProgramWelfare: { favorite: true, desktop: true } });
const before = clone(state.users);
const result = await call({ miniProgramWelfare: { favorite: false, desktop: false }, claimed: false });
assert.deepEqual(result.data, { miniProgramWelfare: { favorite: true, desktop: true }, changed: false });
assert.deepEqual(state.users, before);
});
test("simultaneous retries change the same reward exactly once", async () => {
reset({ miniProgramWelfare: { favorite: false, desktop: false } });
const results = await Promise.all(Array.from({ length: 20 }, () => call()));
assert.ok(results.every(result => result.code === 1 && result.data.miniProgramWelfare.favorite));
assert.equal(results.filter(result => result.data.changed).length, 1);
assert.deepEqual(state.users[0].miniProgramWelfare, { favorite: true, desktop: false });
});
test("simultaneous different rewards retain both marks for a legacy user", async () => {
reset();
const results = await Promise.all([call(), call({ type: "desktop" }), call(), call({ type: "desktop" })]);
assert.ok(results.every(result => result.code === 1));
assert.equal(results.filter(result => result.data.changed).length, 2);
assert.deepEqual(state.users[0].miniProgramWelfare, { favorite: true, desktop: true });
});
test("invalid inputs and unsupported game are rejected before any write", async () => {
for (const input of [
{ uid: "" }, { uid: { $ne: null } }, { uid: 1 },
{ token: "" }, { token: null }, { token: { $ne: null } },
{ type: "" }, { type: "reset" }, { type: "__proto__" }, { type: ["favorite"] },
{ gameName: "iaa" },
]) {
reset();
const before = clone(state.users);
assert.equal((await call(input)).code, 0);
assert.deepEqual(state.users, before);
assert.equal(state.calls.length, 0);
}
assert.equal((await record({})).code, 0);
});
test("missing users, missing stored tokens and mismatched tokens cannot write or read state", async () => {
for (const [extra, input] of [
[{}, { uid: "missing" }], [{}, { uid: "u2" }],
[{}, { token: "wrong" }], [{ token: undefined }, {}],
]) {
reset(extra);
const before = clone(state.users);
const result = await call(input);
assert.equal(result.code, 0);
assert.equal(result.data, null);
assert.deepEqual(state.users, before);
}
});
test("database update failures do not report success", async () => {
reset();
state.failUpdate = true;
const before = clone(state.users);
assert.equal((await call()).code, 0);
assert.deepEqual(state.users, before);
});
test("function configuration exposes POST only", async () => {
const yaml = await readFile(new URL("../functions/miniProgramWelfare.yaml", import.meta.url), "utf8");
assert.match(yaml, /^name: miniProgramWelfare$/m);
assert.match(yaml, /methods:\s*\n\s*- POST\s*\ntags:/);
});