Files
lotteryLaravel/e2e/tests/fixtures.ts
kang 6ec9634704
Some checks failed
lotterLaravel CI / test (push) Has been cancelled
feat: 添加E2E测试环境配置支持
添加LOTTERY_E2E环境变量来控制E2E测试相关功能,
包括绕过验证码、登录限制和钱包API URL验证,
同时更新composer.json以包含E2E专用的提供者和服务。
2026-06-18 14:42:22 +08:00

130 lines
4.8 KiB
TypeScript
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.
/**
* e2e 共用 fixture玩家 / 超管登录、API 客户端、期号工具。
*
* 设计原则:
* - 走真 HTTPrequest.newContext不绕开鉴权
* - token 由各用例按需获取(每个 spec 自带 reset不跨测试串味
* - 任何 SQL 写操作走真 PG不允许 Http::fake / Bus::fake
*/
import { request as pwRequest, type APIRequestContext, type APIResponse } from '@playwright/test';
export interface PlayerSession {
accessToken: string;
expiresIn: number;
player: { id: number; site_code: string; username: string; funding_mode: string; auth_source: string };
}
export interface AdminSession {
accessToken: string;
tokenType: string;
expiresIn: number;
admin: { id: number; username: string; is_super_admin: boolean };
}
const API = process.env.PLAYWRIGHT_API_URL ?? 'http://127.0.0.1:8000';
export function apiUrl(path: string): string {
return new URL(path, API + '/').toString();
}
export async function expectOk<T = any>(resp: APIResponse, hint = ''): Promise<T> {
if (!resp.ok()) {
const body = await resp.text().catch(() => '');
throw new Error(`HTTP ${resp.status()} ${hint}\nURL: ${resp.url()}\nBody: ${body.slice(0, 800)}`);
}
return resp.json() as Promise<T>;
}
export async function playerLogin(password?: string): Promise<PlayerSession> {
const ctx = await pwRequest.newContext({ baseURL: API });
const resp = await ctx.post('/api/v1/player/auth/login', {
data: {
site_code: process.env.E2E_PLAYER_SITE_CODE ?? 'demo',
username: process.env.E2E_PLAYER_USERNAME ?? 'demo_player',
password: password ?? process.env.E2E_PLAYER_PASSWORD ?? '12345678',
},
});
const data = await expectOk<{ code: number; data: PlayerSession; msg?: string }>(resp, 'player login');
await ctx.dispose();
if (!data.data?.access_token) throw new Error('login ok but no access_token: ' + JSON.stringify(data));
return data.data;
}
export async function adminLogin(): Promise<AdminSession> {
const ctx = await pwRequest.newContext({ baseURL: API });
const captchaResp = await ctx.get('/api/v1/admin/auth/captcha');
const captchaBody = (await captchaResp.json()) as { data: { captcha_key: string } };
const resp = await ctx.post('/api/v1/admin/auth/login', {
data: {
account: process.env.E2E_ADMIN_USERNAME ?? 'admin',
password: process.env.E2E_ADMIN_PASSWORD ?? '12345678',
captcha_key: captchaBody.data.captcha_key,
captcha_code: 'LOTTERY_E2E_BYPASS',
},
});
const data = await expectOk<{ code: number; data: AdminSession; msg?: string }>(resp, 'admin login');
await ctx.dispose();
if (!data.data?.access_token) throw new Error('admin login ok but no access_token: ' + JSON.stringify(data));
return data.data;
}
export async function playerCtx(token: string): Promise<APIRequestContext> {
return pwRequest.newContext({
baseURL: API,
extraHTTPHeaders: { Authorization: `Bearer ${token}` },
});
}
export async function adminCtx(token: string): Promise<APIRequestContext> {
return pwRequest.newContext({
baseURL: API,
extraHTTPHeaders: { Authorization: `Bearer ${token}` },
});
}
/** 取当前开放、最近尚未开奖的期号(公开接口,无需登录)。
* 返回 null 表示当前没有可下注期号(大厅空),调用方应跳过下注用例。
*/
export async function fetchCurrentDrawNo(): Promise<{ draw_no: string; status: string | number; close_time?: string } | null> {
const ctx = await pwRequest.newContext({ baseURL: API });
const resp = await ctx.get('/api/v1/draw/current');
const body = await expectOk<{
code: number;
data: {
server_now_ms?: number;
data: { draw_no?: string; status?: string | number; close_time?: string } | null;
};
}>(resp, 'draw/current');
await ctx.dispose();
const snapshot = body.data?.data;
if (!snapshot?.draw_no) return null;
return snapshot as { draw_no: string; status: string | number; close_time?: string };
}
/** 取玩法/赔率/位元e2e 验证玩家下注时拼 bet payload 正确) */
export async function fetchPlayEffective(): Promise<any> {
const ctx = await pwRequest.newContext({ baseURL: API });
const resp = await ctx.get('/api/v1/play/effective');
const data = await expectOk<{ code: number; data: any }>(resp, 'play/effective');
await ctx.dispose();
return data.data;
}
/** 通过 /api/v1/_e2e/* 调一个 e2e 辅助端点POST/GET 通用) */
export async function e2e<T = any>(
method: 'GET' | 'POST',
path: string,
body?: Record<string, any>,
): Promise<T> {
const ctx = await pwRequest.newContext({ baseURL: API });
const resp = await ctx.fetch(`/api/v1/_e2e${path}`, {
method,
data: body ?? {},
headers: { 'Content-Type': 'application/json' },
});
const data = await expectOk<T>(resp, `e2e ${method} ${path}`);
await ctx.dispose();
return data;
}