Task 3-1: Integration Test - cNFT Mint & Transfer
Phase: 3 Priority: Medium Module:
solanaDepends on: task-1-1 Reference: docs/bountyhunter-blockchain-p2/details/feature-solana-integration/SPEC.md
Background
cNFT mint và transfer là operations phức tạp nhất trong Solana module — liên quan đến Merkle tree, Bubblegum program, và Metaplex Umi. Chưa có integration test cho các luồng này. Cần mock SolanaUtil/Connection cho unit tests và có thể sử dụng devnet cho integration tests tùy ngân sách. Sau khi task-1-1 (retry logic) hoàn thành, test cần verify retry behavior.
Tasks
Note: Mock
@solana/web3.jsConnectiontrong test module bằngjest.mockhoặc DI override. Mock MetaplexUmiclient để tránh gọi thật lên Solana. Sử dụng@nestjs/testingTest.createTestingModule. Đối với devnet test: cần SOL airdrop (requestAirdrop) và real Merkle tree — chỉ chạy trong CI với tag@integration.
- [ ] Tạo test file
solana-cnft.integration.spec.ts - [ ] Test case 1 — cNFT Mint (happy path):
it('POST /solana-cnft/mint → Merkle tree leaf created → webhook called', async () => { const mockSignature = 'mock-signature-base58'; mockUmi.send.mockResolvedValue({ signature: mockSignature }); const res = await request(app.getHttpServer()) .post('/solana-cnft/mint') .send({ ownerAddress: 'Btk...abc', merkleTree: 'Tree...xyz', name: 'BountyBall #1', uri: 'https://arweave.net/...', webhookUrl: 'http://backend/webhook', }); expect(res.status).toBe(201); await waitForJobCompletion(); expect(mockWebhook).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ status: 'SUCCESS', signature: mockSignature }) ); }); - [ ] Test case 2 — Admin transfer NFT:
it('POST /solana/transfer-nft → admin transfer to user wallet', async () => { mockSolanaUtil.sendWithRetry.mockResolvedValue('tx-signature-abc'); const res = await request(app.getHttpServer()) .post('/solana/transfer-nft') .send({ toAddress: 'User...wallet', mint: 'NFT...mint', amount: 1 }); expect(res.status).toBe(201); await waitForJobCompletion(); expect(mockWebhook).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ status: 'SUCCESS', txHash: 'tx-signature-abc' }) ); }); - [ ] Test case 3 — User-signed transfer (user signature via
nacl/bs58): - Mock
decryptData()để lấy private key - Mock
nacl.sign/bs58.decode - Verify transaction được sign bởi user key và gửi thành công
- [ ] Test case 4 — Retry on BlockhashNotFound (liên quan task-1-1):
it('BlockhashNotFound on first attempt → retry → success on attempt 2', async () => { mockConnection.sendAndConfirm .mockRejectedValueOnce(new Error('BlockhashNotFound')) .mockResolvedValueOnce('success-signature'); await solanaUtil.sendWithRetry(transaction, signers); expect(mockConnection.sendAndConfirm).toHaveBeenCalledTimes(2); }); - [ ] Test case 5 — RPC failover (liên quan task-1-2):
- Primary RPC fails → fallback connection used
Verification / Acceptance Criteria
- [ ] cNFT mint:
umi.send()được gọi với đúng tham số Merkle tree và leaf metadata - [ ] Admin transfer:
sendWithRetryđược gọi, không phải baresendAndConfirmTransaction - [ ] User transfer:
decryptData()được gọi để lấy key; transaction signed bởi user - [ ] Retry: mock fail 1 lần →
sendWithRetryretry và succeed lần 2 - [ ] RPC fallover: sau primary fail → connection thứ 2 được dùng
- [ ] Test pass khi chạy
npm run test
Files to Modify
src/solana/tests/solana-cnft.integration.spec.ts(tạo mới)src/solana/tests/solana-transfer.integration.spec.ts(tạo mới)