Task 3-1: Integration Test - Rental Lifecycle
Phase: 3 Priority: Medium Module:
rentalDepends on: task-1-1 Reference: docs/bountyhunter-blockchain-p2/details/feature-nft-rental/SPEC.md
Background
Sau khi migrate sang async queue (task-1-1), cần có integration test toàn bộ rental lifecycle: create → rent → return/cancel. Mỗi operation cần test cả EVM path và IMX path (riêng createRentNft). Đặc biệt cần test error codes granular (task-1-2) và chain validation (task-2-1) đã được implement đúng.
Tasks
Note: Sau task-1-1, controller trả HTTP 202 thay vì blocking response. Test cần
waitForJobCompletionđể kiểm tra webhook. MockBlockchainUtilvàImmutableUtilđể tránh gọi thật lên chain. Đảm bảo task-1-1 và task-1-2 đã hoàn thành trước khi viết test.
- [ ] Tạo test file
rental.integration.spec.ts - [ ] Test case 1 — Create Rent NFT (EVM):
it('POST /rental/create-rent-nft (EVM) → queued → tx executed → webhook called', async () => { mockBlockchainUtil.createRentNft.mockResolvedValue('0xrent123'); const res = await request(app.getHttpServer()) .post('/rental/create-rent-nft') .send({ chainId: EVM_CHAIN_ID, nftId: '1', duration: 7, webhookUrl: 'http://backend/webhook' }); expect(res.status).toBe(202); expect(res.body).toHaveProperty('queued', true); expect(res.body).toHaveProperty('jobId'); await waitForJobCompletion(); expect(mockWebhook).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ status: 'SUCCESS', txHash: '0xrent123' }) ); }); - [ ] Test case 2 — Rent NFT (on-chain execution):
- Mock
blockchainUtil.rentNft→ success - Verify webhook success callback
- [ ] Test case 3 — Cancel Rent NFT:
- Mock
blockchainUtil.cancelRentNft→ success - Verify cancellation webhook callback
- [ ] Test case 4 — Return Rent NFT:
- Mock
blockchainUtil.returnRentNft→ success - Verify return webhook callback
- [ ] Test case 5 — IMX path cho createRentNft:
it('POST /rental/create-rent-nft (IMX chainId) → ImmutableUtil called', async () => { mockImmutableUtil.createRentNft.mockResolvedValue({ orderId: 'imx-order-1' }); await request(app.getHttpServer()) .post('/rental/create-rent-nft') .send({ chainId: IMX_CHAIN_ID, nftId: '1', duration: 7, ... }); await waitForJobCompletion(); expect(mockImmutableUtil.createRentNft).toHaveBeenCalled(); expect(mockBlockchainUtil.createRentNft).not.toHaveBeenCalled(); }); - [ ] Test case 6 — Unsupported chain error:
it('returnRentNft with IMX chainId → RENTAL_UNSUPPORTED_CHAIN error', async () => { const res = await request(app.getHttpServer()) .post('/rental/return-rent-nft') .send({ chainId: IMX_CHAIN_ID, ... }); expect(res.body.code).toBe('RENTAL_UNSUPPORTED_CHAIN'); }); - [ ] Test case 7 — Error code:
insufficient funds→RENTAL_INSUFFICIENT_BALANCE:it('blockchain throws insufficient funds → RENTAL_INSUFFICIENT_BALANCE webhook payload', async () => { mockBlockchainUtil.rentNft.mockRejectedValue(new Error('insufficient funds for gas')); // ...simulate max retries... expect(mockWebhook).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ errorCode: 'RENTAL_INSUFFICIENT_BALANCE' }) ); });
Verification / Acceptance Criteria
- [ ] 4 operations (create, rent, cancel, return) đều trả HTTP 202 ngay lập tức
- [ ] Webhook success được gọi sau khi job hoàn thành với
txHash - [ ] IMX path:
ImmutableUtil.createRentNftđược gọi, không phải EVM path - [ ] IMX return →
RENTAL_UNSUPPORTED_CHAINerror trong response - [ ] Granular error codes trong webhook failure payload khớp với loại blockchain error
- [ ] Test pass khi chạy
npm run test
Files to Modify
src/rental/tests/rental.integration.spec.ts(tạo mới)