Files
ny-exam-platform/docs/dev/testing.md
T
nxxy335 a3be81b261 fix(security): 修复上传名单文件可被 URL 直连访问(nginx 下可 getshell)
严重漏洞:roster_import 使用 $_FILES 的原始扩展名拼 roster_YYYYMMDD_HHMMSS.<ext>
落在 Web 根目录内的 data/uploads/,既无扩展名白名单(可上传 .php),文件名又
可按秒枚举;而 bootstrap 只写了 data/.htaccess,nginx 不读 .htaccess,
生产环境(nginx + PHP-FPM)下可直接远程代码执行。

修复(api.php roster_import 四层防护):
- 扩展名白名单:仅 xlsx / xlsm / csv / txt
- 随机文件名:bin2hex(random_bytes(16)),杜绝枚举
- 解析完成后 @unlink 立即删除,Web 目录下不留存任何上传文件
- 20MB 大小限制 + UPLOAD_ERR 检查

加固(bootstrap.php):data 及 cache/uploads/snapshots 每个目录都写
.htaccess(Deny from all)+ index.html 占位;README 与文档补充 nginx
location ^~ /data/ { deny all; return 404; } 规则。

测试:新增 e2e tools/test-upload-guard.js(11 断言,覆盖 .php/.phtml 被拒、
无残留、合法 csv 导入成功且即删、无可枚举文件名),并入 tests/run-all.sh。
全套 90 项通过。

docs: 新增 VitePress 文档站(18 页)
- 使用指南 12 页:简介/部署/快速上手/系统设置/名单管理/统计口径/试卷设置/
  快照趋势异常/导出打印/数据看板/随机抽取/常见问题
- 安全 2 页:安全总览(含本次漏洞复盘)与部署加固清单(P0-P2)
- 开发 4 页:架构与目录/接口一览/SQLite 存储/测试
- 配图迁移至 docs/public/images/;.gitignore 忽略文档构建产物
2026-09-10 22:42:13 +08:00

99 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试
回归测试分两类:**PHP 自测**(纯逻辑断言)与 **Node e2e**(起 mock 服务 + PHP 内置服务器,走真实 HTTP)。
## 一键运行
```bash
bash tests/run-all.sh
```
脚本会依次执行:
| 文件 | 类型 | 覆盖 |
| --- | --- | --- |
| `tests/test-aggregate.php` | PHP | 聚合、去重、考试次数、排序 |
| `tests/test-new-features.php` | PHP | 异常检测、快照、趋势、XLSX 多表 |
| `tools/test-xlsxwriter.php` | PHP | XLSX 写入与 ZIP 打包 |
| `tools/test-config-save.js` | Node e2e | 配置保存、连接变化检测、每卷配置隔离、CSRF |
| `tools/test-draw-alloc.js` | Node e2e | 随机抽取名额分配(加权 / 自定义 / 守恒 fuzz) |
可通过环境变量覆盖运行时路径:
```bash
PHP_BIN=/usr/bin/php NODE_BIN=$(which node) bash tests/run-all.sh
```
## 单独运行 PHP 自测
```bash
php -d extension_dir="C:/php/ext" -d extension=pdo_sqlite tests/test-aggregate.php
php tests/test-new-features.php
```
输出为逐条 `✓ / ✗` 断言,结尾给出通过数 / 总数,失败时非零退出。
## 单独运行 Node e2e
e2e 需要两个进程:
1. **mock 卷王服务**(必须在 `tools/` 目录下启动,内部用 `__dirname` 解析相对路径):
```bash
cd tools
node mock-server.js # 默认监听 19911
```
2. **PHP 内置服务器**(端口 18081):
```bash
php -S 127.0.0.1:18081
```
然后:
```bash
node tools/test-config-save.js
node tools/test-draw-alloc.js
```
::: warning 测试会污染 `data/`
e2e 会备份并修改 `data/ny.db`、`data/config.json`、`data/roster.json`,
并在结束后恢复。若中途异常中断,请手动检查并还原:
```bash
git status data/ # data/ 已被 gitignore,需手工核对
```
另外测试前请删除 `data/login_fail.json`,否则可能触发登录限流。
:::
## 写新测试时注意
| 陷阱 | 说明 |
| --- | --- |
| CSRF | 写接口必须带 `_csrf`,e2e 里先从 `config_get` 取全局 token |
| DB 优先 | 配置读的是 **DB 优先**,只备份 `config.json` 不够,必须一起备份 `data/ny.db` |
| `/tmp` 路径 | Node 在 Windows 下会把 `/tmp/x.js` 解析成 `C:\tmp\x.js`,脚本请写在项目目录内 |
| `json_decode` | 必须传 `true` 得到关联数组,否则 `is_array(stdClass)` 为 `false` 会静默跳过 |
| mock 账号 | mock-server 接受账号 `mock` |
## 手工冒烟清单
改完代码建议至少过一遍:
- [ ] 登录 → 主页面三张卡片有数字
- [ ] 切换班级 → 卡片与表格联动
- [ ] 点某一行 → 能看到该学生的每次提交
- [ ] 导入含 `.php` 后缀的文件 → 被拒绝
- [ ] 导入正常 `.xlsx` → 成功,且 `data/uploads/` 为空
- [ ] 导出成绩 / 导出缺考 → 能打开,学号不是科学计数法
- [ ] 打印预览 → 默认成绩降序
- [ ] 数据看板全屏进入 / 退出都正常
- [ ] 随机抽取 → 名额守恒
## 相关
- [架构与目录](./architecture.md)
- [接口一览](./api.md)