MatchMaster/docs/superpowers/plans/2026-07-24-cat-arr.md
2026-07-30 12:22:02 +08:00

340 lines
9.6 KiB
Markdown

# catArr Server Cloud Functions Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add `catArr` initialization, reading, and manual saving to the Laf server cloud functions.
**Architecture:** Keep `catArr` independent from `headArr`. Initialize it in `login`, and expose a dedicated `setCatArr` cloud function with `read` and `save` actions while preserving the existing database routing and token-validation patterns.
**Tech Stack:** TypeScript, Laf cloud functions, Node.js built-in test runner.
## Global Constraints
- `catArr` is an array of non-negative integer IDs.
- New and legacy users default to `[1, 2, 3]` when the field is absent.
- Existing `catArr` values are never overwritten during login.
- The function must support both `users` and `usersAd`.
- No cat-drawing or gacha behavior is included.
- Do not create Git commits for this work.
---
### Task 1: Initialize catArr During Login
**Files:**
- Modify: `server/laf-cloud/functions/login.ts`
- Create: `server/laf-cloud/tests/login-cat-arr.test.mjs`
**Interfaces:**
- Consumes: the existing new-user object passed to `db.collection(dbname).add(...)`
- Produces: a persisted `catArr: number[]` field for newly created users
- [ ] **Step 1: Write the failing source-level regression test**
```js
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
test("login initializes new users with catArr [1, 2, 3]", async () => {
const source = await readFile(
new URL("../functions/login.ts", import.meta.url),
"utf8"
);
assert.match(
source,
/catArr:\s*user\?\.catArr\s*\|\|\s*\[1,\s*2,\s*3\]/
);
});
```
- [ ] **Step 2: Run the test and verify RED**
Run:
```powershell
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe" --test server/laf-cloud/tests/login-cat-arr.test.mjs
```
Expected: FAIL because `login.ts` does not yet initialize `catArr`.
- [ ] **Step 3: Add the minimal login field**
Add beside `headArr` in the new-user object:
```ts
catArr: user?.catArr || [1, 2, 3],
```
- [ ] **Step 4: Run the test and verify GREEN**
Run the same Node test command.
Expected: one passing test.
### Task 2: Add the Independent setCatArr Cloud Function
**Files:**
- Create: `server/laf-cloud/functions/setCatArr.ts`
- Create: `server/laf-cloud/functions/setCatArr.yaml`
- Create: `server/laf-cloud/tests/set-cat-arr.test.mjs`
**Interfaces:**
- Consumes: `{ action, uid, token, gameName?, catNum? }` from `ctx.body`
- Produces for `read`: `{ code: 1, data: { catArr: number[] }, msg: "成功" }`
- Produces for `save`: the same shape after persisting a unique non-negative integer ID
- [ ] **Step 1: Write failing behavior tests with a Laf database stub**
```js
import test from "node:test";
import assert from "node:assert/strict";
import { registerHooks } from "node:module";
import { readFile } from "node:fs/promises";
const state = {
user: null,
collections: [],
updates: [],
};
globalThis.__catArrCloudMock = {
database() {
return {
collection(name) {
state.collections.push(name);
return {
where() {
return {
async getOne() {
return { data: state.user };
},
async update(payload) {
state.updates.push(payload);
return {};
},
};
},
};
},
};
},
};
globalThis.__catArrUtilsMock = {
checkToken(received, expected) {
return received === expected;
},
};
registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier === "@lafjs/cloud") {
return {
url: "data:text/javascript,export default globalThis.__catArrCloudMock",
shortCircuit: true,
};
}
if (specifier === "@/Utils") {
return {
url: "data:text/javascript,export default globalThis.__catArrUtilsMock",
shortCircuit: true,
};
}
return nextResolve(specifier, context);
},
});
const { default: setCatArr } = await import(
new URL("../functions/setCatArr.ts", import.meta.url)
);
function reset(user) {
state.user = user;
state.collections.length = 0;
state.updates.length = 0;
}
test("configuration exposes the setCatArr cloud function", async () => {
const yaml = await readFile(
new URL("../functions/setCatArr.yaml", import.meta.url),
"utf8"
);
assert.match(yaml, /^name:\s*setCatArr$/m);
});
test("read returns defaults for a legacy user", async () => {
reset({ _id: "u1", token: "secret" });
const result = await setCatArr({
body: { action: "read", uid: "u1", token: "secret" },
});
assert.deepEqual(result.data.catArr, [1, 2, 3]);
});
test("save appends and persists a new cat ID", async () => {
reset({ _id: "u1", token: "secret" });
const result = await setCatArr({
body: { action: "save", uid: "u1", token: "secret", catNum: 4 },
});
assert.deepEqual(result.data.catArr, [1, 2, 3, 4]);
assert.deepEqual(state.updates, [{ catArr: [1, 2, 3, 4] }]);
});
test("save rejects duplicate and invalid cat IDs", async () => {
reset({ _id: "u1", token: "secret", catArr: [1, 2, 3] });
const duplicate = await setCatArr({
body: { action: "save", uid: "u1", token: "secret", catNum: 2 },
});
const invalid = await setCatArr({
body: { action: "save", uid: "u1", token: "secret", catNum: -1 },
});
assert.equal(duplicate.code, 0);
assert.equal(invalid.code, 0);
assert.deepEqual(state.updates, []);
});
test("gameName iaa routes to usersAd", async () => {
reset({ _id: "u1", token: "secret", catArr: [1, 2, 3] });
await setCatArr({
body: {
action: "read",
uid: "u1",
token: "secret",
gameName: "iaa",
},
});
assert.equal(state.collections[0], "usersAd");
});
test("missing user returns an error", async () => {
reset(null);
const result = await setCatArr({
body: { action: "read", uid: "missing", token: "secret" },
});
assert.equal(result.code, 0);
});
```
- [ ] **Step 2: Run the tests and verify RED**
Run:
```powershell
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe" --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test server/laf-cloud/tests/set-cat-arr.test.mjs
```
Expected: FAIL because `setCatArr.ts` and `setCatArr.yaml` do not exist.
- [ ] **Step 3: Add the cloud-function configuration**
Create `setCatArr.yaml`:
```yaml
name: setCatArr
desc: ""
methods:
- GET
- POST
tags: []
```
- [ ] **Step 4: Implement the minimal cloud function**
Create `setCatArr.ts`:
```ts
import cloud from '@lafjs/cloud'
import Utils from "@/Utils";
const db = cloud.database();
const DEFAULT_CAT_ARR = [1, 2, 3];
export default async function (ctx: FunctionContext) {
const action = ctx.body.action;
const uid = ctx.body.uid;
const dbname = ctx.body.gameName == "iaa" ? "usersAd" : "users";
if (!action) {
return { code: 0, data: null, msg: "未获取到action" };
}
if (!uid) {
return { code: 0, data: null, msg: "未获取到uid" };
}
const res = await db.collection(dbname).where({ _id: uid }).getOne();
if (!res.data) {
return { code: 0, data: null, msg: "未获取到玩家信息" };
}
if (res.data.token && !Utils.checkToken(ctx.body.token, res.data.token)) {
return { code: 0, data: null, msg: "token校验失败" };
}
const catArr = Array.isArray(res.data.catArr)
? [...res.data.catArr]
: [...DEFAULT_CAT_ARR];
switch (action) {
case "save": {
const catNum = Number(ctx.body.catNum);
if (!Number.isInteger(catNum) || catNum < 0) {
return { code: 0, data: null, msg: "猫咪id错误" };
}
if (catArr.includes(catNum)) {
return { code: 0, data: null, msg: "已保存猫咪" };
}
catArr.push(catNum);
await db.collection(dbname).where({ _id: uid }).update({ catArr });
return {
code: 1,
data: { catArr },
msg: "用户数据更新成功",
};
}
case "read":
return { code: 1, data: { catArr }, msg: "成功" };
default:
return {
code: 400,
message: "无效的操作类型,请使用 save 或 read",
};
}
}
```
- [ ] **Step 5: Run the behavior tests and verify GREEN**
Run the same Node test command.
Expected: six passing tests.
### Task 3: Run Combined Verification
**Files:**
- Verify: `server/laf-cloud/functions/login.ts`
- Verify: `server/laf-cloud/functions/setCatArr.ts`
- Verify: `server/laf-cloud/functions/setCatArr.yaml`
- Verify: `server/laf-cloud/tests/login-cat-arr.test.mjs`
- Verify: `server/laf-cloud/tests/set-cat-arr.test.mjs`
**Interfaces:**
- Consumes: completed Tasks 1 and 2
- Produces: verified server behavior for initialization, reading, and saving
- [ ] **Step 1: Run all catArr tests**
```powershell
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe" --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test server/laf-cloud/tests/*cat-arr.test.mjs
```
Expected: seven passing tests and zero failures.
- [ ] **Step 2: Inspect only the scoped changes**
```powershell
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\native\git\cmd\git.exe" diff -- server/laf-cloud/functions/login.ts server/laf-cloud/functions/setCatArr.ts server/laf-cloud/functions/setCatArr.yaml server/laf-cloud/tests/login-cat-arr.test.mjs server/laf-cloud/tests/set-cat-arr.test.mjs
```
Expected: only `catArr` initialization, the independent cloud function/configuration, and their tests.