Skip to content

Task 3-1: Integration Test - cNFT Mint & Transfer

Phase: 3 Priority: Medium Module: solana Depends 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.js Connection trong test module bằng jest.mock hoặc DI override. Mock Metaplex Umi client để tránh gọi thật lên Solana. Sử dụng @nestjs/testing Test.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 bare sendAndConfirmTransaction
  • [ ] User transfer: decryptData() được gọi để lấy key; transaction signed bởi user
  • [ ] Retry: mock fail 1 lần → sendWithRetry retry 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)