feat: save opening booster inventory independently

This commit is contained in:
COMPUTER\EDY
2026-10-15 19:13:13 +08:00
parent 0985d5e9c1
commit faba0bc88e
2 changed files with 122 additions and 3 deletions
+14 -3
View File
@@ -46,13 +46,22 @@ export default async function (ctx: FunctionContext) {
data.infiniteRocketTime = user.data?.infiniteRocketTime || 0;
let propType = Number(ctx.body.propType);
let propData = ctx.body.propData;
const boosterField = propType === 2004 ? "hourglassAmount" : propType === 2005 ? "rocketAmount" : null;
if (!propType && propType != 0) {
return { code: 0, data: null, msg: "未获取到propType" };
}
if (!propData) {
if (!propData && !(boosterField && propData === 0)) {
return { code: 0, data: null, msg: "未获取到propData" }
}
if (propType === 2001) {
if (boosterField) {
const amount = Number(propData);
if ((typeof propData !== "number" && typeof propData !== "string")
|| (typeof propData === "string" && !propData.trim())
|| !Number.isSafeInteger(amount) || amount < 0) {
return { code: 0, data: null, msg: "道具数量必须是非负整数" };
}
data[boosterField] = amount;
} else if (propType === 2001) {
data.freezeAmount = Number(propData);
} else if (propType === 2002) {
data.hammerAmount = Number(propData);
@@ -75,7 +84,9 @@ export default async function (ctx: FunctionContext) {
if (propData.infinite_hourglass !== undefined) data.infiniteHourglassTime = Number(propData.infinite_hourglass);
if (propData.infinite_rocket !== undefined) data.infiniteRocketTime = Number(propData.infinite_rocket);
}
let ret = await db.collection(dbname).where({ _id: uid }).update(data);
let ret = await db.collection(dbname).where({ _id: uid }).update(boosterField
? { [boosterField]: data[boosterField], timestamp: data.timestamp }
: data);
return {
code: 1, data: {
freeze: data.freezeAmount,
+108
View File
@@ -0,0 +1,108 @@
import test from "node:test";
import assert from "node:assert/strict";
import { registerHooks } from "node:module";
let user;
let writes;
let beforeUpdate;
globalThis.__boosterPropCloudMock = {
database: () => ({
collection: () => ({
where: () => ({
getOne: async () => ({ data: { ...user } }),
async update(update) {
beforeUpdate?.();
writes.push({ ...update });
Object.assign(user, update);
},
}),
}),
}),
};
registerHooks({
load(url, context, next) {
return next(url, url.endsWith(".ts") ? { ...context, format: "module-typescript" } : context);
},
resolve(specifier, context, next) {
if (specifier === "@lafjs/cloud") {
return { url: "data:text/javascript,export default globalThis.__boosterPropCloudMock", shortCircuit: true };
}
if (specifier === "@/Utils") {
return { url: "data:text/javascript,export default {checkToken:(actual,expected)=>actual===expected}", shortCircuit: true };
}
return next(specifier, context);
},
});
const { default: userProp } = await import("../functions/userProp.ts");
function reset() {
user = {
token: "valid", freezeAmount: 1, hammerAmount: 2, magicAmount: 3,
hourglassAmount: 4, rocketAmount: 5, infiniteHourglassTime: 100, infiniteRocketTime: 200,
};
writes = [];
beforeUpdate = null;
}
function save(propType, propData, extra = {}) {
return userProp({ body: { uid: "u1", token: "valid", action: "save", propType, propData, ...extra } });
}
for (const [propType, field, responseField] of [[2004, "hourglassAmount", "hourglass"], [2005, "rocketAmount", "rocket"]]) {
test(`${propType} saves only its own count and timestamp`, async () => {
reset();
const original = { ...user };
const result = await save(propType, JSON.stringify(7));
assert.equal(result.code, 1);
assert.equal(result.data[responseField], 7);
assert.deepEqual(Object.keys(writes[0]).sort(), [field, "timestamp"].sort());
assert.deepEqual(user, { ...original, [field]: 7, timestamp: writes[0].timestamp });
});
test(`${propType} can consume the last item using string or numeric zero`, async () => {
for (const value of ["0", 0]) {
reset();
assert.equal((await save(propType, value)).code, 1);
assert.equal(user[field], 0);
}
});
test(`${propType} does not undo concurrent changes to another prop or infinite entitlement`, async () => {
reset();
const otherField = propType === 2004 ? "rocketAmount" : "hourglassAmount";
beforeUpdate = () => Object.assign(user, { hammerAmount: 8, [otherField]: 9, infiniteRocketTime: 999 });
await save(propType, "2");
assert.equal(user[field], 2);
assert.equal(user.hammerAmount, 8);
assert.equal(user[otherField], 9);
assert.equal(user.infiniteRocketTime, 999);
});
test(`${propType} rejects invalid inventory amounts without writing`, async () => {
for (const value of [undefined, null, "", " ", -1, "-1", 1.5, "NaN", Infinity, true, {}, []]) {
reset();
assert.equal((await save(propType, value)).code, 0);
assert.deepEqual(writes, []);
}
});
}
test("invalid token cannot change either new prop", async () => {
for (const propType of [2004, 2005]) {
reset();
assert.equal((await save(propType, "3", { token: "invalid" })).code, 0);
assert.deepEqual(writes, []);
}
});
test("legacy single-prop and all-prop requests retain their existing behavior", async () => {
reset();
assert.equal((await save(2001, "0")).code, 1);
assert.equal(user.freezeAmount, 0);
assert.equal((await save(2001, 0)).code, 0);
assert.equal((await save(0, JSON.stringify({ hourglass: 6, rocket: 0, infinite_hourglass: 123 }))).code, 1);
assert.equal(user.hourglassAmount, 6);
assert.equal(user.rocketAmount, 0);
assert.equal(user.infiniteHourglassTime, 123);
assert.equal(user.hammerAmount, 2);
});