Răsfoiți Sursa

✅ test(geo-areas): add admin area API integration tests

- add new integration test file for admin area API endpoints
- implement tests for authentication, CRUD operations, search, and tree structure queries
- add test utility class for integration test assertions
- add @d8d/user-module dependency for user service integration
yourname 4 săptămâni în urmă
părinte
comite
381ce0e86d

+ 1 - 0
packages/geo-areas/package.json

@@ -49,6 +49,7 @@
   },
   "dependencies": {
     "@d8d/auth-module": "workspace:*",
+    "@d8d/user-module": "workspace:*",
     "@d8d/shared-types": "workspace:*",
     "@d8d/shared-utils": "workspace:*",
     "@d8d/shared-crud": "workspace:*",

+ 423 - 0
packages/geo-areas/tests/integration/admin-areas.integration.test.ts

@@ -0,0 +1,423 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import { testClient } from 'hono/testing';
+import {
+  IntegrationTestDatabase,
+  setupIntegrationDatabaseHooksWithEntities
+} from '@d8d/shared-test-util';
+import { IntegrationTestAssertions } from '../utils/integration-test-utils';
+import adminAreaRoutes from '../../src/api/admin/areas';
+import { AreaEntity, AreaLevel } from '../../src/modules/areas/area.entity';
+import { DisabledStatus } from '@d8d/shared-types';
+import { TestDataFactory } from '../utils/test-data-factory';
+import { AuthService } from '@d8d/auth-module';
+import { UserService } from '@d8d/user-module';
+
+// 设置集成测试钩子
+setupIntegrationDatabaseHooksWithEntities([AreaEntity])
+
+describe('管理地区API集成测试 (使用hono/testing)', () => {
+  let client: ReturnType<typeof testClient<typeof adminAreaRoutes>>;
+  let authService: AuthService;
+  let userService: UserService;
+  let testToken: string;
+  let testUser: any;
+  let testAreas: AreaEntity[];
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(adminAreaRoutes);
+
+    // 获取数据源
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    if (!dataSource) throw new Error('Database not initialized');
+
+    // 初始化服务
+    userService = new UserService(dataSource);
+    authService = new AuthService(userService);
+
+    // 创建测试用户并生成token
+    testUser = await TestDataFactory.createTestUser(dataSource, {
+      username: 'testuser_admin_areas',
+      password: 'TestPassword123!',
+      email: 'testuser_admin_areas@example.com'
+    });
+
+    // 生成测试用户的token
+    testToken = authService.generateToken(testUser);
+
+    // 创建测试地区数据
+    const province1 = await TestDataFactory.createTestArea(dataSource, {
+      name: '北京市',
+      level: AreaLevel.PROVINCE,
+      isDisabled: DisabledStatus.ENABLED
+    });
+    const province2 = await TestDataFactory.createTestArea(dataSource, {
+      name: '上海市',
+      level: AreaLevel.PROVINCE,
+      isDisabled: DisabledStatus.ENABLED
+    });
+    const city1 = await TestDataFactory.createTestArea(dataSource, {
+      name: '朝阳区',
+      level: AreaLevel.CITY,
+      parentId: province1.id,
+      isDisabled: DisabledStatus.ENABLED
+    });
+    const city2 = await TestDataFactory.createTestArea(dataSource, {
+      name: '海淀区',
+      level: AreaLevel.CITY,
+      parentId: province1.id,
+      isDisabled: DisabledStatus.ENABLED
+    });
+    const district1 = await TestDataFactory.createTestArea(dataSource, {
+      name: '朝阳区',
+      level: AreaLevel.DISTRICT,
+      parentId: city1.id,
+      isDisabled: DisabledStatus.ENABLED
+    });
+
+    testAreas = [province1, province2, city1, city2, district1];
+  });
+
+  describe('认证测试', () => {
+    it('应该拒绝无认证令牌的请求', async () => {
+      const response = await client.index.$get({
+        query: {}
+      });
+
+      // 应该返回401状态码,因为缺少认证
+      expect(response.status).toBe(401);
+      if (response.status === 401) {
+        const responseData = await response.json();
+        expect(responseData.message).toContain('Authorization header missing');
+      }
+    });
+
+    it('应该拒绝无效认证令牌的请求', async () => {
+      const response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': 'Bearer invalid.token.here'
+        }
+      });
+
+      // 应该返回401状态码,因为令牌无效
+      expect(response.status).toBe(401);
+      if (response.status === 401) {
+        const responseData = await response.json();
+        expect(responseData.message).toContain('Invalid token');
+      }
+    });
+  });
+
+  describe('地区CRUD操作测试', () => {
+    it('应该成功创建地区(使用有效认证令牌)', async () => {
+      const areaData = {
+        name: '测试省份',
+        code: 'test_province_001',
+        level: AreaLevel.PROVINCE,
+        parentId: null,
+        isDisabled: DisabledStatus.ENABLED
+      };
+
+      const response = await client.index.$post({
+        json: areaData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      // 断言响应
+      expect(response.status).toBe(201);
+      if (response.status === 201) {
+        const responseData = await response.json();
+        expect(responseData).toHaveProperty('id');
+        expect(responseData.name).toBe(areaData.name);
+        expect(responseData.code).toBe(areaData.code);
+        expect(responseData.level).toBe(areaData.level);
+
+        // 断言数据库中存在地区
+        await IntegrationTestAssertions.expectAreaToExist(areaData.name);
+      }
+    });
+
+    it('应该成功获取地区列表', async () => {
+      const response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(Array.isArray(responseData.data)).toBe(true);
+        expect(responseData.data.length).toBeGreaterThanOrEqual(5); // 至少有5个测试地区
+      }
+    });
+
+    it('应该成功获取单个地区详情', async () => {
+      const testArea = testAreas[0];
+
+      const response = await client[':id'].$get({
+        param: { id: testArea.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(responseData.id).toBe(testArea.id);
+        expect(responseData.name).toBe(testArea.name);
+        expect(responseData.level).toBe(testArea.level);
+      }
+    });
+
+    it('应该返回404当地区不存在时', async () => {
+      const response = await client[':id'].$get({
+        param: { id: 999999 }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+      if (response.status === 404) {
+        const responseData = await response.json();
+        expect(responseData.message).toContain('资源不存在');
+      }
+    });
+
+    it('应该成功更新地区信息', async () => {
+      const testArea = testAreas[0];
+      const updateData = {
+        name: '更新后的北京市',
+        code: 'updated_beijing'
+      };
+
+      const response = await client[':id'].$put({
+        param: { id: testArea.id },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(responseData.name).toBe(updateData.name);
+        expect(responseData.code).toBe(updateData.code);
+      }
+    });
+
+    it('应该成功删除地区', async () => {
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      if (!dataSource) throw new Error('Database not initialized');
+
+      const testArea = await TestDataFactory.createTestArea(dataSource, {
+        name: '待删除地区',
+        level: AreaLevel.PROVINCE
+      });
+
+      const response = await client[':id'].$delete({
+        param: { id: testArea.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      IntegrationTestAssertions.expectStatus(response, 204);
+
+      // 验证地区已从数据库中删除
+      await IntegrationTestAssertions.expectAreaNotToExist('待删除地区');
+    });
+  });
+
+  describe('地区搜索测试', () => {
+    it('应该能够按名称搜索地区', async () => {
+      const response = await client.index.$get({
+        query: { keyword: '北京' }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(Array.isArray(responseData.data)).toBe(true);
+        expect(responseData.data.length).toBeGreaterThan(0);
+
+        // 验证搜索结果包含正确的地区
+        const names = responseData.data.map((area: any) => area.name);
+        expect(names).toContain('北京市');
+      }
+    });
+
+    it('应该能够按代码搜索地区', async () => {
+      const response = await client.index.$get({
+        query: { keyword: 'area_' }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(responseData.data.length).toBeGreaterThan(0);
+      }
+    });
+  });
+
+  describe('树形结构查询测试', () => {
+    it('应该成功获取完整树形结构', async () => {
+      const response = await client.tree.$get({}, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(responseData).toHaveProperty('success', true);
+        expect(responseData).toHaveProperty('data');
+        expect(Array.isArray(responseData.data)).toBe(true);
+
+        // 验证树形结构包含省份
+        const provinceNames = responseData.data.map((area: any) => area.name);
+        expect(provinceNames).toContain('北京市');
+        expect(provinceNames).toContain('上海市');
+      }
+    });
+
+    it('应该根据层级获取树形结构', async () => {
+      const response = await client.tree.level[':level'].$get({
+        param: { level: AreaLevel.PROVINCE }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(responseData).toHaveProperty('success', true);
+        expect(responseData).toHaveProperty('data');
+
+        // 验证只返回省级地区
+        const provinces = responseData.data;
+        expect(provinces.length).toBeGreaterThan(0);
+        provinces.forEach((province: any) => {
+          expect(province.level).toBe(AreaLevel.PROVINCE);
+        });
+      }
+    });
+
+    it('应该获取指定节点的子树', async () => {
+      const province = testAreas.find(area => area.name === '北京市');
+      expect(province).toBeDefined();
+
+      const response = await client.tree[':id'].$get({
+        param: { id: province!.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(responseData).toHaveProperty('success', true);
+        expect(responseData).toHaveProperty('data');
+
+        // 验证子树包含城市
+        const subTree = responseData.data;
+        expect(subTree).toHaveProperty('children');
+        if (subTree.children) {
+          const cityNames = subTree.children.map((city: any) => city.name);
+          expect(cityNames).toContain('朝阳区');
+          expect(cityNames).toContain('海淀区');
+        }
+      }
+    });
+
+    it('应该获取区域路径', async () => {
+      const district = testAreas.find(area => area.level === AreaLevel.DISTRICT);
+      expect(district).toBeDefined();
+
+      const response = await client.path[':id'].$get({
+        param: { id: district!.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      if (response.status === 200) {
+        const responseData = await response.json();
+        expect(responseData).toHaveProperty('success', true);
+        expect(responseData).toHaveProperty('data');
+
+        // 验证路径包含正确的层级
+        const path = responseData.data;
+        expect(Array.isArray(path)).toBe(true);
+        expect(path.length).toBeGreaterThan(0);
+
+        // 路径应该包含从省份到区县的完整路径
+        const levels = path.map((area: any) => area.level);
+        expect(levels).toContain(AreaLevel.PROVINCE);
+        expect(levels).toContain(AreaLevel.CITY);
+        expect(levels).toContain(AreaLevel.DISTRICT);
+      }
+    });
+  });
+
+  describe('性能测试', () => {
+    it('地区列表查询响应时间应小于200ms', async () => {
+      const startTime = Date.now();
+      const response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+      const endTime = Date.now();
+      const responseTime = endTime - startTime;
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      expect(responseTime).toBeLessThan(200); // 响应时间应小于200ms
+    });
+
+    it('树形结构查询响应时间应小于300ms', async () => {
+      const startTime = Date.now();
+      const response = await client.tree.$get({}, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+      const endTime = Date.now();
+      const responseTime = endTime - startTime;
+
+      IntegrationTestAssertions.expectStatus(response, 200);
+      expect(responseTime).toBeLessThan(300); // 树形结构查询响应时间应小于300ms
+    });
+  });
+});

+ 52 - 0
packages/geo-areas/tests/utils/integration-test-utils.ts

@@ -0,0 +1,52 @@
+import { DataSource } from 'typeorm';
+import { AreaEntity } from '../../src/modules/areas/area.entity';
+
+export class IntegrationTestAssertions {
+  /**
+   * 断言响应状态码
+   */
+  static expectStatus(response: any, expectedStatus: number): void {
+    expect(response.status).toBe(expectedStatus);
+  }
+
+  /**
+   * 断言地区存在
+   */
+  static async expectAreaToExist(areaName: string): Promise<void> {
+    const dataSource = await this.getDataSource();
+    const areaRepository = dataSource.getRepository(AreaEntity);
+
+    const area = await areaRepository.findOne({
+      where: { name: areaName }
+    });
+
+    expect(area).toBeDefined();
+    expect(area?.name).toBe(areaName);
+  }
+
+  /**
+   * 断言地区不存在
+   */
+  static async expectAreaNotToExist(areaName: string): Promise<void> {
+    const dataSource = await this.getDataSource();
+    const areaRepository = dataSource.getRepository(AreaEntity);
+
+    const area = await areaRepository.findOne({
+      where: { name: areaName }
+    });
+
+    expect(area).toBeNull();
+  }
+
+  /**
+   * 获取数据源
+   */
+  private static async getDataSource(): Promise<DataSource> {
+    const { IntegrationTestDatabase } = await import('@d8d/shared-test-util');
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    if (!dataSource) {
+      throw new Error('Database not initialized');
+    }
+    return dataSource;
+  }
+}