areas.integration.test.ts 17 KB

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