|
|
@@ -0,0 +1,349 @@
|
|
|
+import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
+import { testClient } from 'hono/testing';
|
|
|
+import {
|
|
|
+ IntegrationTestDatabase,
|
|
|
+ setupIntegrationDatabaseHooksWithEntities,
|
|
|
+ IntegrationTestAssertions
|
|
|
+} from '@d8d/shared-test-util';
|
|
|
+import { areaRoutes } from '../../src/api/areas';
|
|
|
+import { AreaEntity, AreaLevel } from '../../src/modules/areas/area.entity';
|
|
|
+import { DisabledStatus } from '@d8d/shared-types';
|
|
|
+import { TestDataFactory } from '../utils/test-data-factory';
|
|
|
+
|
|
|
+// 设置集成测试钩子
|
|
|
+setupIntegrationDatabaseHooksWithEntities([AreaEntity])
|
|
|
+
|
|
|
+describe('区域API集成测试', () => {
|
|
|
+ let client: ReturnType<typeof testClient<typeof areaRoutes>>;
|
|
|
+ let testAreas: AreaEntity[];
|
|
|
+
|
|
|
+ beforeEach(async () => {
|
|
|
+ // 创建测试客户端
|
|
|
+ client = testClient(areaRoutes);
|
|
|
+
|
|
|
+ // 创建测试数据
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+
|
|
|
+ // 创建启用状态的省份
|
|
|
+ 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 province3 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '广东省',
|
|
|
+ level: AreaLevel.PROVINCE,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建启用状态的城市
|
|
|
+ const city11 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '北京市',
|
|
|
+ level: AreaLevel.CITY,
|
|
|
+ parentId: province1.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+ const city12 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '朝阳区',
|
|
|
+ level: AreaLevel.CITY,
|
|
|
+ parentId: province1.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+ const city13 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '海淀区',
|
|
|
+ level: AreaLevel.CITY,
|
|
|
+ parentId: province1.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+ const city21 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '上海市',
|
|
|
+ level: AreaLevel.CITY,
|
|
|
+ parentId: province2.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+ const city22 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '浦东新区',
|
|
|
+ level: AreaLevel.CITY,
|
|
|
+ parentId: province2.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建启用状态的区县
|
|
|
+ const district101 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '朝阳区',
|
|
|
+ level: AreaLevel.DISTRICT,
|
|
|
+ parentId: city12.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+ const district102 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '海淀区',
|
|
|
+ level: AreaLevel.DISTRICT,
|
|
|
+ parentId: city13.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+ const district103 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '西城区',
|
|
|
+ level: AreaLevel.DISTRICT,
|
|
|
+ parentId: city12.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+ const district201 = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '浦东新区',
|
|
|
+ level: AreaLevel.DISTRICT,
|
|
|
+ parentId: city22.id,
|
|
|
+ isDisabled: DisabledStatus.ENABLED
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建禁用状态的区域用于测试过滤
|
|
|
+ const disabledProvince = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '禁用省份',
|
|
|
+ level: AreaLevel.PROVINCE,
|
|
|
+ isDisabled: DisabledStatus.DISABLED
|
|
|
+ });
|
|
|
+ const disabledCity = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '禁用城市',
|
|
|
+ level: AreaLevel.CITY,
|
|
|
+ parentId: province3.id,
|
|
|
+ isDisabled: DisabledStatus.DISABLED
|
|
|
+ });
|
|
|
+ const disabledDistrict = await TestDataFactory.createTestArea(dataSource, {
|
|
|
+ name: '禁用区县',
|
|
|
+ level: AreaLevel.DISTRICT,
|
|
|
+ parentId: city12.id,
|
|
|
+ isDisabled: DisabledStatus.DISABLED
|
|
|
+ });
|
|
|
+
|
|
|
+ testAreas = [
|
|
|
+ province1, province2, province3,
|
|
|
+ city11, city12, city13, city21, city22,
|
|
|
+ district101, district102, district103, district201,
|
|
|
+ disabledProvince, disabledCity, disabledDistrict
|
|
|
+ ];
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('GET /areas/provinces', () => {
|
|
|
+ it('应该成功获取启用状态的省份列表', async () => {
|
|
|
+ const response = await client.provinces.$get({
|
|
|
+ query: { page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ // 验证响应数据格式
|
|
|
+ expect(data).toHaveProperty('success', true);
|
|
|
+ expect(data).toHaveProperty('data');
|
|
|
+ expect(data.data).toHaveProperty('provinces');
|
|
|
+ expect(data.data).toHaveProperty('pagination');
|
|
|
+
|
|
|
+ // 验证只返回启用状态的省份
|
|
|
+ const provinces = data.data.provinces;
|
|
|
+ expect(provinces).toHaveLength(3); // 只返回3个启用状态的省份
|
|
|
+
|
|
|
+ // 验证不包含禁用状态的省份
|
|
|
+ const disabledProvince = provinces.find((p: any) => p.isDisabled === DisabledStatus.DISABLED);
|
|
|
+ expect(disabledProvince).toBeUndefined();
|
|
|
+
|
|
|
+ // 验证分页信息
|
|
|
+ expect(data.data.pagination).toEqual({
|
|
|
+ page: 1,
|
|
|
+ pageSize: 50,
|
|
|
+ total: 3,
|
|
|
+ totalPages: 1
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理分页参数', async () => {
|
|
|
+ const response = await client.provinces.$get({
|
|
|
+ query: { page: 1, pageSize: 2 }
|
|
|
+ });
|
|
|
+
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ // 验证分页结果
|
|
|
+ expect(data.data.provinces).toHaveLength(2);
|
|
|
+ expect(data.data.pagination).toEqual({
|
|
|
+ page: 1,
|
|
|
+ pageSize: 2,
|
|
|
+ total: 3,
|
|
|
+ totalPages: 2
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('GET /areas/cities', () => {
|
|
|
+ it('应该成功获取指定省份下启用状态的城市列表', async () => {
|
|
|
+ const response = await client.cities.$get({
|
|
|
+ query: { provinceId: testAreas[0].id, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ // 验证响应数据格式
|
|
|
+ expect(data).toHaveProperty('success', true);
|
|
|
+ expect(data).toHaveProperty('data');
|
|
|
+ expect(data.data).toHaveProperty('cities');
|
|
|
+
|
|
|
+ // 验证只返回启用状态的城市
|
|
|
+ const cities = data.data.cities;
|
|
|
+ expect(cities).toHaveLength(3); // 北京市下有3个启用状态的城市
|
|
|
+
|
|
|
+ // 验证城市数据正确
|
|
|
+ const cityNames = cities.map((c: any) => c.name);
|
|
|
+ expect(cityNames).toContain('北京市');
|
|
|
+ expect(cityNames).toContain('朝阳区');
|
|
|
+ expect(cityNames).toContain('海淀区');
|
|
|
+
|
|
|
+ // 验证不包含禁用状态的城市
|
|
|
+ const disabledCity = cities.find((c: any) => c.name === '禁用城市');
|
|
|
+ expect(disabledCity).toBeUndefined();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该处理不存在的省份ID', async () => {
|
|
|
+ const response = await client.cities.$get({
|
|
|
+ query: { provinceId: 999, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ // 不存在的省份应该返回空数组
|
|
|
+ expect(data.data.cities).toHaveLength(0);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证省份ID参数', async () => {
|
|
|
+ const response = await client.cities.$get({
|
|
|
+ query: { provinceId: 0, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 参数验证应该返回400错误
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 400);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('GET /areas/districts', () => {
|
|
|
+ it('应该成功获取指定城市下启用状态的区县列表', async () => {
|
|
|
+ // 找到朝阳区城市对象
|
|
|
+ const chaoyangCity = testAreas.find(area => area.name === '朝阳区' && area.level === AreaLevel.CITY);
|
|
|
+ expect(chaoyangCity).toBeDefined();
|
|
|
+
|
|
|
+ const response = await client.districts.$get({
|
|
|
+ query: { cityId: chaoyangCity!.id, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ // 验证响应数据格式
|
|
|
+ expect(data).toHaveProperty('success', true);
|
|
|
+ expect(data).toHaveProperty('data');
|
|
|
+ expect(data.data).toHaveProperty('districts');
|
|
|
+
|
|
|
+ // 验证只返回启用状态的区县
|
|
|
+ const districts = data.data.districts;
|
|
|
+ expect(districts).toHaveLength(2); // 朝阳区下有2个启用状态的区县
|
|
|
+
|
|
|
+ // 验证区县数据正确
|
|
|
+ const districtNames = districts.map((d: any) => d.name);
|
|
|
+ expect(districtNames).toContain('朝阳区');
|
|
|
+ expect(districtNames).toContain('西城区');
|
|
|
+
|
|
|
+ // 验证不包含禁用状态的区县
|
|
|
+ const disabledDistrict = districts.find((d: any) => d.name === '禁用区县');
|
|
|
+ expect(disabledDistrict).toBeUndefined();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该处理不存在的城市ID', async () => {
|
|
|
+ const response = await client.districts.$get({
|
|
|
+ query: { cityId: 999, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 200);
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ // 不存在的城市应该返回空数组
|
|
|
+ expect(data.data.districts).toHaveLength(0);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证城市ID参数', async () => {
|
|
|
+ const response = await client.districts.$get({
|
|
|
+ query: { cityId: 0, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 参数验证应该返回400错误
|
|
|
+ IntegrationTestAssertions.expectStatus(response, 400);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('过滤禁用状态验证', () => {
|
|
|
+ it('应该确保所有API只返回启用状态的区域', async () => {
|
|
|
+ // 测试省份API
|
|
|
+ const provincesResponse = await client.provinces.$get({
|
|
|
+ query: { page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+ IntegrationTestAssertions.expectStatus(provincesResponse, 200);
|
|
|
+ const provincesData = await provincesResponse.json();
|
|
|
+
|
|
|
+ // 验证省份不包含禁用状态
|
|
|
+ if ('data' in provincesData) {
|
|
|
+ const provinces = provincesData.data.provinces;
|
|
|
+ const hasDisabledProvince = provinces.some((p: any) => p.isDisabled === DisabledStatus.DISABLED);
|
|
|
+ expect(hasDisabledProvince).toBe(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 测试城市API
|
|
|
+ const citiesResponse = await client.cities.$get({
|
|
|
+ query: { provinceId: testAreas[0].id, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+ IntegrationTestAssertions.expectStatus(citiesResponse, 200);
|
|
|
+ const citiesData = await citiesResponse.json();
|
|
|
+
|
|
|
+ // 验证城市不包含禁用状态
|
|
|
+ if ('data' in citiesData) {
|
|
|
+ const cities = citiesData.data.cities;
|
|
|
+ const hasDisabledCity = cities.some((c: any) => c.isDisabled === DisabledStatus.DISABLED);
|
|
|
+ expect(hasDisabledCity).toBe(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 测试区县API
|
|
|
+ const chaoyangCity = testAreas.find(area => area.name === '朝阳区' && area.level === AreaLevel.CITY);
|
|
|
+ const districtsResponse = await client.districts.$get({
|
|
|
+ query: { cityId: chaoyangCity!.id, page: 1, pageSize: 50 }
|
|
|
+ });
|
|
|
+ IntegrationTestAssertions.expectStatus(districtsResponse, 200);
|
|
|
+ const districtsData = await districtsResponse.json();
|
|
|
+
|
|
|
+ // 验证区县不包含禁用状态
|
|
|
+ if ('data' in districtsData) {
|
|
|
+ const districts = districtsData.data.districts;
|
|
|
+ const hasDisabledDistrict = districts.some((d: any) => d.isDisabled === DisabledStatus.DISABLED);
|
|
|
+ expect(hasDisabledDistrict).toBe(false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|