# 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/`。
