areas.integration.test.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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 { areasRoutesMt } from '../../src/api/areas/index.mt';
  9. import { AreaEntityMt, AreaLevel } from '../../src/modules/areas/area.entity.mt';
  10. import { DisabledStatus } from '@d8d/shared-types';
  11. import { TestDataFactory } from '../utils/test-data-factory';
  12. // 设置集成测试钩子
  13. setupIntegrationDatabaseHooksWithEntities([AreaEntityMt])
  14. describe('区域API集成测试', () => {
  15. let client: ReturnType<typeof testClient<typeof areasRoutesMt>>;
  16. let testAreas: AreaEntityMt[];
  17. beforeEach(async () => {
  18. // 创建测试客户端
  19. client = testClient(areasRoutesMt);
  20. // 创建测试数据
  21. const dataSource = await IntegrationTestDatabase.getDataSource();
  22. // 创建启用状态的省份(租户1)
  23. const province1 = await TestDataFactory.createTestArea(dataSource, {
  24. name: '北京市',
  25. level: AreaLevel.PROVINCE,
  26. isDisabled: DisabledStatus.ENABLED,
  27. tenantId: 1
  28. });
  29. const province2 = await TestDataFactory.createTestArea(dataSource, {
  30. name: '上海市',
  31. level: AreaLevel.PROVINCE,
  32. isDisabled: DisabledStatus.ENABLED,
  33. tenantId: 1
  34. });
  35. const province3 = await TestDataFactory.createTestArea(dataSource, {
  36. name: '广东省',
  37. level: AreaLevel.PROVINCE,
  38. isDisabled: DisabledStatus.ENABLED,
  39. tenantId: 1
  40. });
  41. // 创建启用状态的城市
  42. const city11 = await TestDataFactory.createTestArea(dataSource, {
  43. name: '北京市',
  44. level: AreaLevel.CITY,
  45. parentId: province1.id,
  46. isDisabled: DisabledStatus.ENABLED
  47. });
  48. const city12 = await TestDataFactory.createTestArea(dataSource, {
  49. name: '朝阳区',
  50. level: AreaLevel.CITY,
  51. parentId: province1.id,
  52. isDisabled: DisabledStatus.ENABLED
  53. });
  54. const city13 = await TestDataFactory.createTestArea(dataSource, {
  55. name: '海淀区',
  56. level: AreaLevel.CITY,
  57. parentId: province1.id,
  58. isDisabled: DisabledStatus.ENABLED
  59. });
  60. const city21 = await TestDataFactory.createTestArea(dataSource, {
  61. name: '上海市',
  62. level: AreaLevel.CITY,
  63. parentId: province2.id,
  64. isDisabled: DisabledStatus.ENABLED
  65. });
  66. const city22 = await TestDataFactory.createTestArea(dataSource, {
  67. name: '浦东新区',
  68. level: AreaLevel.CITY,
  69. parentId: province2.id,
  70. isDisabled: DisabledStatus.ENABLED
  71. });
  72. // 创建启用状态的区县
  73. const district101 = await TestDataFactory.createTestArea(dataSource, {
  74. name: '朝阳区',
  75. level: AreaLevel.DISTRICT,
  76. parentId: city12.id,
  77. isDisabled: DisabledStatus.ENABLED
  78. });
  79. const district102 = await TestDataFactory.createTestArea(dataSource, {
  80. name: '海淀区',
  81. level: AreaLevel.DISTRICT,
  82. parentId: city13.id,
  83. isDisabled: DisabledStatus.ENABLED
  84. });
  85. const district103 = await TestDataFactory.createTestArea(dataSource, {
  86. name: '西城区',
  87. level: AreaLevel.DISTRICT,
  88. parentId: city12.id,
  89. isDisabled: DisabledStatus.ENABLED
  90. });
  91. const district201 = await TestDataFactory.createTestArea(dataSource, {
  92. name: '浦东新区',
  93. level: AreaLevel.DISTRICT,
  94. parentId: city22.id,
  95. isDisabled: DisabledStatus.ENABLED
  96. });
  97. // 创建禁用状态的区域用于测试过滤
  98. const disabledProvince = await TestDataFactory.createTestArea(dataSource, {
  99. name: '禁用省份',
  100. level: AreaLevel.PROVINCE,
  101. isDisabled: DisabledStatus.DISABLED
  102. });
  103. const disabledCity = await TestDataFactory.createTestArea(dataSource, {
  104. name: '禁用城市',
  105. level: AreaLevel.CITY,
  106. parentId: province3.id,
  107. isDisabled: DisabledStatus.DISABLED
  108. });
  109. const disabledDistrict = await TestDataFactory.createTestArea(dataSource, {
  110. name: '禁用区县',
  111. level: AreaLevel.DISTRICT,
  112. parentId: city12.id,
  113. isDisabled: DisabledStatus.DISABLED
  114. });
  115. testAreas = [
  116. province1, province2, province3,
  117. city11, city12, city13, city21, city22,
  118. district101, district102, district103, district201,
  119. disabledProvince, disabledCity, disabledDistrict
  120. ];
  121. });
  122. describe('GET /areas/provinces', () => {
  123. it('应该成功获取启用状态的省份列表', async () => {
  124. const response = await client.provinces.$get({
  125. query: { page: 1, pageSize: 50 }
  126. });
  127. IntegrationTestAssertions.expectStatus(response, 200);
  128. if (response.status === 200) {
  129. const data = await response.json();
  130. // 验证响应数据格式
  131. expect(data).toHaveProperty('success', true);
  132. expect(data).toHaveProperty('data');
  133. expect(data.data).toHaveProperty('provinces');
  134. expect(data.data).toHaveProperty('pagination');
  135. // 验证只返回启用状态的省份
  136. const provinces = data.data.provinces;
  137. expect(provinces).toHaveLength(3); // 只返回3个启用状态的省份
  138. // 验证不包含禁用状态的省份
  139. const disabledProvince = provinces.find((p: any) => p.isDisabled === DisabledStatus.DISABLED);
  140. expect(disabledProvince).toBeUndefined();
  141. // 验证分页信息
  142. expect(data.data.pagination).toEqual({
  143. page: 1,
  144. pageSize: 50,
  145. total: 3,
  146. totalPages: 1
  147. });
  148. }
  149. });
  150. it('应该正确处理分页参数', async () => {
  151. const response = await client.provinces.$get({
  152. query: { page: 1, pageSize: 2 }
  153. });
  154. IntegrationTestAssertions.expectStatus(response, 200);
  155. if (response.status === 200) {
  156. const data = await response.json();
  157. // 验证分页结果
  158. expect(data.data.provinces).toHaveLength(2);
  159. expect(data.data.pagination).toEqual({
  160. page: 1,
  161. pageSize: 2,
  162. total: 3,
  163. totalPages: 2
  164. });
  165. }
  166. });
  167. });
  168. describe('GET /areas/cities', () => {
  169. it('应该成功获取指定省份下启用状态的城市列表', async () => {
  170. const response = await client.cities.$get({
  171. query: { provinceId: testAreas[0].id, page: 1, pageSize: 50 }
  172. });
  173. IntegrationTestAssertions.expectStatus(response, 200);
  174. if (response.status === 200) {
  175. const data = await response.json();
  176. // 验证响应数据格式
  177. expect(data).toHaveProperty('success', true);
  178. expect(data).toHaveProperty('data');
  179. expect(data.data).toHaveProperty('cities');
  180. // 验证只返回启用状态的城市
  181. const cities = data.data.cities;
  182. expect(cities).toHaveLength(3); // 北京市下有3个启用状态的城市
  183. // 验证城市数据正确
  184. const cityNames = cities.map((c: any) => c.name);
  185. expect(cityNames).toContain('北京市');
  186. expect(cityNames).toContain('朝阳区');
  187. expect(cityNames).toContain('海淀区');
  188. // 验证不包含禁用状态的城市
  189. const disabledCity = cities.find((c: any) => c.name === '禁用城市');
  190. expect(disabledCity).toBeUndefined();
  191. }
  192. });
  193. it('应该处理不存在的省份ID', async () => {
  194. const response = await client.cities.$get({
  195. query: { provinceId: 999, page: 1, pageSize: 50 }
  196. });
  197. IntegrationTestAssertions.expectStatus(response, 200);
  198. if (response.status === 200) {
  199. const data = await response.json();
  200. // 不存在的省份应该返回空数组
  201. expect(data.data.cities).toHaveLength(0);
  202. }
  203. });
  204. it('应该验证省份ID参数', async () => {
  205. const response = await client.cities.$get({
  206. query: { provinceId: 0, page: 1, pageSize: 50 }
  207. });
  208. // 参数验证应该返回400错误
  209. IntegrationTestAssertions.expectStatus(response, 400);
  210. });
  211. });
  212. describe('GET /areas/districts', () => {
  213. it('应该成功获取指定城市下启用状态的区县列表', async () => {
  214. // 找到朝阳区城市对象
  215. const chaoyangCity = testAreas.find(area => area.name === '朝阳区' && area.level === AreaLevel.CITY);
  216. expect(chaoyangCity).toBeDefined();
  217. const response = await client.districts.$get({
  218. query: { cityId: chaoyangCity!.id, page: 1, pageSize: 50 }
  219. });
  220. IntegrationTestAssertions.expectStatus(response, 200);
  221. if (response.status === 200) {
  222. const data = await response.json();
  223. // 验证响应数据格式
  224. expect(data).toHaveProperty('success', true);
  225. expect(data).toHaveProperty('data');
  226. expect(data.data).toHaveProperty('districts');
  227. // 验证只返回启用状态的区县
  228. const districts = data.data.districts;
  229. expect(districts).toHaveLength(2); // 朝阳区下有2个启用状态的区县
  230. // 验证区县数据正确
  231. const districtNames = districts.map((d: any) => d.name);
  232. expect(districtNames).toContain('朝阳区');
  233. expect(districtNames).toContain('西城区');
  234. // 验证不包含禁用状态的区县
  235. const disabledDistrict = districts.find((d: any) => d.name === '禁用区县');
  236. expect(disabledDistrict).toBeUndefined();
  237. }
  238. });
  239. it('应该处理不存在的城市ID', async () => {
  240. const response = await client.districts.$get({
  241. query: { cityId: 999, page: 1, pageSize: 50 }
  242. });
  243. IntegrationTestAssertions.expectStatus(response, 200);
  244. if (response.status === 200) {
  245. const data = await response.json();
  246. // 不存在的城市应该返回空数组
  247. expect(data.data.districts).toHaveLength(0);
  248. }
  249. });
  250. it('应该验证城市ID参数', async () => {
  251. const response = await client.districts.$get({
  252. query: { cityId: 0, page: 1, pageSize: 50 }
  253. });
  254. // 参数验证应该返回400错误
  255. IntegrationTestAssertions.expectStatus(response, 400);
  256. });
  257. });
  258. describe('过滤禁用状态验证', () => {
  259. it('应该确保所有API只返回启用状态的区域', async () => {
  260. // 测试省份API
  261. const provincesResponse = await client.provinces.$get({
  262. query: { page: 1, pageSize: 50 }
  263. });
  264. IntegrationTestAssertions.expectStatus(provincesResponse, 200);
  265. const provincesData = await provincesResponse.json();
  266. // 验证省份不包含禁用状态
  267. if ('data' in provincesData) {
  268. const provinces = provincesData.data.provinces;
  269. const hasDisabledProvince = provinces.some((p: any) => p.isDisabled === DisabledStatus.DISABLED);
  270. expect(hasDisabledProvince).toBe(false);
  271. }
  272. // 测试城市API
  273. const citiesResponse = await client.cities.$get({
  274. query: { provinceId: testAreas[0].id, page: 1, pageSize: 50 }
  275. });
  276. IntegrationTestAssertions.expectStatus(citiesResponse, 200);
  277. const citiesData = await citiesResponse.json();
  278. // 验证城市不包含禁用状态
  279. if ('data' in citiesData) {
  280. const cities = citiesData.data.cities;
  281. const hasDisabledCity = cities.some((c: any) => c.isDisabled === DisabledStatus.DISABLED);
  282. expect(hasDisabledCity).toBe(false);
  283. }
  284. // 测试区县API
  285. const chaoyangCity = testAreas.find(area => area.name === '朝阳区' && area.level === AreaLevel.CITY);
  286. const districtsResponse = await client.districts.$get({
  287. query: { cityId: chaoyangCity!.id, page: 1, pageSize: 50 }
  288. });
  289. IntegrationTestAssertions.expectStatus(districtsResponse, 200);
  290. const districtsData = await districtsResponse.json();
  291. // 验证区县不包含禁用状态
  292. if ('data' in districtsData) {
  293. const districts = districtsData.data.districts;
  294. const hasDisabledDistrict = districts.some((d: any) => d.isDisabled === DisabledStatus.DISABLED);
  295. expect(hasDisabledDistrict).toBe(false);
  296. }
  297. });
  298. });
  299. describe('租户数据隔离测试', () => {
  300. beforeEach(async () => {
  301. // 为租户2创建测试数据
  302. const dataSource = await IntegrationTestDatabase.getDataSource();
  303. // 租户2的省份
  304. await TestDataFactory.createTestArea(dataSource, {
  305. name: '租户2-北京市',
  306. level: AreaLevel.PROVINCE,
  307. isDisabled: DisabledStatus.ENABLED,
  308. tenantId: 2
  309. });
  310. // 租户2的城市
  311. await TestDataFactory.createTestArea(dataSource, {
  312. name: '租户2-上海市',
  313. level: AreaLevel.PROVINCE,
  314. isDisabled: DisabledStatus.ENABLED,
  315. tenantId: 2
  316. });
  317. });
  318. it('应该只返回指定租户的数据', async () => {
  319. // 测试租户1的数据
  320. const response1 = await client.provinces.$get({
  321. query: { tenantId: 1, page: 1, pageSize: 50 }
  322. });
  323. IntegrationTestAssertions.expectStatus(response1, 200);
  324. const data1 = await response1.json();
  325. // 验证租户1只看到租户1的数据
  326. const tenant1Provinces = data1.data.provinces;
  327. expect(tenant1Provinces).toHaveLength(3); // 租户1有3个省份
  328. const hasTenant2Data = tenant1Provinces.some((p: any) => p.name.includes('租户2'));
  329. expect(hasTenant2Data).toBe(false);
  330. // 测试租户2的数据
  331. const response2 = await client.provinces.$get({
  332. query: { tenantId: 2, page: 1, pageSize: 50 }
  333. });
  334. IntegrationTestAssertions.expectStatus(response2, 200);
  335. const data2 = await response2.json();
  336. // 验证租户2只看到租户2的数据
  337. const tenant2Provinces = data2.data.provinces;
  338. expect(tenant2Provinces).toHaveLength(2); // 租户2有2个省份
  339. const hasTenant1Data = tenant2Provinces.some((p: any) => p.name.includes('北京市') && !p.name.includes('租户2'));
  340. expect(hasTenant1Data).toBe(false);
  341. });
  342. it('不同租户的数据应该完全隔离', async () => {
  343. // 租户1查询省份
  344. const response1 = await client.provinces.$get({
  345. query: { tenantId: 1, page: 1, pageSize: 50 }
  346. });
  347. IntegrationTestAssertions.expectStatus(response1, 200);
  348. const data1 = await response1.json();
  349. // 租户2查询省份
  350. const response2 = await client.provinces.$get({
  351. query: { tenantId: 2, page: 1, pageSize: 50 }
  352. });
  353. IntegrationTestAssertions.expectStatus(response2, 200);
  354. const data2 = await response2.json();
  355. // 验证两个租户的数据完全不同
  356. const tenant1Names = data1.data.provinces.map((p: any) => p.name);
  357. const tenant2Names = data2.data.provinces.map((p: any) => p.name);
  358. expect(tenant1Names).not.toEqual(tenant2Names);
  359. expect(tenant1Names).toContain('北京市');
  360. expect(tenant1Names).toContain('上海市');
  361. expect(tenant1Names).toContain('广东省');
  362. expect(tenant2Names).toContain('租户2-北京市');
  363. expect(tenant2Names).toContain('租户2-上海市');
  364. });
  365. it('应该验证tenantId参数', async () => {
  366. // 测试缺少tenantId参数
  367. const response = await client.provinces.$get({
  368. query: { page: 1, pageSize: 50 }
  369. });
  370. // 应该返回400错误,因为缺少必需的tenantId参数
  371. IntegrationTestAssertions.expectStatus(response, 400);
  372. });
  373. it('应该处理不存在的租户ID', async () => {
  374. const response = await client.provinces.$get({
  375. query: { tenantId: 999, page: 1, pageSize: 50 }
  376. });
  377. IntegrationTestAssertions.expectStatus(response, 200);
  378. const data = await response.json();
  379. // 不存在的租户应该返回空数组
  380. expect(data.data.provinces).toHaveLength(0);
  381. });
  382. });
  383. });