| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- 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');
- }
- });
- });
- });
|