admin-goods-routes.integration.test.ts 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  1. import { describe, it, expect, beforeEach } from 'vitest';
  2. import { testClient } from 'hono/testing';
  3. import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
  4. import { JWTUtil } from '@d8d/shared-utils';
  5. import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
  6. import { FileMt } from '@d8d/file-module-mt';
  7. import { SupplierMt } from '@d8d/supplier-module-mt';
  8. import { MerchantMt } from '@d8d/merchant-module-mt';
  9. import { adminGoodsRoutesMt } from '../../src/routes/index.mt';
  10. import { GoodsMt, GoodsCategoryMt } from '../../src/entities/index.mt';
  11. import { GoodsTestFactory } from '../factories/goods-test-factory';
  12. // 设置集成测试钩子
  13. setupIntegrationDatabaseHooksWithEntities([
  14. UserEntityMt, RoleMt, GoodsMt, GoodsCategoryMt, FileMt, SupplierMt, MerchantMt
  15. ])
  16. describe('管理员商品管理API集成测试', () => {
  17. let client: ReturnType<typeof testClient<typeof adminGoodsRoutesMt>>;
  18. let adminToken: string;
  19. let testUser: UserEntityMt;
  20. let testAdmin: UserEntityMt;
  21. let testCategory: GoodsCategoryMt;
  22. let testSupplier: SupplierMt;
  23. let testMerchant: MerchantMt;
  24. let testFactory: GoodsTestFactory;
  25. beforeEach(async () => {
  26. // 创建测试客户端
  27. client = testClient(adminGoodsRoutesMt);
  28. // 获取数据源并创建测试工厂
  29. const dataSource = await IntegrationTestDatabase.getDataSource();
  30. testFactory = new GoodsTestFactory(dataSource);
  31. // 使用测试工厂创建测试数据
  32. testUser = await testFactory.createTestUser();
  33. testAdmin = await testFactory.createTestAdmin();
  34. testCategory = await testFactory.createTestCategory(testUser.id);
  35. testSupplier = await testFactory.createTestSupplier(testUser.id);
  36. testMerchant = await testFactory.createTestMerchant(testUser.id);
  37. // 生成测试管理员的token
  38. adminToken = JWTUtil.generateToken({
  39. id: testAdmin.id,
  40. username: testAdmin.username,
  41. roles: [{name:'admin'}]
  42. });
  43. });
  44. describe('GET /goods', () => {
  45. it('应该返回所有商品列表', async () => {
  46. // 创建多个用户的商品
  47. const userGoods1 = await testFactory.createTestGoods(testUser.id, {
  48. name: '用户商品1',
  49. price: 100.00,
  50. costPrice: 80.00,
  51. categoryId1: testCategory.id,
  52. categoryId2: testCategory.id,
  53. categoryId3: testCategory.id,
  54. supplierId: testSupplier.id,
  55. merchantId: testMerchant.id,
  56. stock: 100
  57. });
  58. const userGoods2 = await testFactory.createTestGoods(testAdmin.id, {
  59. name: '用户商品2',
  60. price: 200.00,
  61. costPrice: 160.00,
  62. categoryId1: testCategory.id,
  63. categoryId2: testCategory.id,
  64. categoryId3: testCategory.id,
  65. supplierId: testSupplier.id,
  66. merchantId: testMerchant.id,
  67. stock: 50
  68. });
  69. const response = await client.index.$get({
  70. query: {}
  71. }, {
  72. headers: {
  73. 'Authorization': `Bearer ${adminToken}`
  74. }
  75. });
  76. console.debug('管理员商品列表响应状态:', response.status);
  77. expect(response.status).toBe(200);
  78. if (response.status === 200) {
  79. const data = await response.json();
  80. expect(data).toHaveProperty('data');
  81. expect(Array.isArray(data.data)).toBe(true);
  82. // 验证返回所有用户的商品(管理员可以访问所有数据)
  83. const userGoodsCount = data.data.filter((goods: any) => goods.createdBy === testUser.id).length;
  84. const adminGoodsCount = data.data.filter((goods: any) => goods.createdBy === testAdmin.id).length;
  85. expect(userGoodsCount).toBeGreaterThan(0);
  86. expect(adminGoodsCount).toBeGreaterThan(0);
  87. }
  88. });
  89. it('应该拒绝未认证用户的访问', async () => {
  90. const response = await client.index.$get({
  91. query: {}
  92. });
  93. expect(response.status).toBe(401);
  94. });
  95. });
  96. describe('POST /goods', () => {
  97. it('应该成功创建商品并可以指定权限', async () => {
  98. const createData = {
  99. name: '管理员创建商品',
  100. price: 150.00,
  101. costPrice: 120.00,
  102. categoryId1: testCategory.id,
  103. categoryId2: testCategory.id,
  104. categoryId3: testCategory.id,
  105. goodsType: 1,
  106. supplierId: testSupplier.id,
  107. merchantId: testMerchant.id,
  108. state: 1,
  109. stock: 80,
  110. lowestBuy: 1,
  111. createdBy: testUser.id // 管理员可以指定创建人
  112. };
  113. const response = await client.index.$post({
  114. json: createData
  115. }, {
  116. headers: {
  117. 'Authorization': `Bearer ${adminToken}`
  118. }
  119. });
  120. console.debug('管理员创建商品响应状态:', response.status);
  121. if (response.status !== 201) {
  122. const errorData = await response.json();
  123. console.debug('管理员创建商品错误响应:', errorData);
  124. }
  125. expect(response.status).toBe(201);
  126. if (response.status === 201) {
  127. const data = await response.json();
  128. expect(data).toHaveProperty('id');
  129. expect(data.name).toBe(createData.name);
  130. expect(data.price).toBe(Number(createData.price));
  131. expect(data.createdBy).toBe(testUser.id); // 验证可以指定创建人
  132. }
  133. });
  134. it('应该验证创建商品的必填字段', async () => {
  135. const invalidData = {
  136. // 缺少必填字段
  137. name: '',
  138. price: -1,
  139. categoryId1: -1
  140. };
  141. const response = await client.index.$post({
  142. json: invalidData
  143. }, {
  144. headers: {
  145. 'Authorization': `Bearer ${adminToken}`
  146. }
  147. });
  148. expect(response.status).toBe(400);
  149. });
  150. });
  151. describe('GET /goods/:id', () => {
  152. it('应该返回指定商品的详情', async () => {
  153. // 先创建一个商品
  154. const testGoods = await testFactory.createTestGoods(testUser.id, {
  155. name: '测试商品详情',
  156. price: 100.00,
  157. costPrice: 80.00,
  158. categoryId1: testCategory.id,
  159. categoryId2: testCategory.id,
  160. categoryId3: testCategory.id,
  161. supplierId: testSupplier.id,
  162. merchantId: testMerchant.id,
  163. stock: 100
  164. });
  165. const response = await client[':id'].$get({
  166. param: { id: testGoods.id }
  167. }, {
  168. headers: {
  169. 'Authorization': `Bearer ${adminToken}`
  170. }
  171. });
  172. console.debug('管理员商品详情响应状态:', response.status);
  173. expect(response.status).toBe(200);
  174. if (response.status === 200) {
  175. const data = await response.json();
  176. expect(data.id).toBe(testGoods.id);
  177. expect(data.name).toBe(testGoods.name);
  178. expect(data.createdBy).toBe(testUser.id);
  179. }
  180. });
  181. it('应该处理不存在的商品', async () => {
  182. const response = await client[':id'].$get({
  183. param: { id: 999999 }
  184. }, {
  185. headers: {
  186. 'Authorization': `Bearer ${adminToken}`
  187. }
  188. });
  189. expect(response.status).toBe(404);
  190. });
  191. });
  192. describe('PUT /goods/:id', () => {
  193. it('应该成功更新任何商品', async () => {
  194. // 先创建一个商品
  195. const testGoods = await testFactory.createTestGoods(testUser.id, {
  196. name: '测试更新商品',
  197. price: 100.00,
  198. costPrice: 80.00,
  199. categoryId1: testCategory.id,
  200. categoryId2: testCategory.id,
  201. categoryId3: testCategory.id,
  202. supplierId: testSupplier.id,
  203. merchantId: testMerchant.id,
  204. stock: 100
  205. });
  206. const updateData = {
  207. name: '管理员更新后的商品名称',
  208. price: 120.00,
  209. state: 2,
  210. updatedBy: testAdmin.id // 管理员可以指定更新人
  211. };
  212. const response = await client[':id'].$put({
  213. param: { id: testGoods.id },
  214. json: updateData
  215. }, {
  216. headers: {
  217. 'Authorization': `Bearer ${adminToken}`
  218. }
  219. });
  220. console.debug('管理员更新商品响应状态:', response.status);
  221. expect(response.status).toBe(200);
  222. if (response.status === 200) {
  223. const data = await response.json();
  224. expect(data.name).toBe(updateData.name);
  225. expect(data.price).toBe(Number(updateData.price));
  226. expect(data.state).toBe(updateData.state);
  227. expect(data.updatedBy).toBe(testAdmin.id); // 验证可以指定更新人
  228. }
  229. });
  230. });
  231. describe('DELETE /goods/:id', () => {
  232. it('应该成功删除任何商品', async () => {
  233. // 先创建一个商品
  234. const testGoods = await testFactory.createTestGoods(testUser.id, {
  235. name: '测试删除商品',
  236. price: 100.00,
  237. costPrice: 80.00,
  238. categoryId1: testCategory.id,
  239. categoryId2: testCategory.id,
  240. categoryId3: testCategory.id,
  241. supplierId: testSupplier.id,
  242. merchantId: testMerchant.id,
  243. stock: 100
  244. });
  245. const response = await client[':id'].$delete({
  246. param: { id: testGoods.id }
  247. }, {
  248. headers: {
  249. 'Authorization': `Bearer ${adminToken}`
  250. }
  251. });
  252. console.debug('管理员删除商品响应状态:', response.status);
  253. expect(response.status).toBe(204);
  254. });
  255. });
  256. describe('商品状态管理测试', () => {
  257. it('应该正确处理商品状态变更', async () => {
  258. // 创建可用状态的商品
  259. const activeGoods = await testFactory.createTestGoods(testUser.id, {
  260. name: '可用商品',
  261. price: 100.00,
  262. costPrice: 80.00,
  263. categoryId1: testCategory.id,
  264. categoryId2: testCategory.id,
  265. categoryId3: testCategory.id,
  266. supplierId: testSupplier.id,
  267. merchantId: testMerchant.id,
  268. state: 1,
  269. stock: 100
  270. });
  271. // 创建不可用状态的商品
  272. const inactiveGoods = await testFactory.createTestGoods(testUser.id, {
  273. name: '不可用商品',
  274. price: 200.00,
  275. costPrice: 160.00,
  276. categoryId1: testCategory.id,
  277. categoryId2: testCategory.id,
  278. categoryId3: testCategory.id,
  279. supplierId: testSupplier.id,
  280. merchantId: testMerchant.id,
  281. state: 2,
  282. stock: 50
  283. });
  284. // 验证状态过滤
  285. const response = await client.index.$get({
  286. query: { filters: JSON.stringify({ state: 1 }) }
  287. }, {
  288. headers: {
  289. 'Authorization': `Bearer ${adminToken}`
  290. }
  291. });
  292. expect(response.status).toBe(200);
  293. const data = await response.json();
  294. // 类型检查确保data属性存在
  295. if ('data' in data && Array.isArray(data.data)) {
  296. // 应该只返回可用状态的商品
  297. const activeGoodsInResponse = data.data.filter((goods: any) => goods.state === 1);
  298. const inactiveGoodsInResponse = data.data.filter((goods: any) => goods.state === 2);
  299. expect(activeGoodsInResponse.length).toBeGreaterThan(0);
  300. expect(inactiveGoodsInResponse.length).toBe(0);
  301. } else {
  302. // 如果响应是错误格式,应该失败
  303. expect(data).toHaveProperty('data');
  304. }
  305. });
  306. });
  307. describe('商品库存管理测试', () => {
  308. it('应该正确处理商品库存更新', async () => {
  309. // 创建一个商品
  310. const testGoods = await testFactory.createTestGoods(testUser.id, {
  311. name: '库存测试商品',
  312. price: 100.00,
  313. costPrice: 80.00,
  314. categoryId1: testCategory.id,
  315. categoryId2: testCategory.id,
  316. categoryId3: testCategory.id,
  317. supplierId: testSupplier.id,
  318. merchantId: testMerchant.id,
  319. stock: 100
  320. });
  321. // 更新库存
  322. const updateData = {
  323. stock: 50
  324. };
  325. const response = await client[':id'].$put({
  326. param: { id: testGoods.id },
  327. json: updateData
  328. }, {
  329. headers: {
  330. 'Authorization': `Bearer ${adminToken}`
  331. }
  332. });
  333. expect(response.status).toBe(200);
  334. if (response.status === 200) {
  335. const data = await response.json();
  336. expect(data.stock).toBe(Number(updateData.stock));
  337. }
  338. });
  339. });
  340. describe('管理员权限验证测试', () => {
  341. it('应该验证管理员可以访问所有数据', async () => {
  342. // 创建多个用户的商品
  343. const userGoods = await testFactory.createTestGoods(testUser.id, {
  344. name: '用户商品',
  345. price: 100.00,
  346. costPrice: 80.00,
  347. categoryId1: testCategory.id,
  348. categoryId2: testCategory.id,
  349. categoryId3: testCategory.id,
  350. supplierId: testSupplier.id,
  351. merchantId: testMerchant.id,
  352. stock: 100
  353. });
  354. const adminGoods = await testFactory.createTestGoods(testAdmin.id, {
  355. name: '管理员商品',
  356. price: 200.00,
  357. costPrice: 160.00,
  358. categoryId1: testCategory.id,
  359. categoryId2: testCategory.id,
  360. categoryId3: testCategory.id,
  361. supplierId: testSupplier.id,
  362. merchantId: testMerchant.id,
  363. stock: 50
  364. });
  365. // 使用管理员token获取列表
  366. const response = await client.index.$get({
  367. query: {}
  368. }, {
  369. headers: {
  370. 'Authorization': `Bearer ${adminToken}`
  371. }
  372. });
  373. expect(response.status).toBe(200);
  374. const data = await response.json();
  375. // 类型检查确保data属性存在
  376. if ('data' in data && Array.isArray(data.data)) {
  377. // 验证返回所有用户的商品
  378. const userGoodsInResponse = data.data.filter((goods: any) => goods.createdBy === testUser.id);
  379. const adminGoodsInResponse = data.data.filter((goods: any) => goods.createdBy === testAdmin.id);
  380. expect(userGoodsInResponse.length).toBeGreaterThan(0);
  381. expect(adminGoodsInResponse.length).toBeGreaterThan(0);
  382. } else {
  383. // 如果响应是错误格式,应该失败
  384. expect(data).toHaveProperty('data');
  385. }
  386. });
  387. });
  388. describe('父子商品配置功能测试 (故事006.001)', () => {
  389. it('应该成功创建父商品 (spuId=0)', async () => {
  390. const createData = {
  391. name: '父商品测试',
  392. price: 200.00,
  393. costPrice: 150.00,
  394. categoryId1: testCategory.id,
  395. categoryId2: testCategory.id,
  396. categoryId3: testCategory.id,
  397. goodsType: 1,
  398. supplierId: testSupplier.id,
  399. merchantId: testMerchant.id,
  400. state: 1,
  401. stock: 100,
  402. lowestBuy: 1,
  403. spuId: 0, // 父商品spuId=0
  404. spuName: null // 父商品spuName为null
  405. };
  406. const response = await client.index.$post({
  407. json: createData
  408. }, {
  409. headers: {
  410. 'Authorization': `Bearer ${adminToken}`
  411. }
  412. });
  413. console.debug('创建父商品响应状态:', response.status);
  414. expect(response.status).toBe(201);
  415. if (response.status === 201) {
  416. const data = await response.json();
  417. expect(data).toHaveProperty('id');
  418. expect(data.name).toBe(createData.name);
  419. expect(data.spuId).toBe(0); // 验证spuId=0
  420. expect(data.spuName).toBeUndefined(); // 验证spuName不再返回(已从API响应中移除)
  421. }
  422. });
  423. it('应该成功创建子商品并关联父商品', async () => {
  424. // 先创建父商品
  425. const parentGoods = await testFactory.createTestGoods(testUser.id, {
  426. name: '父商品-用于子商品测试',
  427. price: 300.00,
  428. spuId: 0,
  429. spuName: null
  430. });
  431. const createData = {
  432. name: '子商品测试',
  433. price: 150.00,
  434. costPrice: 120.00,
  435. categoryId1: testCategory.id,
  436. categoryId2: testCategory.id,
  437. categoryId3: testCategory.id,
  438. goodsType: 1,
  439. supplierId: testSupplier.id,
  440. merchantId: testMerchant.id,
  441. state: 1,
  442. stock: 50,
  443. lowestBuy: 1,
  444. spuId: parentGoods.id, // 子商品spuId=父商品ID
  445. spuName: parentGoods.name // 子商品spuName=父商品名称
  446. };
  447. const response = await client.index.$post({
  448. json: createData
  449. }, {
  450. headers: {
  451. 'Authorization': `Bearer ${adminToken}`
  452. }
  453. });
  454. console.debug('创建子商品响应状态:', response.status);
  455. expect(response.status).toBe(201);
  456. if (response.status === 201) {
  457. const data = await response.json();
  458. expect(data).toHaveProperty('id');
  459. expect(data.name).toBe(createData.name);
  460. expect(data.spuId).toBe(parentGoods.id); // 验证spuId=父商品ID
  461. // spuName字段已从API响应中移除,改为通过parent对象获取父商品名称
  462. // expect(data.spuName).toBe(parentGoods.name); // 验证spuName=父商品名称
  463. }
  464. });
  465. it('应该成功更新商品的父子关系', async () => {
  466. // 创建父商品
  467. const parentGoods = await testFactory.createTestGoods(testUser.id, {
  468. name: '父商品-用于更新测试',
  469. price: 400.00,
  470. spuId: 0,
  471. spuName: null
  472. });
  473. // 创建子商品
  474. const childGoods = await testFactory.createTestGoods(testUser.id, {
  475. name: '子商品-用于更新测试',
  476. price: 200.00,
  477. spuId: parentGoods.id,
  478. spuName: parentGoods.name
  479. });
  480. // 更新子商品信息
  481. const updateData = {
  482. name: '更新后的子商品名称',
  483. price: 250.00,
  484. spuId: parentGoods.id, // 保持父子关系
  485. spuName: '更新后的父商品名称' // 更新spuName
  486. };
  487. const response = await client[':id'].$put({
  488. param: { id: childGoods.id },
  489. json: updateData
  490. }, {
  491. headers: {
  492. 'Authorization': `Bearer ${adminToken}`
  493. }
  494. });
  495. console.debug('更新子商品响应状态:', response.status);
  496. expect(response.status).toBe(200);
  497. if (response.status === 200) {
  498. const data = await response.json();
  499. expect(data.name).toBe(updateData.name);
  500. expect(data.price).toBe(Number(updateData.price));
  501. expect(data.spuId).toBe(parentGoods.id); // 验证父子关系保持
  502. // spuName字段已从API响应中移除,改为通过parent对象获取父商品名称
  503. // expect(data.spuName).toBe(updateData.spuName); // 验证spuName更新
  504. }
  505. });
  506. it('应该成功获取商品详情并包含父子关系字段', async () => {
  507. // 创建父商品
  508. const parentGoods = await testFactory.createTestGoods(testUser.id, {
  509. name: '父商品-用于详情测试',
  510. price: 500.00,
  511. spuId: 0,
  512. spuName: null
  513. });
  514. // 获取父商品详情
  515. const response = await client[':id'].$get({
  516. param: { id: parentGoods.id }
  517. }, {
  518. headers: {
  519. 'Authorization': `Bearer ${adminToken}`
  520. }
  521. });
  522. console.debug('获取父商品详情响应状态:', response.status);
  523. expect(response.status).toBe(200);
  524. if (response.status === 200) {
  525. const data = await response.json();
  526. expect(data.id).toBe(parentGoods.id);
  527. expect(data.name).toBe(parentGoods.name);
  528. expect(data.spuId).toBe(0); // 验证父商品spuId=0
  529. expect(data.spuName).toBeUndefined(); // 验证父商品spuName不再返回(已从API响应中移除)
  530. }
  531. });
  532. it('应该验证父子商品关系约束', async () => {
  533. // 测试1: spuId必须是非负数 - Schema验证
  534. const invalidData1 = {
  535. name: '测试商品-无效spuId',
  536. price: 100.00,
  537. costPrice: 80.00,
  538. categoryId1: testCategory.id,
  539. categoryId2: testCategory.id,
  540. categoryId3: testCategory.id,
  541. goodsType: 1,
  542. supplierId: testSupplier.id,
  543. merchantId: testMerchant.id,
  544. state: 1,
  545. stock: 100,
  546. lowestBuy: 1,
  547. spuId: -1 // 无效的spuId,Schema应该验证失败
  548. };
  549. const response1 = await client.index.$post({
  550. json: invalidData1
  551. }, {
  552. headers: {
  553. 'Authorization': `Bearer ${adminToken}`
  554. }
  555. });
  556. console.debug('无效spuId测试响应状态:', response1.status);
  557. expect(response1.status).toBe(400);
  558. // 测试2: 创建商品时spuId不能指向不存在的商品
  559. const invalidData2 = {
  560. name: '测试商品-无效父商品',
  561. price: 100.00,
  562. costPrice: 80.00,
  563. categoryId1: testCategory.id,
  564. categoryId2: testCategory.id,
  565. categoryId3: testCategory.id,
  566. goodsType: 1,
  567. supplierId: testSupplier.id,
  568. merchantId: testMerchant.id,
  569. state: 1,
  570. stock: 100,
  571. lowestBuy: 1,
  572. spuId: 999999 // 不存在的商品ID
  573. };
  574. const response2 = await client.index.$post({
  575. json: invalidData2
  576. }, {
  577. headers: {
  578. 'Authorization': `Bearer ${adminToken}`
  579. }
  580. });
  581. console.debug('无效父商品测试响应状态:', response2.status);
  582. // 可能返回201(创建成功)或400(验证失败),取决于业务逻辑
  583. // 这里我们只记录状态,不进行断言
  584. console.debug('创建指向不存在父商品的商品状态:', response2.status);
  585. });
  586. it('应该支持批量创建父子商品', async () => {
  587. // 创建父商品
  588. const parentGoods = await testFactory.createTestGoods(testUser.id, {
  589. name: '父商品-批量测试',
  590. price: 600.00,
  591. spuId: 0,
  592. spuName: null
  593. });
  594. // 批量创建3个子商品
  595. const childGoodsData = [
  596. {
  597. name: '子商品-规格1',
  598. price: 300.00,
  599. costPrice: 240.00,
  600. categoryId1: testCategory.id,
  601. categoryId2: testCategory.id,
  602. categoryId3: testCategory.id,
  603. goodsType: 1,
  604. supplierId: testSupplier.id,
  605. merchantId: testMerchant.id,
  606. state: 1,
  607. stock: 30,
  608. lowestBuy: 1,
  609. spuId: parentGoods.id,
  610. spuName: parentGoods.name
  611. },
  612. {
  613. name: '子商品-规格2',
  614. price: 320.00,
  615. costPrice: 260.00,
  616. categoryId1: testCategory.id,
  617. categoryId2: testCategory.id,
  618. categoryId3: testCategory.id,
  619. goodsType: 1,
  620. supplierId: testSupplier.id,
  621. merchantId: testMerchant.id,
  622. state: 1,
  623. stock: 40,
  624. lowestBuy: 1,
  625. spuId: parentGoods.id,
  626. spuName: parentGoods.name
  627. },
  628. {
  629. name: '子商品-规格3',
  630. price: 350.00,
  631. costPrice: 280.00,
  632. categoryId1: testCategory.id,
  633. categoryId2: testCategory.id,
  634. categoryId3: testCategory.id,
  635. goodsType: 1,
  636. supplierId: testSupplier.id,
  637. merchantId: testMerchant.id,
  638. state: 1,
  639. stock: 50,
  640. lowestBuy: 1,
  641. spuId: parentGoods.id,
  642. spuName: parentGoods.name
  643. }
  644. ];
  645. // 逐个创建子商品
  646. const createdChildIds = [];
  647. for (const childData of childGoodsData) {
  648. const response = await client.index.$post({
  649. json: childData
  650. }, {
  651. headers: {
  652. 'Authorization': `Bearer ${adminToken}`
  653. }
  654. });
  655. expect(response.status).toBe(201);
  656. const data = await response.json();
  657. createdChildIds.push(data.id);
  658. // 验证每个子商品都正确关联到父商品
  659. expect(data.spuId).toBe(parentGoods.id);
  660. // spuName字段已从API响应中移除,改为通过parent对象获取父商品名称
  661. // expect(data.spuName).toBe(parentGoods.name);
  662. }
  663. console.debug(`批量创建了 ${createdChildIds.length} 个子商品`);
  664. expect(createdChildIds).toHaveLength(3);
  665. });
  666. it('应该验证子商品继承父商品的分类信息', async () => {
  667. // 创建父商品,设置特定的分类信息
  668. const parentGoods = await testFactory.createTestGoods(testUser.id, {
  669. name: '父商品-分类继承测试',
  670. price: 700.00,
  671. spuId: 0,
  672. spuName: null,
  673. categoryId1: testCategory.id,
  674. categoryId2: testCategory.id,
  675. categoryId3: testCategory.id,
  676. goodsType: 2, // 虚拟产品
  677. supplierId: testSupplier.id,
  678. merchantId: testMerchant.id
  679. });
  680. // 创建子商品,从父商品继承分类信息
  681. const createData = {
  682. name: '子商品-分类继承测试',
  683. price: 350.00,
  684. costPrice: 280.00,
  685. // 从父商品继承分类ID
  686. categoryId1: parentGoods.categoryId1,
  687. categoryId2: parentGoods.categoryId2,
  688. categoryId3: parentGoods.categoryId3,
  689. goodsType: 1, // 可以覆盖父商品的商品类型
  690. supplierId: null, // 可以设置为null
  691. merchantId: null, // 可以设置为null
  692. state: 1,
  693. stock: 60,
  694. lowestBuy: 1,
  695. spuId: parentGoods.id,
  696. spuName: parentGoods.name
  697. };
  698. const response = await client.index.$post({
  699. json: createData
  700. }, {
  701. headers: {
  702. 'Authorization': `Bearer ${adminToken}`
  703. }
  704. });
  705. console.debug('分类继承测试响应状态:', response.status);
  706. if (response.status !== 201) {
  707. const errorData = await response.json();
  708. console.debug('分类继承测试错误响应:', errorData);
  709. }
  710. expect(response.status).toBe(201);
  711. if (response.status === 201) {
  712. const data = await response.json();
  713. expect(data.name).toBe(createData.name);
  714. expect(data.spuId).toBe(parentGoods.id);
  715. // spuName字段已从API响应中移除,改为通过parent对象获取父商品名称
  716. // expect(data.spuName).toBe(parentGoods.name);
  717. // 验证子商品使用了父商品的分类信息
  718. expect(data.categoryId1).toBe(parentGoods.categoryId1);
  719. expect(data.categoryId2).toBe(parentGoods.categoryId2);
  720. expect(data.categoryId3).toBe(parentGoods.categoryId3);
  721. // 验证商品类型可以被覆盖
  722. expect(data.goodsType).toBe(createData.goodsType); // 子商品使用自己的商品类型
  723. expect(data.supplierId).toBeNull(); // 子商品可以设置为null
  724. expect(data.merchantId).toBeNull(); // 子商品可以设置为null
  725. }
  726. });
  727. it('应该验证创建子商品时必须提供有效的分类ID', async () => {
  728. // 创建父商品
  729. const parentGoods = await testFactory.createTestGoods(testUser.id, {
  730. name: '父商品-分类验证测试',
  731. price: 800.00,
  732. spuId: 0,
  733. spuName: null,
  734. categoryId1: testCategory.id, // 父商品有有效的分类ID
  735. categoryId2: testCategory.id,
  736. categoryId3: testCategory.id
  737. });
  738. // 测试1: 创建子商品时不指定分类ID(应该使用默认值0,但会导致外键约束错误)
  739. const invalidData1 = {
  740. name: '子商品-无效分类测试',
  741. price: 400.00,
  742. costPrice: 320.00,
  743. // 不指定categoryId1/2/3,默认值为0
  744. goodsType: 1,
  745. supplierId: testSupplier.id,
  746. merchantId: testMerchant.id,
  747. state: 1,
  748. stock: 70,
  749. lowestBuy: 1,
  750. spuId: parentGoods.id,
  751. spuName: parentGoods.name
  752. };
  753. const response1 = await client.index.$post({
  754. json: invalidData1
  755. }, {
  756. headers: {
  757. 'Authorization': `Bearer ${adminToken}`
  758. }
  759. });
  760. console.debug('无效分类测试响应状态:', response1.status);
  761. // 由于外键约束,可能会返回500错误
  762. // 这里我们只记录状态,不进行断言,因为行为取决于业务逻辑
  763. console.debug('创建子商品不指定分类ID的状态:', response1.status);
  764. // 测试2: 创建子商品时指定有效的分类ID(应该成功)
  765. const validData = {
  766. name: '子商品-有效分类测试',
  767. price: 420.00,
  768. costPrice: 340.00,
  769. categoryId1: testCategory.id,
  770. categoryId2: testCategory.id,
  771. categoryId3: testCategory.id,
  772. goodsType: 1,
  773. supplierId: testSupplier.id,
  774. merchantId: testMerchant.id,
  775. state: 1,
  776. stock: 80,
  777. lowestBuy: 1,
  778. spuId: parentGoods.id,
  779. spuName: parentGoods.name
  780. };
  781. const response2 = await client.index.$post({
  782. json: validData
  783. }, {
  784. headers: {
  785. 'Authorization': `Bearer ${adminToken}`
  786. }
  787. });
  788. console.debug('有效分类测试响应状态:', response2.status);
  789. expect(response2.status).toBe(201);
  790. if (response2.status === 201) {
  791. const data = await response2.json();
  792. expect(data.categoryId1).toBe(testCategory.id);
  793. expect(data.categoryId2).toBe(testCategory.id);
  794. expect(data.categoryId3).toBe(testCategory.id);
  795. }
  796. });
  797. });
  798. describe('spuId过滤功能测试 (故事006.004)', () => {
  799. let parentGoods1: GoodsMt;
  800. let parentGoods2: GoodsMt;
  801. let childGoods1: GoodsMt;
  802. let childGoods2: GoodsMt;
  803. beforeEach(async () => {
  804. // 创建测试数据:2个父商品,每个父商品有1个子商品
  805. parentGoods1 = await testFactory.createTestGoods(testUser.id, {
  806. name: '父商品1',
  807. price: 100.00,
  808. costPrice: 80.00,
  809. categoryId1: testCategory.id,
  810. categoryId2: testCategory.id,
  811. categoryId3: testCategory.id,
  812. supplierId: testSupplier.id,
  813. merchantId: testMerchant.id,
  814. state: 1,
  815. spuId: 0,
  816. spuName: null
  817. });
  818. parentGoods2 = await testFactory.createTestGoods(testUser.id, {
  819. name: '父商品2',
  820. price: 200.00,
  821. costPrice: 160.00,
  822. categoryId1: testCategory.id,
  823. categoryId2: testCategory.id,
  824. categoryId3: testCategory.id,
  825. supplierId: testSupplier.id,
  826. merchantId: testMerchant.id,
  827. state: 1,
  828. spuId: 0,
  829. spuName: null
  830. });
  831. childGoods1 = await testFactory.createTestGoods(testUser.id, {
  832. name: '子商品1 - 红色',
  833. price: 110.00,
  834. costPrice: 85.00,
  835. categoryId1: testCategory.id,
  836. categoryId2: testCategory.id,
  837. categoryId3: testCategory.id,
  838. supplierId: testSupplier.id,
  839. merchantId: testMerchant.id,
  840. state: 1,
  841. spuId: parentGoods1.id,
  842. spuName: parentGoods1.name
  843. });
  844. childGoods2 = await testFactory.createTestGoods(testUser.id, {
  845. name: '子商品2 - 蓝色',
  846. price: 220.00,
  847. costPrice: 165.00,
  848. categoryId1: testCategory.id,
  849. categoryId2: testCategory.id,
  850. categoryId3: testCategory.id,
  851. supplierId: testSupplier.id,
  852. merchantId: testMerchant.id,
  853. state: 1,
  854. spuId: parentGoods2.id,
  855. spuName: parentGoods2.name
  856. });
  857. });
  858. it('应该支持通过filters参数过滤只显示父商品 (spuId=0)', async () => {
  859. const response = await client.index.$get({
  860. query: {
  861. page: 1,
  862. pageSize: 10,
  863. filters: JSON.stringify({ spuId: 0 })
  864. }
  865. }, {
  866. headers: {
  867. 'Authorization': `Bearer ${adminToken}`
  868. }
  869. });
  870. expect(response.status).toBe(200);
  871. const data = await response.json();
  872. // 应该只返回父商品
  873. expect(data.pagination.total).toBe(2);
  874. expect(data.data).toHaveLength(2);
  875. // 验证返回的是父商品
  876. const returnedIds = data.data.map((item: any) => item.id);
  877. expect(returnedIds).toContain(parentGoods1.id);
  878. expect(returnedIds).toContain(parentGoods2.id);
  879. expect(returnedIds).not.toContain(childGoods1.id);
  880. expect(returnedIds).not.toContain(childGoods2.id);
  881. // 验证所有返回商品的spuId为0
  882. data.data.forEach((item: any) => {
  883. expect(item.spuId).toBe(0);
  884. });
  885. });
  886. it('应该支持通过filters参数过滤显示指定父商品的子商品 (spuId>0)', async () => {
  887. const response = await client.index.$get({
  888. query: {
  889. page: 1,
  890. pageSize: 10,
  891. filters: JSON.stringify({ spuId: parentGoods1.id })
  892. }
  893. }, {
  894. headers: {
  895. 'Authorization': `Bearer ${adminToken}`
  896. }
  897. });
  898. expect(response.status).toBe(200);
  899. const data = await response.json();
  900. // 应该只返回parentGoods1的子商品
  901. expect(data.pagination.total).toBe(1);
  902. expect(data.data).toHaveLength(1);
  903. // 验证返回的是childGoods1
  904. expect(data.data[0].id).toBe(childGoods1.id);
  905. expect(data.data[0].spuId).toBe(parentGoods1.id);
  906. // spuName字段已从API响应中移除,改为通过parent对象获取父商品名称
  907. // expect(data.data[0].spuName).toBe(parentGoods1.name);
  908. });
  909. it('应该支持通过filters参数组合过滤', async () => {
  910. const response = await client.index.$get({
  911. query: {
  912. page: 1,
  913. pageSize: 10,
  914. filters: JSON.stringify({
  915. spuId: 0,
  916. state: 1
  917. })
  918. }
  919. }, {
  920. headers: {
  921. 'Authorization': `Bearer ${adminToken}`
  922. }
  923. });
  924. expect(response.status).toBe(200);
  925. const data = await response.json();
  926. // 应该只返回可用状态的父商品
  927. expect(data.pagination.total).toBe(2);
  928. data.data.forEach((item: any) => {
  929. expect(item.spuId).toBe(0);
  930. expect(item.state).toBe(1);
  931. });
  932. });
  933. it('管理员商品列表应该默认显示所有商品(无spuId过滤)', async () => {
  934. const response = await client.index.$get({
  935. query: {
  936. page: 1,
  937. pageSize: 10
  938. }
  939. }, {
  940. headers: {
  941. 'Authorization': `Bearer ${adminToken}`
  942. }
  943. });
  944. expect(response.status).toBe(200);
  945. const data = await response.json();
  946. // 应该返回所有4个商品
  947. expect(data.pagination.total).toBe(4);
  948. expect(data.data).toHaveLength(4);
  949. // 验证包含所有商品
  950. const returnedIds = data.data.map((item: any) => item.id);
  951. expect(returnedIds).toContain(parentGoods1.id);
  952. expect(returnedIds).toContain(parentGoods2.id);
  953. expect(returnedIds).toContain(childGoods1.id);
  954. expect(returnedIds).toContain(childGoods2.id);
  955. });
  956. });
  957. });