feie-api.integration.test.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  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 { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
  5. import { FileMt } from '@d8d/file-module-mt';
  6. import { FeieMtRoutes } from '../../src/routes';
  7. import { FeiePrinterMt, FeiePrintTaskMt, FeieConfigMt } from '../../src/entities';
  8. import { FeieTestDataFactory } from '../utils/test-data-factory';
  9. // 设置集成测试钩子
  10. setupIntegrationDatabaseHooksWithEntities([
  11. UserEntityMt, RoleMt, FileMt, FeiePrinterMt, FeiePrintTaskMt, FeieConfigMt
  12. ])
  13. describe('飞鹅打印多租户API集成测试', () => {
  14. let client: ReturnType<typeof testClient<typeof FeieMtRoutes>>;
  15. let userToken: string;
  16. let adminToken: string;
  17. let otherUserToken: string;
  18. let otherTenantUserToken: string;
  19. let testUser: UserEntityMt;
  20. let otherUser: UserEntityMt;
  21. let otherTenantUser: UserEntityMt;
  22. beforeEach(async () => {
  23. // 获取数据源
  24. const dataSource = await IntegrationTestDatabase.getDataSource();
  25. // 创建测试客户端
  26. client = testClient(FeieMtRoutes);
  27. // 创建测试用户
  28. testUser = await FeieTestDataFactory.createTestUser(dataSource, 1);
  29. otherUser = await FeieTestDataFactory.createTestUser(dataSource, 1);
  30. otherTenantUser = await FeieTestDataFactory.createTestUser(dataSource, 2);
  31. // 生成JWT令牌
  32. userToken = FeieTestDataFactory.generateUserToken(testUser);
  33. adminToken = FeieTestDataFactory.generateAdminToken(1);
  34. otherUserToken = FeieTestDataFactory.generateUserToken(otherUser);
  35. otherTenantUserToken = FeieTestDataFactory.generateUserToken(otherTenantUser);
  36. // 创建飞鹅API配置
  37. await FeieTestDataFactory.createFullFeieConfig(dataSource, 1);
  38. await FeieTestDataFactory.createFullFeieConfig(dataSource, 2);
  39. });
  40. describe('租户数据隔离验证', () => {
  41. it('应该只能访问自己租户的打印机', async () => {
  42. const dataSource = await IntegrationTestDatabase.getDataSource();
  43. // 创建租户1的打印机
  44. const tenant1Printer = await FeieTestDataFactory.createTestPrinter(dataSource, 1);
  45. // 创建租户2的打印机
  46. const tenant2Printer = await FeieTestDataFactory.createTestPrinter(dataSource, 2);
  47. // 使用租户1的用户查询打印机列表
  48. const response = await client.printers.$get({
  49. query: {}
  50. }, {
  51. headers: {
  52. 'Authorization': `Bearer ${userToken}`
  53. }
  54. });
  55. expect(response.status).toBe(200);
  56. if (response.status === 200) {
  57. const data = await response.json();
  58. // 应该只返回租户1的打印机
  59. expect(data.success).toBe(true);
  60. expect(data.data.data).toHaveLength(1);
  61. expect(data.data.data[0].tenantId).toBe(1);
  62. expect(data.data.data[0].printerSn).toBe(tenant1Printer.printerSn);
  63. }
  64. });
  65. it('不应该访问其他租户的打印机详情', async () => {
  66. const dataSource = await IntegrationTestDatabase.getDataSource();
  67. // 创建租户2的打印机
  68. const otherTenantPrinter = await FeieTestDataFactory.createTestPrinter(dataSource, 2);
  69. // 使用租户1的用户尝试访问租户2的打印机
  70. const response = await client['printers/:printerSn'].$get({
  71. param: { printerSn: otherTenantPrinter.printerSn }
  72. }, {
  73. headers: {
  74. 'Authorization': `Bearer ${userToken}`
  75. }
  76. });
  77. // 应该返回404,因为打印机不在当前租户
  78. expect(response.status).toBe(404);
  79. });
  80. it('应该正确过滤跨租户打印机访问', async () => {
  81. const dataSource = await IntegrationTestDatabase.getDataSource();
  82. // 创建租户1的打印机
  83. const tenant1Printer = await FeieTestDataFactory.createTestPrinter(dataSource, 1);
  84. // 使用租户2的用户尝试访问租户1的打印机
  85. const response = await client['printers/:printerSn'].$get({
  86. param: { printerSn: tenant1Printer.printerSn }
  87. }, {
  88. headers: {
  89. 'Authorization': `Bearer ${otherTenantUserToken}`
  90. }
  91. });
  92. // 应该返回404,因为打印机不在当前租户
  93. expect(response.status).toBe(404);
  94. });
  95. });
  96. describe('打印机管理功能验证', () => {
  97. it('应该能够查询打印机列表', async () => {
  98. const dataSource = await IntegrationTestDatabase.getDataSource();
  99. // 创建测试打印机
  100. const printer1 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '打印机1' });
  101. const printer2 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '打印机2' });
  102. // 查询打印机列表
  103. const response = await client.printers.$get({
  104. query: {}
  105. }, {
  106. headers: {
  107. 'Authorization': `Bearer ${userToken}`
  108. }
  109. });
  110. expect(response.status).toBe(200);
  111. if (response.status === 200) {
  112. const data = await response.json();
  113. expect(data.success).toBe(true);
  114. expect(data.data.data).toHaveLength(2);
  115. expect(data.data.total).toBe(2);
  116. }
  117. });
  118. it('应该能够根据名称搜索打印机', async () => {
  119. const dataSource = await IntegrationTestDatabase.getDataSource();
  120. // 创建测试打印机
  121. const printer1 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '测试打印机A' });
  122. const printer2 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '其他打印机' });
  123. // 搜索打印机
  124. const response = await client.printers.$get({
  125. query: { search: '测试' }
  126. }, {
  127. headers: {
  128. 'Authorization': `Bearer ${userToken}`
  129. }
  130. });
  131. expect(response.status).toBe(200);
  132. if (response.status === 200) {
  133. const data = await response.json();
  134. expect(data.success).toBe(true);
  135. expect(data.data.data).toHaveLength(1);
  136. expect(data.data.data[0].printerName).toBe('测试打印机A');
  137. }
  138. });
  139. it('应该能够设置默认打印机', async () => {
  140. const dataSource = await IntegrationTestDatabase.getDataSource();
  141. // 创建两个打印机
  142. const printer1 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '打印机1', isDefault: 1 });
  143. const printer2 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '打印机2', isDefault: 0 });
  144. // 设置打印机2为默认
  145. const response = await client['printers/:printerSn/set-default'].$post({
  146. param: { printerSn: printer2.printerSn }
  147. }, {
  148. headers: {
  149. 'Authorization': `Bearer ${userToken}`
  150. }
  151. });
  152. expect(response.status).toBe(200);
  153. if (response.status === 200) {
  154. const data = await response.json();
  155. expect(data.success).toBe(true);
  156. expect(data.data.isDefault).toBe(1);
  157. }
  158. // 验证打印机1不再是默认
  159. const updatedPrinter1 = await dataSource.getRepository(FeiePrinterMt).findOne({
  160. where: { tenantId: 1, printerSn: printer1.printerSn }
  161. });
  162. expect(updatedPrinter1?.isDefault).toBe(0);
  163. });
  164. });
  165. describe('打印任务管理功能验证', () => {
  166. let testPrinter: FeiePrinterMt;
  167. beforeEach(async () => {
  168. const dataSource = await IntegrationTestDatabase.getDataSource();
  169. // 创建测试打印机
  170. testPrinter = await FeieTestDataFactory.createTestPrinter(dataSource, 1);
  171. });
  172. it.skip('应该能够创建打印任务 - 需要实际飞鹅API连接', async () => {
  173. const taskData = {
  174. printerSn: testPrinter.printerSn,
  175. content: '<CB>测试打印内容</CB><BR>',
  176. printType: 'RECEIPT',
  177. delaySeconds: 0
  178. };
  179. const response = await client.tasks.$post({
  180. json: taskData
  181. }, {
  182. headers: {
  183. 'Authorization': `Bearer ${userToken}`
  184. }
  185. });
  186. console.debug('创建打印任务响应状态码:', response.status);
  187. if (response.status !== 200) {
  188. const errorResult = await response.json();
  189. console.debug('创建打印任务错误响应:', errorResult);
  190. }
  191. expect(response.status).toBe(200);
  192. if (response.status === 200) {
  193. const data = await response.json();
  194. expect(data.success).toBe(true);
  195. expect(data.data.taskId).toBeDefined();
  196. expect(data.data.printerSn).toBe(testPrinter.printerSn);
  197. expect(data.data.printType).toBe('RECEIPT');
  198. }
  199. });
  200. it('应该能够查询打印任务列表', async () => {
  201. const dataSource = await IntegrationTestDatabase.getDataSource();
  202. // 创建测试打印任务
  203. const task1 = await FeieTestDataFactory.createTestPrintTask(dataSource, 1, testPrinter.printerSn);
  204. const task2 = await FeieTestDataFactory.createTestPrintTask(dataSource, 1, testPrinter.printerSn);
  205. // 查询打印任务列表
  206. const response = await client.tasks.$get({
  207. query: {}
  208. }, {
  209. headers: {
  210. 'Authorization': `Bearer ${userToken}`
  211. }
  212. });
  213. expect(response.status).toBe(200);
  214. if (response.status === 200) {
  215. const data = await response.json();
  216. expect(data.success).toBe(true);
  217. expect(data.data.data).toHaveLength(2);
  218. expect(data.data.total).toBe(2);
  219. }
  220. });
  221. it('应该能够根据打印机筛选打印任务', async () => {
  222. const dataSource = await IntegrationTestDatabase.getDataSource();
  223. // 创建另一个打印机
  224. const otherPrinter = await FeieTestDataFactory.createTestPrinter(dataSource, 1);
  225. // 创建测试打印任务
  226. const task1 = await FeieTestDataFactory.createTestPrintTask(dataSource, 1, testPrinter.printerSn);
  227. const task2 = await FeieTestDataFactory.createTestPrintTask(dataSource, 1, otherPrinter.printerSn);
  228. // 根据打印机筛选
  229. const response = await client.tasks.$get({
  230. query: { printerSn: testPrinter.printerSn }
  231. }, {
  232. headers: {
  233. 'Authorization': `Bearer ${userToken}`
  234. }
  235. });
  236. expect(response.status).toBe(200);
  237. if (response.status === 200) {
  238. const data = await response.json();
  239. expect(data.success).toBe(true);
  240. expect(data.data.data).toHaveLength(1);
  241. expect(data.data.data[0].printerSn).toBe(testPrinter.printerSn);
  242. }
  243. });
  244. });
  245. describe('错误处理验证', () => {
  246. it('应该在打印机不存在时创建打印任务失败', async () => {
  247. const taskData = {
  248. printerSn: 'NONEXISTENT_PRINTER',
  249. content: '测试内容',
  250. printType: 'RECEIPT'
  251. };
  252. const response = await client.tasks.$post({
  253. json: taskData
  254. }, {
  255. headers: {
  256. 'Authorization': `Bearer ${userToken}`
  257. }
  258. });
  259. // 应该返回错误
  260. expect(response.status).toBe(500);
  261. if (response.status === 500) {
  262. const data = await response.json();
  263. expect(data.success).toBe(false);
  264. expect(data.message).toContain('打印机不存在');
  265. }
  266. });
  267. it('应该在打印任务不存在时查询状态失败', async () => {
  268. const response = await client['tasks/:taskId/status'].$get({
  269. param: { taskId: 'NONEXISTENT_TASK' }
  270. }, {
  271. headers: {
  272. 'Authorization': `Bearer ${userToken}`
  273. }
  274. });
  275. // 应该返回500(服务层抛出错误)
  276. expect(response.status).toBe(500);
  277. if (response.status === 500) {
  278. const data = await response.json();
  279. expect(data.success).toBe(false);
  280. expect(data.message).toBe('打印任务不存在');
  281. }
  282. });
  283. it('应该在缺少飞鹅API配置时返回错误', async () => {
  284. // 清理配置
  285. const dataSource = await IntegrationTestDatabase.getDataSource();
  286. await dataSource.getRepository(FeieConfigMt).delete({ tenantId: 1 });
  287. // 尝试查询打印机列表
  288. const response = await client.printers.$get({
  289. query: {}
  290. }, {
  291. headers: {
  292. 'Authorization': `Bearer ${userToken}`
  293. }
  294. });
  295. // 应该返回400,因为缺少配置
  296. expect(response.status).toBe(400);
  297. if (response.status === 400) {
  298. const data = await response.json();
  299. expect(data.success).toBe(false);
  300. expect(data.message).toBe('飞鹅API配置未找到或配置不完整');
  301. }
  302. });
  303. });
  304. describe('调度器管理功能验证', () => {
  305. it.skip('应该能够获取调度器状态 - 需要实际飞鹅API连接', async () => {
  306. const response = await client['scheduler/status'].$get({}, {
  307. headers: {
  308. 'Authorization': `Bearer ${userToken}`
  309. }
  310. });
  311. expect(response.status).toBe(200);
  312. if (response.status === 200) {
  313. const data = await response.json();
  314. expect(data.success).toBe(true);
  315. expect(data.data).toBeDefined();
  316. expect(data.data.isRunning).toBeDefined();
  317. expect(data.data.lastRunTime).toBeDefined();
  318. }
  319. });
  320. it.skip('应该能够启动和停止调度器 - 需要实际飞鹅API连接', async () => {
  321. // 启动调度器
  322. const startResponse = await client['scheduler/start'].$post({}, {
  323. headers: {
  324. 'Authorization': `Bearer ${userToken}`
  325. }
  326. });
  327. expect(startResponse.status).toBe(200);
  328. if (startResponse.status === 200) {
  329. const data = await startResponse.json();
  330. expect(data.success).toBe(true);
  331. expect(data.message).toBe('调度器已启动');
  332. }
  333. // 停止调度器
  334. const stopResponse = await client['scheduler/stop'].$post({}, {
  335. headers: {
  336. 'Authorization': `Bearer ${userToken}`
  337. }
  338. });
  339. expect(stopResponse.status).toBe(200);
  340. if (stopResponse.status === 200) {
  341. const data = await stopResponse.json();
  342. expect(data.success).toBe(true);
  343. expect(data.message).toBe('调度器已停止');
  344. }
  345. });
  346. it.skip('应该能够进行调度器健康检查 - 需要实际飞鹅API连接', async () => {
  347. const response = await client['scheduler/health'].$get({}, {
  348. headers: {
  349. 'Authorization': `Bearer ${userToken}`
  350. }
  351. });
  352. expect(response.status).toBe(200);
  353. if (response.status === 200) {
  354. const data = await response.json();
  355. expect(data.success).toBe(true);
  356. expect(data.data).toBeDefined();
  357. expect(data.data.healthy).toBeDefined();
  358. expect(data.data.message).toBeDefined();
  359. }
  360. });
  361. });
  362. describe('配置管理功能验证', () => {
  363. it('应该能够获取打印配置', async () => {
  364. const response = await client.config.$get({}, {
  365. headers: {
  366. 'Authorization': `Bearer ${userToken}`
  367. }
  368. });
  369. expect(response.status).toBe(200);
  370. if (response.status === 200) {
  371. const data = await response.json();
  372. expect(data.success).toBe(true);
  373. expect(data.data.data).toBeDefined();
  374. expect(Array.isArray(data.data.data)).toBe(true);
  375. // 应该包含飞鹅API配置
  376. const feieConfigs = data.data.data.filter((config: any) =>
  377. config.configKey.startsWith('feie.api.')
  378. );
  379. expect(feieConfigs.length).toBeGreaterThan(0);
  380. }
  381. });
  382. it('应该能够更新打印配置', async () => {
  383. const updateData = {
  384. configValue: 'new_test_user'
  385. };
  386. const response = await client['config/:configKey'].$put({
  387. param: { configKey: 'feie.api.user' },
  388. json: updateData
  389. }, {
  390. headers: {
  391. 'Authorization': `Bearer ${userToken}`
  392. }
  393. });
  394. expect(response.status).toBe(200);
  395. if (response.status === 200) {
  396. const data = await response.json();
  397. expect(data.success).toBe(true);
  398. expect(data.data.configKey).toBe('feie.api.user');
  399. expect(data.data.configValue).toBe('new_test_user');
  400. }
  401. // 验证配置已更新
  402. const dataSource = await IntegrationTestDatabase.getDataSource();
  403. const updatedConfig = await dataSource.getRepository(FeieConfigMt).findOne({
  404. where: { tenantId: 1, configKey: 'feie.api.user' }
  405. });
  406. expect(updatedConfig?.configValue).toBe('new_test_user');
  407. });
  408. it('应该在配置值为空时返回错误', async () => {
  409. const updateData = {
  410. configValue: ''
  411. };
  412. const response = await client['config/:configKey'].$put({
  413. param: { configKey: 'feie.api.user' },
  414. json: updateData
  415. }, {
  416. headers: {
  417. 'Authorization': `Bearer ${userToken}`
  418. }
  419. });
  420. // 应该返回400
  421. expect(response.status).toBe(400);
  422. if (response.status === 400) {
  423. const data = await response.json();
  424. expect(data.success).toBe(false);
  425. expect(data.message).toBe('配置值不能为空');
  426. }
  427. });
  428. });
  429. // 新增:越权访问防护测试
  430. describe('越权访问防护验证', () => {
  431. it('应该拒绝访问其他用户的打印机详情', async () => {
  432. const dataSource = await IntegrationTestDatabase.getDataSource();
  433. // 为otherUser创建打印机
  434. const otherUserPrinter = await FeieTestDataFactory.createTestPrinter(dataSource, 1);
  435. // testUser尝试访问otherUser的打印机
  436. const response = await client['printers/:printerSn'].$get({
  437. param: { printerSn: otherUserPrinter.printerSn }
  438. }, {
  439. headers: {
  440. 'Authorization': `Bearer ${userToken}` // testUser的token
  441. }
  442. });
  443. // 应该返回404,因为打印机查询包含租户ID过滤
  444. expect(response.status).toBe(404);
  445. });
  446. it('应该拒绝跨租户访问打印机详情', async () => {
  447. const dataSource = await IntegrationTestDatabase.getDataSource();
  448. // 为otherTenantUser创建打印机(租户2)
  449. const otherTenantPrinter = await FeieTestDataFactory.createTestPrinter(dataSource, 2);
  450. // testUser(租户1)尝试访问otherTenantUser(租户2)的打印机
  451. const response = await client['printers/:printerSn'].$get({
  452. param: { printerSn: otherTenantPrinter.printerSn }
  453. }, {
  454. headers: {
  455. 'Authorization': `Bearer ${userToken}` // 租户1的用户
  456. }
  457. });
  458. // 应该返回404,因为租户ID不匹配
  459. expect(response.status).toBe(404);
  460. });
  461. });
  462. // 新增:边界条件测试
  463. describe('边界条件测试', () => {
  464. it('应该处理打印机名称为空的情况', async () => {
  465. const dataSource = await IntegrationTestDatabase.getDataSource();
  466. // 创建打印机名称为空的打印机
  467. const printer = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '' });
  468. // 查询打印机列表
  469. const response = await client.printers.$get({
  470. query: {}
  471. }, {
  472. headers: {
  473. 'Authorization': `Bearer ${userToken}`
  474. }
  475. });
  476. expect(response.status).toBe(200);
  477. if (response.status === 200) {
  478. const data = await response.json();
  479. expect(data.success).toBe(true);
  480. // 应该能正常返回,即使打印机名称为空
  481. expect(data.data.data).toHaveLength(1);
  482. }
  483. });
  484. it('应该处理打印机状态无效的情况', async () => {
  485. const dataSource = await IntegrationTestDatabase.getDataSource();
  486. // 创建状态为无效的打印机
  487. const printer = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerStatus: 'INVALID_STATUS' });
  488. // 查询打印机列表
  489. const response = await client.printers.$get({
  490. query: {}
  491. }, {
  492. headers: {
  493. 'Authorization': `Bearer ${userToken}`
  494. }
  495. });
  496. expect(response.status).toBe(200);
  497. if (response.status === 200) {
  498. const data = await response.json();
  499. expect(data.success).toBe(true);
  500. // 应该能正常返回,即使状态无效
  501. expect(data.data.data).toHaveLength(1);
  502. expect(data.data.data[0].printerStatus).toBe('INVALID_STATUS');
  503. }
  504. });
  505. });
  506. // 新增:管理员权限测试
  507. describe('管理员权限验证', () => {
  508. it('管理员应该能够访问所有打印机', async () => {
  509. const dataSource = await IntegrationTestDatabase.getDataSource();
  510. // 创建多个打印机
  511. const printer1 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '管理员打印机1' });
  512. const printer2 = await FeieTestDataFactory.createTestPrinter(dataSource, 1, { printerName: '管理员打印机2' });
  513. // 管理员查询打印机列表
  514. const response = await client.printers.$get({
  515. query: {}
  516. }, {
  517. headers: {
  518. 'Authorization': `Bearer ${adminToken}`
  519. }
  520. });
  521. expect(response.status).toBe(200);
  522. if (response.status === 200) {
  523. const data = await response.json();
  524. expect(data.success).toBe(true);
  525. // 管理员应该能看到所有打印机
  526. expect(data.data.data).toHaveLength(2);
  527. }
  528. });
  529. it('管理员应该能够更新打印机配置', async () => {
  530. const dataSource = await IntegrationTestDatabase.getDataSource();
  531. // 创建打印机
  532. const printer = await FeieTestDataFactory.createTestPrinter(dataSource, 1);
  533. // 管理员更新打印机
  534. const updateData = {
  535. printerName: '管理员更新后的打印机',
  536. printerType: '80mm'
  537. };
  538. const response = await client['printers/:printerSn'].$put({
  539. param: { printerSn: printer.printerSn },
  540. json: updateData
  541. }, {
  542. headers: {
  543. 'Authorization': `Bearer ${adminToken}`
  544. }
  545. });
  546. expect(response.status).toBe(200);
  547. if (response.status === 200) {
  548. const data = await response.json();
  549. expect(data.success).toBe(true);
  550. expect(data.data.printerName).toBe('管理员更新后的打印机');
  551. expect(data.data.printerType).toBe('80mm');
  552. }
  553. });
  554. });
  555. });