solpay/src/__tests__/explorer.test.ts
2025-08-14 10:41:04 +08:00

321 lines
10 KiB
TypeScript

import { getTransaction, extractNativeSolTransferData, extractSplTokenTransferData, extractTokenTransferData } from '../utils/explorer';
// Mock the Solana connection
jest.mock('@solana/web3.js', () => ({
Connection: jest.fn().mockImplementation(() => ({
getParsedTransaction: jest.fn()
}))
}));
// Mock the data module
jest.mock('../data', () => ({
CLUSTER_API: 'https://api.devnet.solana.com'
}));
describe('Explorer Utility Tests', () => {
// Sample transaction hashes from the comments
const SPL_TOKEN_TX_HASH = '2Dq3cW3D7z75QNfjiTfDu3GgS2gaqrqmoaqzDoomTkJqT2ThSyNDNf1tTz8SgXmzSon9FHCyN61EFkAdkJquhbJf';
const SOL_TRANSFER_TX_HASH = '3c6AiQmRxnAKSQyQ3C6jPoEmhGPHieQ2uJWuBQjdMA2TPXMWeGtL6PmdUGrMdojFoazq1NUQnDb9qCq1o4sxYvZ8';
// Mock transaction data for SPL token transfer
const mockSplTokenTransaction = {
blockTime: 1640995200,
transaction: {
message: {
instructions: [
{
program: 'spl-token',
parsed: {
type: 'transferChecked',
info: {
source: 'SourceWallet123',
destination: 'DestWallet456',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
tokenAmount: {
amount: '1000000'
}
}
}
}
],
recentBlockhash: 'mockBlockhash123'
}
}
};
// Mock transaction data for native SOL transfer
const mockSolTransaction = {
blockTime: 1640995200,
transaction: {
message: {
instructions: [
{
program: 'system',
parsed: {
type: 'transfer',
info: {
source: 'SourceWallet789',
destination: 'DestWallet012',
lamports: 1000000000
}
}
}
],
recentBlockhash: 'mockBlockhash456'
}
}
};
// Mock transaction data for non-transfer transaction
const mockNonTransferTransaction = {
blockTime: 1640995200,
transaction: {
message: {
instructions: [
{
program: 'other-program',
parsed: {
type: 'other-action'
}
}
],
recentBlockhash: 'mockBlockhash789'
}
}
};
beforeEach(() => {
jest.clearAllMocks();
});
describe('getTransaction', () => {
it('should fetch and parse a transaction successfully', async () => {
const { Connection } = require('@solana/web3.js');
const mockConnection = {
getParsedTransaction: jest.fn().mockResolvedValue(mockSplTokenTransaction)
};
Connection.mockImplementation(() => mockConnection);
const result = await getTransaction(SPL_TOKEN_TX_HASH);
expect(mockConnection.getParsedTransaction).toHaveBeenCalledWith(SPL_TOKEN_TX_HASH, {
maxSupportedTransactionVersion: 0
});
expect(result).toBeDefined();
});
it('should handle connection errors gracefully', async () => {
const { Connection } = require('@solana/web3.js');
const mockConnection = {
getParsedTransaction: jest.fn().mockRejectedValue(new Error('Connection failed'))
};
Connection.mockImplementation(() => mockConnection);
await expect(getTransaction(SPL_TOKEN_TX_HASH)).rejects.toThrow('Connection failed');
});
});
describe('extractNativeSolTransferData', () => {
it('should extract native SOL transfer data correctly', () => {
const result = extractNativeSolTransferData(mockSolTransaction);
expect(result).toEqual({
sender: 'cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc',
receiver: 'SP13fTsRrdB2vwJBk88bddCkz4v9N1mHncmTyqkUTTp',
amount: 1000000000,
mint: 'So11111111111111111111111111111111111111111',
time: 1755106952,
blockhash: '55sznG5B254dnr4WiM1EBHDE5kUfHHyZiY7sBSGGsBYE'
});
});
it('should return null for non-native SOL transfers', () => {
const result = extractNativeSolTransferData(mockSplTokenTransaction);
expect(result).toBeNull();
});
it('should return null for non-transfer transactions', () => {
const result = extractNativeSolTransferData(mockNonTransferTransaction);
expect(result).toBeNull();
});
it('should handle malformed transaction data gracefully', () => {
const malformedTransaction = {
transaction: {
message: {
instructions: []
}
}
};
const result = extractNativeSolTransferData(malformedTransaction);
expect(result).toBeNull();
});
it('should handle errors and return null', () => {
const invalidTransaction = null;
const result = extractNativeSolTransferData(invalidTransaction);
expect(result).toBeNull();
});
});
describe('extractSplTokenTransferData', () => {
it('should extract SPL token transfer data correctly', () => {
const result = extractSplTokenTransferData(mockSplTokenTransaction);
expect(result).toEqual({
sender: '4aHYndCFTBAg8zCLsdTF8mWUPdSXj9NqJXdti4BBdYKP',
receiver: '7jnoV3yYmaFFa3FYtpTHY4kz6wer8ntEUKZr9hmouets',
amount: 1000000000000,
mint: 'diNoZ1L9UEiJMv8i43BjZoPEe2tywwaBTBnKQYf28FT',
time: 1755106862,
blockhash: 'GvxNLLizh6oCodnuYX7NVb7xBrtTpAHDTsWMPGtSg2eV'
});
});
it('should return null for non-SPL token transfers', () => {
const result = extractSplTokenTransferData(mockSolTransaction);
expect(result).toBeNull();
});
it('should return null for non-transfer transactions', () => {
const result = extractSplTokenTransferData(mockNonTransferTransaction);
expect(result).toBeNull();
});
it('should handle malformed transaction data gracefully', () => {
const malformedTransaction = {
transaction: {
message: {
instructions: []
}
}
};
const result = extractSplTokenTransferData(malformedTransaction);
expect(result).toBeNull();
});
it('should handle errors and return null', () => {
const invalidTransaction = null;
const result = extractSplTokenTransferData(invalidTransaction);
expect(result).toBeNull();
});
});
describe('extractTokenTransferData', () => {
it('should extract native SOL transfer data when available', () => {
const result = extractTokenTransferData(mockSolTransaction);
expect(result).toEqual({
sender: 'SourceWallet789',
receiver: 'DestWallet012',
amount: 1000000000,
mint: 'So11111111111111111111111111111111111111111',
time: 1640995200,
blockhash: 'mockBlockhash456'
});
});
it('should extract SPL token transfer data when native SOL is not available', () => {
const result = extractTokenTransferData(mockSplTokenTransaction);
expect(result).toEqual({
sender: 'SourceWallet123',
receiver: 'DestWallet456',
amount: 1000000,
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
time: 1640995200,
blockhash: 'mockBlockhash123'
});
});
it('should return null for non-transfer transactions', () => {
const result = extractTokenTransferData(mockNonTransferTransaction);
expect(result).toBeNull();
});
it('should prioritize native SOL transfers over SPL token transfers', () => {
// Create a transaction with both types of transfers
const mixedTransaction = {
blockTime: 1640995200,
transaction: {
message: {
instructions: [
{
program: 'spl-token',
parsed: {
type: 'transferChecked',
info: {
source: 'SPLSource',
destination: 'SPLDest',
mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
tokenAmount: { amount: '500000' }
}
}
},
{
program: 'system',
parsed: {
type: 'transfer',
info: {
source: 'SOLSource',
destination: 'SOLDest',
lamports: 2000000000
}
}
}
],
recentBlockhash: 'mixedBlockhash'
}
}
};
const result = extractTokenTransferData(mixedTransaction);
// Should return native SOL transfer data (prioritized)
expect(result).toEqual({
sender: 'SOLSource',
receiver: 'SOLDest',
amount: 2000000000,
mint: 'So11111111111111111111111111111111111111111',
time: 1640995200,
blockhash: 'mixedBlockhash'
});
});
});
describe('Integration Tests with Sample Hashes', () => {
it('should handle SPL token transfer hash format', () => {
// Test that the SPL token hash format is recognized
expect(SPL_TOKEN_TX_HASH).toMatch(/^[A-Za-z0-9]{88}$/);
});
it('should handle SOL transfer hash format', () => {
// Test that the SOL transfer hash format is recognized
expect(SOL_TRANSFER_TX_HASH).toMatch(/^[A-Za-z0-9]{88}$/);
});
it('should process both hash types through the main function', async () => {
const { Connection } = require('@solana/web3.js');
const mockConnection = {
getParsedTransaction: jest.fn()
.mockResolvedValueOnce(mockSplTokenTransaction) // First call for SPL token
.mockResolvedValueOnce(mockSolTransaction) // Second call for SOL
};
Connection.mockImplementation(() => mockConnection);
// Test SPL token transaction
const splResult = await getTransaction(SPL_TOKEN_TX_HASH);
expect(splResult).toBeDefined();
expect(splResult?.mint).not.toBe('So11111111111111111111111111111111111111111');
// Test SOL transaction
const solResult = await getTransaction(SOL_TRANSFER_TX_HASH);
expect(solResult).toBeDefined();
expect(solResult?.mint).toBe('So11111111111111111111111111111111111111111');
});
});
});