areas.integration.test.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import { describe, it, expect, beforeEach } from 'vitest';
  2. import { testClient } from 'hono/testing';
  3. import {
  4. IntegrationTestDatabase,
  5. setupIntegrationDatabaseHooksWithEntities,
  6. IntegrationTestAssertions
  7. } from '@d8d/shared-test-util';
  8. import { areaRoutes } from '../../src/api/areas';
  9. import { AreaEntity, AreaLevel } from '../../src/modules/areas/area.entity';
  10. import { DisabledStatus } from '@d8d/shared-types';
  11. import { TestDataFactory } from '../utils/test-data-factory';
  12. // 设置集成测试钩子
  13. setupIntegrationDatabaseHooksWithEntities([AreaEntity])
  14. describe('区域API集成测试', () => {
  15. let client: ReturnType<typeof testClient<typeof areaRoutes>>;
  16. let testAreas: AreaEntity[];
  17. beforeEach(async () => {
  18. // 创建测试客户端
  19. client = testClient(areaRoutes);
  20. // 创建测试数据
  21. const dataSource = await IntegrationTestDatabase.getDataSource();
  22. // 创建启用状态的省份
  23. const province1 = await TestDataFactory.createTestArea(dataSource, {
  24. name: '北京市',
  25. level: AreaLevel.PROVINCE,
  26. isDisabled: DisabledStatus.ENABLED
  27. });
  28. const province2 = await TestDataFactory.createTestArea(dataSource, {
  29. name: '上海市',
  30. level: AreaLevel.PROVINCE,
  31. isDisabled: DisabledStatus.ENABLED
  32. });
  33. const province3 = await TestDataFactory.createTestArea(dataSource, {
  34. name: '广东省',
  35. level: AreaLevel.PROVINCE,
  36. isDisabled: DisabledStatus.ENABLED
  37. });
  38. // 创建启用状态的城市
  39. const city11 = await TestDataFactory.createTestArea(dataSource, {
  40. name: '北京市',
  41. level: AreaLevel.CITY,
  42. parentId: province1.id,
  43. isDisabled: DisabledStatus.ENABLED
  44. });
  45. const city12 = await TestDataFactory.createTestArea(dataSource, {
  46. name: '朝阳区',
  47. level: AreaLevel.CITY,
  48. parentId: province1.id,
  49. isDisabled: DisabledStatus.ENABLED
  50. });
  51. const city13 = await TestDataFactory.createTestArea(dataSource, {
  52. name: '海淀区',
  53. level: AreaLevel.CITY,
  54. parentId: province1.id,
  55. isDisabled: DisabledStatus.ENABLED
  56. });
  57. const city21 = await TestDataFactory.createTestArea(dataSource, {
  58. name: '上海市',
  59. level: AreaLevel.CITY,
  60. parentId: province2.id,
  61. isDisabled: DisabledStatus.ENABLED
  62. });
  63. const city22 = await TestDataFactory.createTestArea(dataSource, {
  64. name: '浦东新区',
  65. level: AreaLevel.CITY,
  66. parentId: province2.id,
  67. isDisabled: DisabledStatus.ENABLED
  68. });
  69. // 创建启用状态的区县
  70. const district101 = await TestDataFactory.createTestArea(dataSource, {
  71. name: '朝阳区',
  72. level: AreaLevel.DISTRICT,
  73. parentId: city12.id,
  74. isDisabled: DisabledStatus.ENABLED
  75. });
  76. const district102 = await TestDataFactory.createTestArea(dataSource, {
  77. name: '海淀区',
  78. level: AreaLevel.DISTRICT,
  79. parentId: city13.id,
  80. isDisabled: DisabledStatus.ENABLED
  81. });
  82. const district103 = await TestDataFactory.createTestArea(dataSource, {
  83. name: '西城区',
  84. level: AreaLevel.DISTRICT,
  85. parentId: city12.id,
  86. isDisabled: DisabledStatus.ENABLED
  87. });
  88. const district201 = await TestDataFactory.createTestArea(dataSource, {
  89. name: '浦东新区',
  90. level: AreaLevel.DISTRICT,
  91. parentId: city22.id,
  92. isDisabled: DisabledStatus.ENABLED
  93. });
  94. // 创建禁用状态的区域用于测试过滤
  95. const disabledProvince = await TestDataFactory.createTestArea(dataSource, {
  96. name: '禁用省份',
  97. level: AreaLevel.PROVINCE,
  98. isDisabled: DisabledStatus.DISABLED
  99. });
  100. const disabledCity = await TestDataFactory.createTestArea(dataSource, {
  101. name: '禁用城市',
  102. level: AreaLevel.CITY,
  103. parentId: province3.id,
  104. isDisabled: DisabledStatus.DISABLED
  105. });
  106. const disabledDistrict = await TestDataFactory.createTestArea(dataSource, {
  107. name: '禁用区县',
  108. level: AreaLevel.DISTRICT,
  109. parentId: city12.id,
  110. isDisabled: DisabledStatus.DISABLED
  111. });
  112. testAreas = [
  113. province1, province2, province3,
  114. city11, city12, city13, city21, city22,
  115. district101, district102, district103, district201,
  116. disabledProvince, disabledCity, disabledDistrict
  117. ];
  118. });
  119. describe('GET /areas/provinces', () => {
  120. it('应该成功获取启用状态的省份列表', async () => {
  121. const response = await client.provinces.$get({
  122. query: { page: 1, pageSize: 50 }
  123. });
  124. IntegrationTestAssertions.expectStatus(response, 200);
  125. if (response.status === 200) {
  126. const data = await response.json();
  127. // 验证响应数据格式
  128. expect(data).toHaveProperty('success', true);
  129. expect(data).toHaveProperty('data');
  130. expect(data.data).toHaveProperty('provinces');
  131. expect(data.data).toHaveProperty('pagination');
  132. // 验证只返回启用状态的省份
  133. const provinces = data.data.provinces;
  134. expect(provinces).toHaveLength(3); // 只返回3个启用状态的省份
  135. // 验证不包含禁用状态的省份
  136. const disabledProvince = provinces.find((p: any) => p.isDisabled === DisabledStatus.DISABLED);
  137. expect(disabledProvince).toBeUndefined();
  138. // 验证分页信息
  139. expect(data.data.pagination).toEqual({
  140. page: 1,
  141. pageSize: 50,
  142. total: 3,
  143. totalPages: 1
  144. });
  145. }
  146. });
  147. it('应该正确处理分页参数', async () => {
  148. const response = await client.provinces.$get({
  149. query: { page: 1, pageSize: 2 }
  150. });
  151. IntegrationTestAssertions.expectStatus(response, 200);
  152. if (response.status === 200) {
  153. const data = await response.json();
  154. // 验证分页结果
  155. expect(data.data.provinces).toHaveLength(2);
  156. expect(data.data.pagination).toEqual({
  157. page: 1,
  158. pageSize: 2,
  159. total: 3,
  160. totalPages: 2
  161. });
  162. }
  163. });
  164. });
  165. describe('GET /areas/cities', () => {
  166. it('应该成功获取指定省份下启用状态的城市列表', async () => {
  167. const response = await client.cities.$get({
  168. query: { provinceId: testAreas[0].id, page: 1, pageSize: 50 }
  169. });
  170. IntegrationTestAssertions.expectStatus(response, 200);
  171. if (response.status === 200) {
  172. const data = await response.json();
  173. // 验证响应数据格式
  174. expect(data).toHaveProperty('success', true);
  175. expect(data).toHaveProperty('data');
  176. expect(data.data).toHaveProperty('cities');
  177. // 验证只返回启用状态的城市
  178. const cities = data.data.cities;
  179. expect(cities).toHaveLength(3); // 北京市下有3个启用状态的城市
  180. // 验证城市数据正确
  181. const cityNames = cities.map((c: any) => c.name);
  182. expect(cityNames).toContain('北京市');
  183. expect(cityNames).toContain('朝阳区');
  184. expect(cityNames).toContain('海淀区');
  185. // 验证不包含禁用状态的城市
  186. const disabledCity = cities.find((c: any) => c.name === '禁用城市');
  187. expect(disabledCity).toBeUndefined();
  188. }
  189. });
  190. it('应该处理不存在的省份ID', async () => {
  191. const response = await client.cities.$get({
  192. query: { provinceId: 999, page: 1, pageSize: 50 }
  193. });
  194. IntegrationTestAssertions.expectStatus(response, 200);
  195. if (response.status === 200) {
  196. const data = await response.json();
  197. // 不存在的省份应该返回空数组
  198. expect(data.data.cities).toHaveLength(0);
  199. }
  200. });
  201. it('应该验证省份ID参数', async () => {
  202. const response = await client.cities.$get({
  203. query: { provinceId: 0, page: 1, pageSize: 50 }
  204. });
  205. // 参数验证应该返回400错误
  206. IntegrationTestAssertions.expectStatus(response, 400);
  207. });
  208. });
  209. describe('GET /areas/districts', () => {
  210. it('应该成功获取指定城市下启用状态的区县列表', async () => {
  211. // 找到朝阳区城市对象
  212. const chaoyangCity = testAreas.find(area => area.name === '朝阳区' && area.level === AreaLevel.CITY);
  213. expect(chaoyangCity).toBeDefined();
  214. const response = await client.districts.$get({
  215. query: { cityId: chaoyangCity!.id, page: 1, pageSize: 50 }
  216. });
  217. IntegrationTestAssertions.expectStatus(response, 200);
  218. if (response.status === 200) {
  219. const data = await response.json();
  220. // 验证响应数据格式
  221. expect(data).toHaveProperty('success', true);
  222. expect(data).toHaveProperty('data');
  223. expect(data.data).toHaveProperty('districts');
  224. // 验证只返回启用状态的区县
  225. const districts = data.data.districts;
  226. expect(districts).toHaveLength(2); // 朝阳区下有2个启用状态的区县
  227. // 验证区县数据正确
  228. const districtNames = districts.map((d: any) => d.name);
  229. expect(districtNames).toContain('朝阳区');
  230. expect(districtNames).toContain('西城区');
  231. // 验证不包含禁用状态的区县
  232. const disabledDistrict = districts.find((d: any) => d.name === '禁用区县');
  233. expect(disabledDistrict).toBeUndefined();
  234. }
  235. });
  236. it('应该处理不存在的城市ID', async () => {
  237. const response = await client.districts.$get({
  238. query: { cityId: 999, page: 1, pageSize: 50 }
  239. });
  240. IntegrationTestAssertions.expectStatus(response, 200);
  241. if (response.status === 200) {
  242. const data = await response.json();
  243. // 不存在的城市应该返回空数组
  244. expect(data.data.districts).toHaveLength(0);
  245. }
  246. });
  247. it('应该验证城市ID参数', async () => {
  248. const response = await client.districts.$get({
  249. query: { cityId: 0, page: 1, pageSize: 50 }
  250. });
  251. // 参数验证应该返回400错误
  252. IntegrationTestAssertions.expectStatus(response, 400);
  253. });
  254. });
  255. describe('过滤禁用状态验证', () => {
  256. it('应该确保所有API只返回启用状态的区域', async () => {
  257. // 测试省份API
  258. const provincesResponse = await client.provinces.$get({
  259. query: { page: 1, pageSize: 50 }
  260. });
  261. IntegrationTestAssertions.expectStatus(provincesResponse, 200);
  262. const provincesData = await provincesResponse.json();
  263. // 验证省份不包含禁用状态
  264. if ('data' in provincesData) {
  265. const provinces = provincesData.data.provinces;
  266. const hasDisabledProvince = provinces.some((p: any) => p.isDisabled === DisabledStatus.DISABLED);
  267. expect(hasDisabledProvince).toBe(false);
  268. }
  269. // 测试城市API
  270. const citiesResponse = await client.cities.$get({
  271. query: { provinceId: testAreas[0].id, page: 1, pageSize: 50 }
  272. });
  273. IntegrationTestAssertions.expectStatus(citiesResponse, 200);
  274. const citiesData = await citiesResponse.json();
  275. // 验证城市不包含禁用状态
  276. if ('data' in citiesData) {
  277. const cities = citiesData.data.cities;
  278. const hasDisabledCity = cities.some((c: any) => c.isDisabled === DisabledStatus.DISABLED);
  279. expect(hasDisabledCity).toBe(false);
  280. }
  281. // 测试区县API
  282. const chaoyangCity = testAreas.find(area => area.name === '朝阳区' && area.level === AreaLevel.CITY);
  283. const districtsResponse = await client.districts.$get({
  284. query: { cityId: chaoyangCity!.id, page: 1, pageSize: 50 }
  285. });
  286. IntegrationTestAssertions.expectStatus(districtsResponse, 200);
  287. const districtsData = await districtsResponse.json();
  288. // 验证区县不包含禁用状态
  289. if ('data' in districtsData) {
  290. const districts = districtsData.data.districts;
  291. const hasDisabledDistrict = districts.some((d: any) => d.isDisabled === DisabledStatus.DISABLED);
  292. expect(hasDisabledDistrict).toBe(false);
  293. }
  294. });
  295. });
  296. });