Преглед изворни кода

✅ test(advertisements): add integration tests for advertisement management APIs

- add @d8d/user-module dependency for user-related operations in tests
- declare hono as peer dependency
- add advertisement-types.integration.test.ts with tests for CRUD operations
- add advertisements.integration.test.ts covering ad list, create, get, update and delete
- test authentication, authorization and basic business logic validation
yourname пре 1 месец
родитељ
комит
ab92951f68

+ 4 - 0
packages/advertisements-module/package.json

@@ -50,6 +50,7 @@
     "@d8d/shared-crud": "workspace:*",
     "@d8d/file-module": "workspace:*",
     "@d8d/auth-module": "workspace:*",
+    "@d8d/user-module": "workspace:*",
     "@hono/zod-openapi": "^1.0.2",
     "typeorm": "^0.3.20",
     "zod": "^4.1.12"
@@ -63,6 +64,9 @@
     "@typescript-eslint/parser": "^8.18.1",
     "eslint": "^9.17.0"
   },
+  "peerDependencies": {
+    "hono": "^4.8.5"
+  },
   "keywords": [
     "advertisements",
     "ads",

+ 259 - 0
packages/advertisements-module/tests/integration/advertisement-types.integration.test.ts

@@ -0,0 +1,259 @@
+import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
+import { testClient } from 'hono/testing';
+import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
+import { JWTUtil } from '@d8d/shared-utils';
+import { UserEntity, Role } from '@d8d/user-module';
+import { File } from '@d8d/file-module';
+import advertisementTypeRoutes from '../../src/routes/advertisement-types';
+import { AdvertisementType } from '../../src/entities/advertisement-type.entity';
+
+// 设置集成测试钩子
+setupIntegrationDatabaseHooksWithEntities([UserEntity, File, Role, AdvertisementType])
+
+describe('广告类型管理API集成测试', () => {
+  let client: ReturnType<typeof testClient<typeof advertisementTypeRoutes>>;
+  let testToken: string;
+  let testUser: UserEntity;
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(advertisementTypeRoutes);
+
+    // 获取数据源
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+
+    // 创建测试用户
+    const userRepository = dataSource.getRepository(UserEntity);
+    testUser = userRepository.create({
+      username: `test_user_${Date.now()}`,
+      password: 'test_password',
+      nickname: '测试用户',
+      registrationSource: 'web'
+    });
+    await userRepository.save(testUser);
+
+    // 生成测试用户的token
+    testToken = JWTUtil.generateToken({
+      id: testUser.id,
+      username: testUser.username,
+      roles: [{name:'user'}]
+    });
+  });
+
+  describe('GET /advertisement-types', () => {
+    it('应该返回广告类型列表', async () => {
+      const response = await client.$get({
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('广告类型列表响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data).toHaveProperty('data');
+        expect(Array.isArray(data.data)).toBe(true);
+      }
+    });
+
+    it('应该拒绝未认证用户的访问', async () => {
+      const response = await client.$get();
+      expect(response.status).toBe(401);
+    });
+  });
+
+  describe('POST /advertisement-types', () => {
+    it('应该成功创建广告类型', async () => {
+      const createData = {
+        name: '测试广告类型',
+        code: 'test_type',
+        remark: '测试备注',
+        status: 1
+      };
+
+      const response = await client.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('创建广告类型响应状态:', response.status);
+      expect(response.status).toBe(201);
+
+      if (response.status === 201) {
+        const data = await response.json();
+        expect(data).toHaveProperty('id');
+        expect(data.name).toBe(createData.name);
+        expect(data.code).toBe(createData.code);
+        expect(data.status).toBe(createData.status);
+      }
+    });
+
+    it('应该验证创建广告类型的必填字段', async () => {
+      const invalidData = {
+        // 缺少必填字段
+        remark: '测试备注'
+      };
+
+      const response = await client.$post({
+        json: invalidData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+
+    it('应该验证广告类型编码的唯一性', async () => {
+      // 先创建一个广告类型
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const advertisementTypeRepository = dataSource.getRepository(AdvertisementType);
+      const existingType = advertisementTypeRepository.create({
+        name: '现有类型',
+        code: 'existing_code',
+        status: 1
+      });
+      await advertisementTypeRepository.save(existingType);
+
+      // 尝试创建相同编码的类型
+      const duplicateData = {
+        name: '重复类型',
+        code: 'existing_code', // 重复的编码
+        status: 1
+      };
+
+      const response = await client.$post({
+        json: duplicateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+  });
+
+  describe('GET /advertisement-types/:id', () => {
+    it('应该返回指定广告类型的详情', async () => {
+      // 先创建一个广告类型
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const advertisementTypeRepository = dataSource.getRepository(AdvertisementType);
+      const testType = advertisementTypeRepository.create({
+        name: '测试类型详情',
+        code: 'test_type_detail',
+        remark: '测试备注',
+        status: 1
+      });
+      await advertisementTypeRepository.save(testType);
+
+      const response = await client[':id'].$get({
+        param: { id: testType.id.toString() }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('广告类型详情响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.id).toBe(testType.id);
+        expect(data.name).toBe(testType.name);
+        expect(data.code).toBe(testType.code);
+      }
+    });
+
+    it('应该处理不存在的广告类型', async () => {
+      const response = await client[':id'].$get({
+        param: { id: '999999' }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+    });
+  });
+
+  describe('PUT /advertisement-types/:id', () => {
+    it('应该成功更新广告类型', async () => {
+      // 先创建一个广告类型
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const advertisementTypeRepository = dataSource.getRepository(AdvertisementType);
+      const testType = advertisementTypeRepository.create({
+        name: '原始类型',
+        code: 'original_type',
+        remark: '原始备注',
+        status: 1
+      });
+      await advertisementTypeRepository.save(testType);
+
+      const updateData = {
+        name: '更新后的类型',
+        code: 'updated_type',
+        remark: '更新后的备注'
+      };
+
+      const response = await client[':id'].$put({
+        param: { id: testType.id.toString() },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('更新广告类型响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.name).toBe(updateData.name);
+        expect(data.code).toBe(updateData.code);
+        expect(data.remark).toBe(updateData.remark);
+      }
+    });
+  });
+
+  describe('DELETE /advertisement-types/:id', () => {
+    it('应该成功删除广告类型', async () => {
+      // 先创建一个广告类型
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const advertisementTypeRepository = dataSource.getRepository(AdvertisementType);
+      const testType = advertisementTypeRepository.create({
+        name: '待删除类型',
+        code: 'delete_type',
+        remark: '待删除备注',
+        status: 1
+      });
+      await advertisementTypeRepository.save(testType);
+
+      const response = await client[':id'].$delete({
+        param: { id: testType.id.toString() }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('删除广告类型响应状态:', response.status);
+      expect(response.status).toBe(204);
+
+      // 验证广告类型确实被删除
+      const deletedType = await advertisementTypeRepository.findOne({
+        where: { id: testType.id }
+      });
+      expect(deletedType).toBeNull();
+    });
+  });
+});

+ 257 - 0
packages/advertisements-module/tests/integration/advertisements.integration.test.ts

@@ -0,0 +1,257 @@
+import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
+import { testClient } from 'hono/testing';
+import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
+import { JWTUtil } from '@d8d/shared-utils';
+import { UserEntity, Role } from '@d8d/user-module';
+import { File } from '@d8d/file-module';
+import advertisementRoutes from '../../src/routes/advertisements';
+import { Advertisement } from '../../src/entities/advertisement.entity';
+import { AdvertisementType } from '../../src/entities/advertisement-type.entity';
+
+// 设置集成测试钩子
+setupIntegrationDatabaseHooksWithEntities([UserEntity, File, Role, Advertisement, AdvertisementType])
+
+describe('广告管理API集成测试', () => {
+  let client: ReturnType<typeof testClient<typeof advertisementRoutes>>;
+  let testToken: string;
+  let testUser: UserEntity;
+  let testAdvertisementType: AdvertisementType;
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(advertisementRoutes);
+
+    // 获取数据源
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+
+    // 创建测试用户
+    const userRepository = dataSource.getRepository(UserEntity);
+    testUser = userRepository.create({
+      username: `test_user_${Date.now()}`,
+      password: 'test_password',
+      nickname: '测试用户',
+      registrationSource: 'web'
+    });
+    await userRepository.save(testUser);
+
+    // 创建测试广告类型
+    const advertisementTypeRepository = dataSource.getRepository(AdvertisementType);
+    testAdvertisementType = advertisementTypeRepository.create({
+      name: '首页轮播',
+      code: 'home_banner',
+      remark: '用于首页轮播图展示',
+      status: 1
+    });
+    await advertisementTypeRepository.save(testAdvertisementType);
+
+    // 生成测试用户的token
+    testToken = JWTUtil.generateToken({
+      id: testUser.id,
+      username: testUser.username,
+      roles: [{name:'user'}]
+    });
+  });
+
+  describe('GET /advertisements', () => {
+    it('应该返回广告列表', async () => {
+      const response = await client.$get({
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('广告列表响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data).toHaveProperty('data');
+        expect(Array.isArray(data.data)).toBe(true);
+      }
+    });
+
+    it('应该拒绝未认证用户的访问', async () => {
+      const response = await client.$get();
+      expect(response.status).toBe(401);
+    });
+  });
+
+  describe('POST /advertisements', () => {
+    it('应该成功创建广告', async () => {
+      const createData = {
+        title: '测试广告',
+        typeId: testAdvertisementType.id,
+        code: 'test_ad',
+        url: 'https://example.com',
+        sort: 10,
+        status: 1,
+        actionType: 1
+      };
+
+      const response = await client.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('创建广告响应状态:', response.status);
+      expect(response.status).toBe(201);
+
+      if (response.status === 201) {
+        const data = await response.json();
+        expect(data).toHaveProperty('id');
+        expect(data.title).toBe(createData.title);
+        expect(data.code).toBe(createData.code);
+        expect(data.status).toBe(createData.status);
+      }
+    });
+
+    it('应该验证创建广告的必填字段', async () => {
+      const invalidData = {
+        // 缺少必填字段
+        url: 'https://example.com'
+      };
+
+      const response = await client.$post({
+        json: invalidData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+  });
+
+  describe('GET /advertisements/:id', () => {
+    it('应该返回指定广告的详情', async () => {
+      // 先创建一个广告
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const advertisementRepository = dataSource.getRepository(Advertisement);
+      const testAdvertisement = advertisementRepository.create({
+        title: '测试广告详情',
+        typeId: testAdvertisementType.id,
+        code: 'test_ad_detail',
+        url: 'https://example.com',
+        sort: 5,
+        status: 1,
+        actionType: 1,
+        createdBy: testUser.id
+      });
+      await advertisementRepository.save(testAdvertisement);
+
+      const response = await client[':id'].$get({
+        param: { id: testAdvertisement.id.toString() }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('广告详情响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.id).toBe(testAdvertisement.id);
+        expect(data.title).toBe(testAdvertisement.title);
+        expect(data.code).toBe(testAdvertisement.code);
+      }
+    });
+
+    it('应该处理不存在的广告', async () => {
+      const response = await client[':id'].$get({
+        param: { id: '999999' }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+    });
+  });
+
+  describe('PUT /advertisements/:id', () => {
+    it('应该成功更新广告', async () => {
+      // 先创建一个广告
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const advertisementRepository = dataSource.getRepository(Advertisement);
+      const testAdvertisement = advertisementRepository.create({
+        title: '原始广告',
+        typeId: testAdvertisementType.id,
+        code: 'original_ad',
+        url: 'https://example.com',
+        sort: 5,
+        status: 1,
+        actionType: 1,
+        createdBy: testUser.id
+      });
+      await advertisementRepository.save(testAdvertisement);
+
+      const updateData = {
+        title: '更新后的广告',
+        code: 'updated_ad',
+        sort: 15
+      };
+
+      const response = await client[':id'].$put({
+        param: { id: testAdvertisement.id.toString() },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('更新广告响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.title).toBe(updateData.title);
+        expect(data.code).toBe(updateData.code);
+        expect(data.sort).toBe(updateData.sort);
+      }
+    });
+  });
+
+  describe('DELETE /advertisements/:id', () => {
+    it('应该成功删除广告', async () => {
+      // 先创建一个广告
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const advertisementRepository = dataSource.getRepository(Advertisement);
+      const testAdvertisement = advertisementRepository.create({
+        title: '待删除广告',
+        typeId: testAdvertisementType.id,
+        code: 'delete_ad',
+        url: 'https://example.com',
+        sort: 5,
+        status: 1,
+        actionType: 1,
+        createdBy: testUser.id
+      });
+      await advertisementRepository.save(testAdvertisement);
+
+      const response = await client[':id'].$delete({
+        param: { id: testAdvertisement.id.toString() }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      console.debug('删除广告响应状态:', response.status);
+      expect(response.status).toBe(204);
+
+      // 验证广告确实被删除
+      const deletedAdvertisement = await advertisementRepository.findOne({
+        where: { id: testAdvertisement.id }
+      });
+      expect(deletedAdvertisement).toBeNull();
+    });
+  });
+});