import { test, expect } from '@playwright/test'; /** * E2E测试:统一广告API兼容性验证 * * 目的:验证统一广告模块替换后,用户端API路径和响应结构保持100%兼容 * 确保:小程序端无需任何修改即可正常工作 * * ## 测试前置条件 * * 本测试需要数据库中有测试数据: * 1. 至少一个租户记录在 `tenant_mt` 表中 * 2. 至少一个用户记录在 `users_mt` 表中 * 3. 用户的密码为 `admin123` (测试默认密码) * * ## 创建测试数据 * * 如果测试失败提示401错误,请先创建测试数据: * * ```sql * -- 创建测试租户 * INSERT INTO tenant_mt (id, name, code, status, created_at, updated_at) * VALUES (1, '测试租户', 'test-tenant', 1, NOW(), NOW()); * * -- 创建测试用户 (密码: admin123) * -- 注意:密码需要使用bcrypt加密 * INSERT INTO users_mt (id, tenant_id, username, password, registration_source, is_disabled, is_deleted, created_at, updated_at) * VALUES (1, 1, 'admin', '$2b$10$x3t2kofPmACnk6y6lfL6ouU836LBEuZE9BinQ3ZzA4Xd04izyY42K', 'web', 0, 0, NOW(), NOW()); * ``` * * ## 认证说明 * * - **用户端API**: 使用 `authMiddleware` (多租户认证),需要提供有效的JWT token * - **小程序端**: 应该使用用户登录后的token访问这些API * - **管理员API**: 使用 `tenantAuthMiddleware` (超级管理员专用,ID=1) * - **租户ID**: 通过查询参数 `?tenantId=1` 或请求头 `X-Tenant-Id: 1` 指定 * * ## API响应格式 * * API返回包装的响应格式: * ```json * { * "code": 200, * "message": "success", * "data": { * "list": [], * "total": 0, * "page": 1, * "pageSize": 10 * } * } * ``` */ test.describe('统一广告API兼容性测试', () => { const baseUrl = process.env.API_BASE_URL || 'http://localhost:8080'; const testUsername = process.env.TEST_USERNAME || 'admin'; const testPassword = process.env.TEST_PASSWORD || 'admin123'; const testTenantId = process.env.TEST_TENANT_ID || '1'; test.describe('用户端广告API (小程序使用)', () => { let userToken: string; test.beforeAll(async ({ request }) => { // 使用测试用户账号登录获取token // 注意:多租户系统中,用户端API需要认证来确定租户上下文 const loginResponse = await request.post(`${baseUrl}/api/v1/auth/login?tenantId=${testTenantId}`, { data: { username: testUsername, password: testPassword } }); if (loginResponse.status() === 200) { const loginData = await loginResponse.json(); userToken = loginData.token || loginData.access_token; console.log('✅ 登录成功,获取到token'); } else { const error = await loginResponse.json(); console.error('❌ 登录失败:', error); console.error('💡 提示: 请确保数据库中有测试用户和租户'); } }); test('GET /api/v1/advertisements - 获取广告列表', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/advertisements`, { headers: { 'Authorization': `Bearer ${userToken}` } }); // 验证响应状态 expect(response.status()).toBe(200); const result = await response.json(); expect(result).toHaveProperty('code', 200); expect(result).toHaveProperty('data'); expect(result.data).toHaveProperty('list'); expect(result.data).toHaveProperty('total'); const data = result.data.list; // 验证响应结构包含广告列表或空数组 expect(Array.isArray(data)).toBeTruthy(); // 如果有数据,验证字段结构 if (data.length > 0) { const ad = data[0]; expect(ad).toHaveProperty('id'); expect(ad).toHaveProperty('title'); expect(ad).toHaveProperty('imageUrl'); expect(ad).toHaveProperty('linkUrl'); expect(ad).toHaveProperty('position'); expect(ad).toHaveProperty('status'); } }); test('GET /api/v1/advertisements?position=home - 获取指定位置广告', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/advertisements?position=home`, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status()).toBe(200); const result = await response.json(); const data = result.data.list; expect(Array.isArray(data)).toBeTruthy(); // 验证返回的ads都是home位置 if (data.length > 0) { data.forEach((ad: any) => { expect(ad.position).toBe('home'); }); } }); test('GET /api/v1/advertisements/:id - 获取广告详情', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } // 先获取列表,找一个有效的ID const listResponse = await request.get(`${baseUrl}/api/v1/advertisements`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const listResult = await listResponse.json(); const listData = listResult.data.list; if (listData.length > 0) { const adId = listData[0].id; const response = await request.get(`${baseUrl}/api/v1/advertisements/${adId}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status()).toBe(200); const result = await response.json(); const data = result.data; expect(data).toHaveProperty('id', adId); expect(data).toHaveProperty('title'); expect(data).toHaveProperty('imageUrl'); expect(data).toHaveProperty('linkUrl'); expect(data).toHaveProperty('position'); expect(data).toHaveProperty('status'); } else { test.skip(true, '没有广告数据,请先创建测试广告'); } }); test('GET /api/v1/advertisement-types - 获取广告类型列表', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/advertisement-types`, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status()).toBe(200); const result = await response.json(); const data = result.data.list; expect(Array.isArray(data)).toBeTruthy(); // 验证响应结构 if (data.length > 0) { const adType = data[0]; expect(adType).toHaveProperty('id'); expect(adType).toHaveProperty('typeName'); expect(adType).toHaveProperty('description'); expect(adType).toHaveProperty('status'); } }); test('验证响应字段类型正确性', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/advertisements`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const result = await response.json(); const data = result.data.list; if (data.length > 0) { const ad = data[0]; expect(typeof ad.id).toBe('number'); expect(typeof ad.title).toBe('string'); expect(typeof ad.imageUrl).toBe('string'); expect(typeof ad.linkUrl).toBe('string'); expect(typeof ad.position).toBe('string'); expect(typeof ad.status).toBe('number'); } }); }); test.describe('管理员广告API (租户后台使用)', () => { let authToken: string; test.beforeAll(async ({ request }) => { // 使用超级管理员账号登录获取token const loginResponse = await request.post(`${baseUrl}/api/v1/auth/login?tenantId=${testTenantId}`, { data: { username: testUsername, password: testPassword } }); if (loginResponse.status() === 200) { const loginData = await loginResponse.json(); authToken = loginData.token || loginData.access_token; } }); test('GET /api/v1/admin/unified-advertisements - 管理员获取广告列表', async ({ request }) => { if (!authToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/admin/unified-advertisements`, { headers: { 'Authorization': `Bearer ${authToken}` } }); // 管理员API应该返回200或401/403(取决于权限配置) expect([200, 401, 403]).toContain(response.status()); if (response.status() === 200) { const result = await response.json(); const data = result.data.list; expect(Array.isArray(data)).toBeTruthy(); } }); test('GET /api/v1/admin/unified-advertisement-types - 管理员获取广告类型列表', async ({ request }) => { if (!authToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/admin/unified-advertisement-types`, { headers: { 'Authorization': `Bearer ${authToken}` } }); expect([200, 401, 403]).toContain(response.status()); if (response.status() === 200) { const result = await response.json(); const data = result.data.list; expect(Array.isArray(data)).toBeTruthy(); } }); }); test.describe('API路径兼容性验证', () => { let userToken: string; test.beforeAll(async ({ request }) => { // 登录获取token const loginResponse = await request.post(`${baseUrl}/api/v1/auth/login?tenantId=${testTenantId}`, { data: { username: testUsername, password: testPassword } }); if (loginResponse.status() === 200) { const loginData = await loginResponse.json(); userToken = loginData.token || loginData.access_token; } }); test('验证所有用户端广告API端点可访问', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const endpoints = [ '/api/v1/advertisements', '/api/v1/advertisement-types' ]; for (const endpoint of endpoints) { const response = await request.get(`${baseUrl}${endpoint}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status(), `端点 ${endpoint} 应该返回200`).toBe(200); } }); test('验证管理员API端点存在', async ({ request }) => { const endpoints = [ '/api/v1/admin/unified-advertisements', '/api/v1/admin/unified-advertisement-types' ]; for (const endpoint of endpoints) { const response = await request.get(`${baseUrl}${endpoint}`); // 管理员API需要认证,所以期望401或403,而不是404 expect([401, 403], `端点 ${endpoint} 应该存在但需要认证`).toContain(response.status()); } }); }); test.describe('响应数据结构兼容性', () => { let userToken: string; test.beforeAll(async ({ request }) => { // 登录获取token const loginResponse = await request.post(`${baseUrl}/api/v1/auth/login?tenantId=${testTenantId}`, { data: { username: testUsername, password: testPassword } }); if (loginResponse.status() === 200) { const loginData = await loginResponse.json(); userToken = loginData.token || loginData.access_token; } }); test('广告列表响应结构与原模块一致', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/advertisements`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const result = await response.json(); const data = result.data.list; if (data.length > 0) { const ad = data[0]; // 验证必填字段存在 expect(ad).toHaveProperty('id'); expect(ad).toHaveProperty('title'); expect(ad).toHaveProperty('imageUrl'); expect(ad).toHaveProperty('linkUrl'); expect(ad).toHaveProperty('position'); // 验证可选字段存在 expect(ad).toHaveProperty('description'); expect(ad).toHaveProperty('status'); expect(ad).toHaveProperty('sortOrder'); } }); test('广告类型响应结构与原模块一致', async ({ request }) => { if (!userToken) { test.skip(true, '缺少认证token,请先创建测试用户'); } const response = await request.get(`${baseUrl}/api/v1/advertisement-types`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const result = await response.json(); const data = result.data.list; if (data.length > 0) { const adType = data[0]; // 验证必填字段存在 expect(adType).toHaveProperty('id'); expect(adType).toHaveProperty('typeName'); expect(adType).toHaveProperty('description'); expect(adType).toHaveProperty('status'); } }); }); });