账期收付改为按累计已付同步 credit_ledger,修复多笔 partial_paid 与坏账核销重复释额;新增 API E2E(占成、权限、部分收付/坏账)与 GitHub Actions e2e-api job。 Co-authored-by: Cursor <cursoragent@cursor.com>
226 lines
10 KiB
Markdown
226 lines
10 KiB
Markdown
# lotterLaravel E2E 测试
|
||
|
||
> 端到端测试,**真实** Postgres + Redis + Laravel + Reverb,与 Feature 测试(SQLite 内存库 + mock)解耦。
|
||
|
||
## 与 Feature 测试的边界
|
||
|
||
| 维度 | Feature | E2E(这套) |
|
||
|------|---------|------------|
|
||
| 数据库 | SQLite `:memory:` + `RefreshDatabase`(事务回滚) | 真 PG(docker compose 15432),事务落库 |
|
||
| Redis | `array` 驱动(不真连) | 真 Redis(16379),Lua/广播全真实 |
|
||
| 队列 | `sync`(同步执行) | `redis`(异步,启 `queue:work`) |
|
||
| 鉴权 | `actingAs` 直接注入 | 真 HTTP 调 `auth/login` 拿 token |
|
||
| 广播 | `Event::fake()` 拦截 | 真走 Reverb(8080) |
|
||
| 入口 | `app()->handle($request)` | 真 `php artisan serve`(8000) |
|
||
| 时延 | 数百 ms | 数十秒(启服务、跑 migrate) |
|
||
| 跑哪 | 每次 push / PR | 上线前 + 重大改动后 |
|
||
|
||
**业务逻辑**交给 Feature;**部署/启动/集成**问题归 e2e。
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
e2e/
|
||
├── docker-compose.yml # postgres + redis(仅 e2e 用)
|
||
├── .env.e2e # 模板 .env
|
||
├── package.json # playwright 依赖
|
||
├── playwright.config.ts
|
||
├── tests/ # 测试用例(API mode)
|
||
│ ├── fixtures.ts # 共享 HTTP 客户端 / login / 拿 draw
|
||
│ ├── api/
|
||
│ │ ├── _helper.spec.ts
|
||
│ │ ├── 01.health.spec.ts
|
||
│ │ ├── 02.player-auth.spec.ts
|
||
│ │ ├── 03.wallet-ticket.spec.ts
|
||
│ │ ├── 04.admin-auth.spec.ts
|
||
│ │ ├── 05.wallet-transfer.spec.ts # 转账 + 幂等 + 1001/1010
|
||
│ │ ├── 06.wallet-logs.spec.ts # 流水一致性 + 过滤 + 分页
|
||
│ │ ├── 07.admin-player.spec.ts # 创建/冻结/解冻玩家
|
||
│ │ ├── 08.draw-publish-settle.spec.ts # 开奖结算派彩(poll,无 skip)
|
||
│ │ ├── 09.credit-bet.spec.ts # 信用盘下注
|
||
│ │ ├── 10.agent-settlement.spec.ts # 代理账期关账
|
||
│ │ ├── 11.sso-mainsite.spec.ts # SSO + 主站 mock 异常 + happy path
|
||
│ │ ├── 12.broadcast.spec.ts # Reverb balance.update
|
||
│ │ ├── 13.credit-settlement-win.spec.ts # 信用盘中奖释额
|
||
│ │ ├── 14.settlement-payment.spec.ts # 账期 confirm + 收付
|
||
│ │ ├── 15.reconcile-job.spec.ts # pending_reconcile 扫描
|
||
│ │ ├── 16.agent-share-bill.spec.ts # 代理占成账单
|
||
│ │ ├── 17.settlement-partial-payment.spec.ts # 部分收付
|
||
│ │ ├── 18.settlement-permissions.spec.ts # 站点财务/代理权限
|
||
│ │ ├── 19.settlement-bad-debt-partial.spec.ts # 部分收付后坏账
|
||
│ │ ├── _helper.ts # 共享步骤
|
||
│ │ └── helpers/draw-settlement.ts # 结算流水线
|
||
│ └── ui/
|
||
│ ├── admin-login.spec.ts
|
||
│ ├── admin-settlement-center.spec.ts
|
||
│ ├── front-login-hall.spec.ts
|
||
│ └── front-place-bet.spec.ts
|
||
├── database/seeders/
|
||
│ └── E2EPlayerSeeder.php # 建可登录玩家(E2E\Seeders 命名空间)
|
||
├── routes/e2e.php # 仅 /api/v1/_e2e/* 路由
|
||
├── providers/
|
||
│ └── E2EServiceProvider.php # 仅在 LOTTERY_E2E=true 时挂载路由
|
||
├── scripts/
|
||
│ ├── run.sh # 一键起 stack + mock + UI + playwright
|
||
│ └── mock-wallet-server.mjs # 主站钱包 mock(5555)
|
||
└── README.md # 本文件
|
||
|
||
# E2E 控制器(仅路由被挂载才暴露,物理上位于 app/ 下便于 Laravel 自动加载)
|
||
app/Http/Controllers/Api/V1/E2E/
|
||
├── E2ECaptchaPeekController.php # captcha bypass 提示
|
||
├── E2EPlayerStateController.php # 玩家 reset / set-balance / unlock / inspect
|
||
├── E2EDrawController.php # close-now / finish-cooldown / tick
|
||
├── E2EInspectController.php # credit-ledger / wallet-txns / ticket-items
|
||
└── E2EProvisionController.php # credit-player / SSO mint / wallet mock 配置
|
||
|
||
# E2E 服务提供者注册入口(仅 E2E 时注册)
|
||
bootstrap/providers.php # 在末尾追加 E2EServiceProvider
|
||
|
||
# 生产代码最小入侵点
|
||
app/Services/AdminCaptchaService.php # 多了一个 `LOTTERY_E2E_BYPASS` 分支(env 关闭时不生效)
|
||
composer.json # autoload-dev 加 E2E\Seeders\, E2E\Providers\
|
||
```
|
||
|
||
## 前置依赖
|
||
|
||
- Docker(Docker Desktop / OrbStack / Colima 任一)
|
||
- Node.js 20+(跑 Playwright)
|
||
- PHP 8.3+、Composer
|
||
- 第一次跑:`npx playwright install chromium`(自动;本仓库 e2e 用 API 模式不依赖浏览器壳,但安装包仍需)
|
||
|
||
## 一键跑(macOS / Linux)
|
||
|
||
```bash
|
||
./e2e/scripts/run.sh
|
||
```
|
||
|
||
会自动:
|
||
|
||
1. `docker compose up -d`(PG :15432 + Redis :16379)
|
||
2. 等 PG/Redis health
|
||
3. `composer install`(缺 vendor 时)
|
||
4. 复制 `e2e/.env.e2e` → `.env`,注入强随机 `LOTTERY_NATIVE_JWT_SECRET` / `REVERB_APP_SECRET`
|
||
5. `php artisan key:generate`(缺时)
|
||
6. **`php artisan lottery:db-init --fresh`** ⚠️ 重建 `lottery_e2e` 库(**只**作用于此库,绝不碰其他库)
|
||
7. 跑 `E2EPlayerSeeder`(建可登录玩家)
|
||
8. 起 `php artisan serve`(8000)、`queue:work redis`(默认队列)、`reverb:start`(8080)
|
||
9. UI 测试默认用本机 **Google Chrome**(`playwright.config.ts` `channel: 'chrome'`),**无需**下载 Playwright Chromium
|
||
10. `npx playwright test`(若要用自带 Chromium:`PLAYWRIGHT_USE_BUNDLED_CHROMIUM=1 ./e2e/scripts/run.sh`)
|
||
|
||
跑完按 Ctrl+C 自动停 serve/queue/reverb。`docker compose down -v` 自行决定(脚本不删 volume,下次跑快)。
|
||
|
||
## 单独跑(不重起 stack)
|
||
|
||
```bash
|
||
# 起 stack(不跑测试)
|
||
docker compose -f e2e/docker-compose.yml up -d
|
||
DB_DATABASE=lottery_e2e php artisan serve --port=8000
|
||
DB_DATABASE=lottery_e2e php artisan queue:work redis
|
||
DB_DATABASE=lottery_e2e php artisan reverb:start --port=8080
|
||
|
||
# 跑测试
|
||
cd e2e
|
||
PLAYWRIGHT_API_URL=http://127.0.0.1:8000 \
|
||
E2E_PLAYER_USERNAME=demo_player E2E_PLAYER_PASSWORD=12345678 \
|
||
npx playwright test --headed # 想要 UI 调试时
|
||
```
|
||
|
||
## e2e 账号
|
||
|
||
| 角色 | 账号 | 密码 |
|
||
|------|------|------|
|
||
| 超管 | `admin` | `12345678` |
|
||
|
||
## e2e 玩家账号
|
||
|
||
| 字段 | 值 |
|
||
|------|---|
|
||
| `site_code` | `demo` |
|
||
| `username` | `demo_player` |
|
||
| `password` | `12345678` |
|
||
| `auth_source` | `lottery_native` |
|
||
| `funding_mode` | `wallet` |
|
||
| 初始余额 | `1,250,000 minor`(NPR 125.00,由 `DEV_SEED_WALLET_BALANCE_MINOR` 改) |
|
||
|
||
## 覆盖矩阵(截至当前)
|
||
|
||
| 链路 | spec | 用例数 | 状态 |
|
||
|------|------|--------|------|
|
||
| 健康/ping/captcha/公开接口 | 01.health | 4 | ✅ |
|
||
| 玩家登录 / 失败 / 锁定 | 02.player-auth | 4 | ✅ |
|
||
| 玩家钱包 + 下注 + 幂等 | 03.wallet-ticket | 3 | ✅ |
|
||
| 超管登录 / dashboard / 401 | 04.admin-auth | 3 | ✅ |
|
||
| 玩家 transfer-in/out + 1001/1010 | 05.wallet-transfer | 7 | ✅ |
|
||
| 钱包流水一致性 + 过滤 + 分页 | 06.wallet-logs | 4 | ✅ |
|
||
| 超管创建/冻结/解冻/查玩家 | 07.admin-player | 6 | ✅ |
|
||
| 开奖+结算+派彩 完整链路 | 08.draw-publish-settle | 1 | ✅ 确定性 poll |
|
||
| 信用盘下注占用授信 | 09.credit-bet | 1 | ✅ |
|
||
| 代理账期关账出账单 | 10.agent-settlement | 1 | ✅ |
|
||
| SSO JWT + 主站钱包异常 + happy path | 11.sso-mainsite | 4 | ✅ |
|
||
| Reverb balance.update | 12.broadcast | 1 | ✅ |
|
||
| 信用盘中奖释额 game_settlement_win | 13.credit-settlement-win | 1 | ✅ |
|
||
| 账期 confirm + 登记收付闭环 | 14.settlement-payment | 1 | ✅ |
|
||
| pending_reconcile → reconcile-jobs | 15.reconcile-job | 1 | ✅ |
|
||
| 代理占成账单 share_profit | 16.agent-share-bill | 1 | ✅ |
|
||
| 部分收付 partial_paid → settled | 17.settlement-partial-payment | 2 | ✅ |
|
||
| 站点财务收付 / 绑定代理禁坏账 | 18.settlement-permissions | 1 | ✅ |
|
||
| 部分收付后坏账核销 | 19.settlement-bad-debt-partial | 1 | ✅ |
|
||
| 管理端 UI 登录 | ui/admin-login | 1 | ✅ |
|
||
| 管理端 UI 结算中心 | ui/admin-settlement-center | 1 | ✅ |
|
||
| 玩家端 UI 登录进大厅 | ui/front-login-hall | 1 | ✅ |
|
||
| 玩家端 UI 下注提交 | ui/front-place-bet | 1 | ✅ |
|
||
|
||
合计 ~43 个用例覆盖 e2e 关键链路。
|
||
|
||
## 已知未覆盖(需外部依赖或重构成本高)
|
||
|
||
- **玩家提现链路**:与 transfer-out 重叠度 90%,增量价值低
|
||
- **报表 / settings 实时生效**:表单字段多,断言脆,价值中
|
||
|
||
## 跑特定 spec
|
||
|
||
```bash
|
||
cd e2e
|
||
npx playwright test 05.wallet-transfer.spec.ts --headed
|
||
```
|
||
|
||
## 关键安全设计
|
||
|
||
- **生产环境零侵入**:`AdminCaptchaService::verify` 走 `env('LOTTERY_E2E')` 开关,生产 `.env` 留空 → bypass 分支 dead code。
|
||
- **E2E 路由不挂载到生产**:`E2EServiceProvider::boot` 在 `LOTTERY_E2E !== true` 时直接 return;`/api/v1/_e2e/*` 在生产完全 404。
|
||
- **数据隔离**:e2e 库名固定 `lottery_e2e`,与 `lottery` 主库**端口不同**(15432 vs 5432)。
|
||
- **migrate:fresh 是库内操作**:脚本注释里明确"只作用于此 docker 库",并对应 AGENTS.md 规定。
|
||
- **JWT / Reverb 密钥运行时随机生成**:避免与生产/主站共用 secret。
|
||
|
||
## 添加新用例
|
||
|
||
1. 在 `e2e/tests/api/` 新建 `XX.something.spec.ts`
|
||
2. 复用 `fixtures.ts` 的 `playerLogin` / `adminLogin` / `playerCtx` / `adminCtx`
|
||
3. 任何要复用 e2e-only 路由的辅助,往 `E2EServiceProvider` 挂的路由里加,**绝不**往生产路由加
|
||
4. 跑:`cd e2e && npx playwright test 05.new.spec.ts`
|
||
|
||
## 排障
|
||
|
||
- **API 启动失败**:`tail -n 100 e2e/logs/serve.log` / `queue.log` / `reverb.log`
|
||
- **PG/Redis 起不来**:`docker compose -f e2e/docker-compose.yml logs`
|
||
- **测试卡住**:脚本的 `cleanup` 钩子 Ctrl+C 会停 artisan/queue/reverb
|
||
- **player 登录 401**:`curl -X POST http://127.0.0.1:8000/api/v1/_e2e/reset-player` 重置玩家
|
||
|
||
## CI 集成
|
||
|
||
仓库已包含 `.github/workflows/e2e.yml`(push/PR 自动跑 API 项目;`E2E_UI=0` 不启前后端 dev server)。
|
||
|
||
本地跑全量(含 UI):
|
||
|
||
```bash
|
||
./e2e/scripts/run.sh
|
||
```
|
||
|
||
仅 API(与 CI 一致):
|
||
|
||
```bash
|
||
E2E_UI=0 ./e2e/scripts/run.sh
|
||
```
|
||
|
||
失败产物:`e2e/artifacts/`、`e2e/logs/`。
|