test(suite): 自检 3(判据不得埋在 process.exit 之后)+ TMPDIR 固化 + API 同名不同义表

## 自检 3(pi 提议)

自检 1/2 管"文件没接线",管不到"检查写在 `process.exit()` 之后"—— 而那正是实际发生的
第 4 例(4 条玻璃判据被并发写入落到文件末尾)。成因是**结构性**的(并发写入总是往文件末尾
追加),所以它一定会再发生,而它下一次仍然不报错。静态扫一遍即可:`process.exit(` 之后
若再出现 `check(`,直接判红并指出文件。变异验证:往 `theme.test.mjs` 尾部追加一条 check → 判红。

## TMPDIR 固化(pi 建议,采纳)

"记得加 TMPDIR"这种约定活不过两次踩坑(hvigor、fpm 各一次)。所以不再靠口径:
- `npm run build:linux` 自带 `mkdir -p .tmp && TMPDIR=${TMPDIR:-$PWD/.tmp}`;
- `BUILD.md` 的 deb 一节写明这条前置与原因(`/tmp` 是 tmpfs、占内存、常年近满)。

## `docs/API.md`:`total` 不是总封数 + 同名不同义表

`GET /me/mail/inbox` 的 `total` 是**未读总数**(`repo.CountUnread`),不是本页/全部邮件数 ——
鸿蒙端曾因此写出「共 7 封」和「未读 7」两行自相矛盾的字。在人类接口开头加了醒目提示,
并新增一张表:`total`(未读数)/ `status`(邮件=unread|read,会话=active|archived)/
`status` 与 `is_read` 同义不同名。

## 验证

`npm test` 退出码 0:9 个判据文件全绿 + vitest 258/258(安装包已按判据要求重打,
AppImage 与 deb 均为最新)。
This commit is contained in:
2026-09-14 13:53:34 +08:00
parent b3f404838b
commit 5ce711fbcd
4 changed files with 48 additions and 2 deletions

View File

@ -141,6 +141,10 @@ AGENTMAIL_USER_KEY=<用户密钥> ./agentmail-web
### deb 打不出来时:先看 `/tmp` 有没有空间2026-09-14 实测)
> 顺带一条已经**固化进脚本**的前置:本机 `/tmp` 是 tmpfs占内存且常年接近满
> `npm run build:linux` 现在自带 `TMPDIR=${TMPDIR:-$PWD/.tmp}`(工作区所在大盘)。
> 手敲 `electron-builder` 时请照着加 —— "记得加 TMPDIR"这种约定活不过两次踩坑hvigor 与 fpm 各踩过一次)。
本机曾以为"deb 打不出来是 fpm 的毛病"(报错停在 `fpm process failed 1`,栈顶是
portable ruby 的 `Dir.chdir`,看着像路径权限)。**实际是 ENOSPC** —— fpm 会把
`release/linux-unpacked`(约 291MB**整份复制**到 `TMPDIR` 里再打包,而本机

View File

@ -16,7 +16,7 @@
"dev:electron": "ELECTRON_START_URL=http://localhost:5173 electron electron/main.cjs",
"build": "npm run gen:bg && vite build",
"build:win": "vite build && electron-builder --win",
"build:linux": "vite build && electron-builder --linux",
"build:linux": "mkdir -p .tmp && vite build && TMPDIR=${TMPDIR:-$PWD/.tmp} electron-builder --linux",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"test": "node test/run-all.mjs && vitest run",

View File

@ -18,7 +18,7 @@
* 在它进套件之前一直是隐身状态。加了这条,**新增判据忘了接线会直接红**。
*/
import { spawnSync } from 'node:child_process';
import { existsSync, readdirSync } from 'node:fs';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
@ -59,6 +59,31 @@ if (ghosts.length || unwired.length) {
process.exit(1);
}
/*
* 自检 3判据不得写在 `process.exit()` **之后**pi 提议2026-09-14
*
* 自检 1/2 管的是"文件没接线",管不到"检查写在了退出之后" —— 而那正是实际发生过的
* 第 4 例4 条玻璃判据被并发写入落到了文件末尾、`process.exit()` 后面,
* 于是**一条都不执行、也不计入通过/失败**,输出看起来完全正常。
* 这种事的成因是结构性的(并发写入总是往文件末尾追加),所以它一定会再发生,
* 而它下一次仍然不报错 —— 静态扫一遍最省事。
*/
const buried = [];
for (const [file, flags] of SUITE) {
if (flags.includes('--test')) {
continue; // node:test 那几条没有 process.exit结构上不会踩这个
}
const src = readFileSync(join(ROOT, file), 'utf8');
const exitAt = src.lastIndexOf('process.exit(');
if (exitAt >= 0 && /(^|\n)\s*check\(/.test(src.slice(exitAt))) {
buried.push(file);
}
}
if (buried.length) {
console.error(`判据写在 process.exit() 之后,永远不会跑(挪到汇总之前):${buried.join('、')}`);
process.exit(1);
}
const reds = [];
for (const [file, flags] of SUITE) {
console.log(`\n========== ${file} ==========`);

View File

@ -64,6 +64,23 @@ curl {host}/api/v1/me/mail/inbox -H "Authorization: Bearer $TOKEN"
## 三、人类接口
> **先读这一条:`total` 不是"总封数"。**
> `GET /me/mail/inbox` 的响应是 `{"mails": [...], "total": N}`,而那个 `N` 是
> **未读总数**(服务端 `repo.CountUnread`,与 `?status=` 过滤无关),**不是**本页/全部邮件数。
> 它叫 `total` 是历史命名所致。后果很具体:鸿蒙端底部曾写「共 N 封」,
> 于是同一屏上出现「共 7 封」和「未读 7」两行自相矛盾的字
> 2026-09-14 修WebUI 侧不读这个字段,故未受影响)。
> 客户端**没有任何可信的"总封数"**可用 —— 想要"还有更多吗"只能看这一页是否取满
> `mails.length === limit`),不能把 `limit` 封说成全部。
### 同名不同义 / 同义不同名(改代码前先看这张表)
| 名字 | 在一处的意思 | 在另一处的意思 |
|---|---|---|
| `total` | `/me/mail/inbox`**未读总数** | 别处(如 `/me/mail/sent` 等)才是"条数",同名不同义,别看名字取值 |
| `status` | 邮件上:`unread` / `read` | 会话上:`active` / `archived`(两套取值域,共用字段名) |
| `status` + `is_read` | 邮件上这两个字段说的是同一件事(同义不同名) | 判断已读时别只看一个,旧数据可能只有一个被写对 |
### 邮件
```