Task 3-2: Integration Test - Solana Marketplace & Rental
Phase: 3 Priority: Medium Module:
solanaDepends on: task-1-1 Reference: docs/bountyhunter-blockchain-p2/details/feature-solana-integration/SPEC.md
Background
Solana Marketplace (list, buy) và Rental (create, rent) sử dụng Anchor programs với IDL (MARKETPLACE_IDL, RENTAL_IDL). Đây là Solana-native smart contracts với PDA (Program Derived Address) — cần mock @project-serum/anchor Program để test mà không cần local validator. Chưa có test coverage cho các luồng này dù là core business operations.
Tasks
Note: Mock
@project-serum/anchorProgramtrong DI test module — inject mock thay vì real program. Escrow PDA được tính bởiPublicKey.findProgramAddressSync— cần mock hoặc dùng deterministic test address.AnchorProvidercần được setup với mockWalletvà mockConnection. ImportProgram,AnchorProvider,web3từ@project-serum/anchor.
- [ ] Setup mock Anchor Program trong test module:
const mockMarketplaceProgram = { methods: { listNft: jest.fn().mockReturnValue({ accounts: jest.fn().mockReturnThis(), signers: jest.fn().mockReturnThis(), rpc: jest.fn().mockResolvedValue('list-tx-signature'), }), buyNft: jest.fn().mockReturnValue({ /* similar chain */ }), }, }; TestingModule = await Test.createTestingModule({ providers: [ { provide: 'MARKETPLACE_PROGRAM', useValue: mockMarketplaceProgram }, // ... ], }).compile(); - [ ] Test case 1 — List NFT (Solana Marketplace):
it('POST /solana/list-nft → Anchor listNft instruction executed → Escrow PDA created → webhook', async () => { const res = await request(app.getHttpServer()) .post('/solana/list-nft') .send({ mint: 'NFT...mint', price: 1_000_000, webhookUrl: '...' }); expect(res.status).toBe(201); await waitForJobCompletion(); expect(mockMarketplaceProgram.methods.listNft).toHaveBeenCalled(); expect(mockWebhook).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ status: 'SUCCESS', txHash: 'list-tx-signature' }) ); }); - [ ] Test case 2 — Buy NFT (on-chain purchase):
it('POST /solana/buy-nft → purchase on-chain → NFT transferred to buyer → webhook', async () => { mockMarketplaceProgram.methods.buyNft.mockReturnValue({ accounts: jest.fn().mockReturnThis(), rpc: jest.fn().mockResolvedValue('buy-tx-signature'), }); // ...verify flow expect(mockWebhook).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ status: 'SUCCESS', txHash: 'buy-tx-signature' }) ); }); - [ ] Test case 3 — Create Rental (Rental PDA):
it('POST /solana/create-rental → Rental PDA created via Anchor → webhook', async () => { const mockRentalPda = PublicKey.default; // deterministic test PDA mockRentalProgram.methods.createRental.mockReturnValue({ accounts: jest.fn().mockReturnThis(), rpc: jest.fn().mockResolvedValue('rental-create-sig'), }); // ...verify PDA in response }); - [ ] Test case 4 — Rent NFT (rental started):
it('POST /solana/rent-nft → rental started on-chain → webhook', async () => { // ...mock rentNft Anchor method // verify webhook called with rental details }); - [ ] Test case 5 — Retry on
BlockhashNotFoundtrong Anchor call: - Mock
rpc()throwsBlockhashNotFoundfirst time → retry → success
Verification / Acceptance Criteria
- [ ] List NFT:
program.methods.listNftđược gọi với đúngmintvàprice - [ ] Buy NFT:
program.methods.buyNftđược gọi; webhook success vớitxHash - [ ] Create Rental:
program.methods.createRentalđược gọi; Rental PDA address trong response - [ ] Rent NFT:
program.methods.rentNftđược gọi với đúng duration - [ ] Retry:
BlockhashNotFoundtrong Anchor RPC call →sendWithRetryhandles retry - [ ] Test pass khi chạy
npm run test
Files to Modify
src/solana/tests/solana-marketplace.integration.spec.ts(tạo mới)src/solana/tests/solana-rental.integration.spec.ts(tạo mới)