Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: plugin rabbi trader tests #2520

Merged
merged 5 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions packages/plugin-rabbi-trader/__tests__/dexscreener.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getDexScreenerData, analyzePair } from '../src/dexscreener';
import puppeteer from 'puppeteer';

// Mock puppeteer
vi.mock('puppeteer', () => ({
default: {
launch: vi.fn().mockResolvedValue({
newPage: vi.fn().mockResolvedValue({
goto: vi.fn(),
evaluate: vi.fn().mockResolvedValue({
pairs: [{
chainId: 'solana',
dexId: 'raydium',
pairAddress: '0x123',
baseToken: {
address: '0xabc',
name: 'Test Token',
symbol: 'TEST',
decimals: 18
},
price: '1.5',
priceUsd: '1.5',
txns: {
m5: { buys: 10, sells: 5 },
h1: { buys: 50, sells: 30 },
h6: { buys: 200, sells: 150 },
h24: { buys: 500, sells: 400 }
},
volume: {
m5: 1000,
h1: 5000,
h6: 20000,
h24: 50000
},
priceChange: {
m5: 0.5,
h1: 6.0, // Above 5% threshold
h6: -0.8,
h24: 2.5
}
}]
}),
close: vi.fn()
}),
close: vi.fn()
})
}
}));

describe('DexScreener Functions', () => {
beforeEach(() => {
vi.clearAllMocks();
});

describe('getDexScreenerData', () => {
it('should fetch and return dexscreener data', async () => {
const data = await getDexScreenerData();
expect(data).toBeDefined();
expect(data.pairs).toHaveLength(1);
expect(data.pairs[0].chainId).toBe('solana');
expect(data.pairs[0].dexId).toBe('raydium');
expect(puppeteer.launch).toHaveBeenCalledWith({ headless: 'new' });
});

it('should handle puppeteer errors', async () => {
vi.mocked(puppeteer.launch).mockRejectedValueOnce(new Error('Browser launch failed'));
await expect(getDexScreenerData()).rejects.toThrow('Browser launch failed');
});
});

describe('analyzePair', () => {
const mockPair = {
chainId: 'solana',
dexId: 'raydium',
pairAddress: '0x123',
baseToken: {
address: '0xabc',
name: 'Test Token',
symbol: 'TEST',
decimals: 18
},
price: '1.5',
priceUsd: '1.5',
txns: {
m5: { buys: 10, sells: 5 },
h1: { buys: 50, sells: 30 },
h6: { buys: 200, sells: 150 },
h24: { buys: 500, sells: 400 }
},
volume: {
m5: 1000,
h1: 5000,
h6: 20000,
h24: 50000 // Above 10k threshold
},
priceChange: {
m5: 0.5,
h1: 6.0, // Above 5% threshold
h6: -0.8,
h24: 2.5
}
};

it('should analyze pair data with significant movement', () => {
const analysis = analyzePair(mockPair);
expect(analysis).toBeTruthy();
expect(analysis).toEqual({
symbol: 'TEST',
price: 1.5,
priceChange: 6.0,
volume24h: 50000,
buyCount: 50,
sellCount: 30
});
});

it('should return false for low volume pairs', () => {
const lowVolumePair = {
...mockPair,
volume: {
...mockPair.volume,
h24: 5000 // Below 10k threshold
}
};
expect(analyzePair(lowVolumePair)).toBe(false);
});

it('should return false for low price change', () => {
const lowPriceChangePair = {
...mockPair,
priceChange: {
...mockPair.priceChange,
h1: 2.0 // Below 5% threshold
}
};
expect(analyzePair(lowPriceChangePair)).toBe(false);
});
});
});
65 changes: 65 additions & 0 deletions packages/plugin-rabbi-trader/__tests__/tokenUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { loadTokenAddresses } from '../src/tokenUtils';
import * as fs from 'fs';
import * as path from 'path';

// Mock fs and path
vi.mock('fs', async () => {
const actual = await vi.importActual('fs');
return {
...actual,
readFileSync: vi.fn()
};
});

vi.mock('path', async () => {
const actual = await vi.importActual('path');
return {
...actual,
resolve: vi.fn()
};
});

describe('Token Utils', () => {
beforeEach(() => {
vi.clearAllMocks();
// Setup default mock implementation
vi.mocked(path.resolve).mockReturnValue('/mock/path/tokenaddresses.json');
});

describe('loadTokenAddresses', () => {
it('should load and parse token addresses successfully', () => {
const mockAddresses = ['0x123', '0x456', '0x789'];
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockAddresses));

const addresses = loadTokenAddresses();
expect(addresses).toEqual(mockAddresses);
expect(fs.readFileSync).toHaveBeenCalledWith('/mock/path/tokenaddresses.json', 'utf8');
});

it('should throw error if file is not found', () => {
vi.mocked(fs.readFileSync).mockImplementation(() => {
throw new Error('ENOENT: no such file or directory');
});

expect(() => loadTokenAddresses()).toThrow('Token addresses file not found or invalid');
});

it('should throw error if file contains invalid JSON', () => {
vi.mocked(fs.readFileSync).mockReturnValue('invalid json content');

expect(() => loadTokenAddresses()).toThrow('Token addresses file not found or invalid');
});

it('should use correct file path', () => {
const mockAddresses = ['0x123'];
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockAddresses));

loadTokenAddresses();
expect(path.resolve).toHaveBeenCalledWith(
process.cwd(),
'../characters/tokens/tokenaddresses.json'
);
});
});
});
100 changes: 100 additions & 0 deletions packages/plugin-rabbi-trader/__tests__/wallet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getWalletKeypair, getWalletBalance } from '../src/wallet';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { IAgentRuntime } from '@elizaos/core';

// Mock dependencies
vi.mock('@solana/web3.js', () => {
const mockGetBalance = vi.fn().mockResolvedValue(1000000000); // 1 SOL in lamports
const mockConnection = {
getBalance: mockGetBalance
};
return {
Connection: vi.fn(() => mockConnection),
Keypair: {
fromSecretKey: vi.fn().mockReturnValue({
publicKey: {
toBase58: () => 'mocked-public-key'
}
})
},
PublicKey: vi.fn()
};
});

describe('Wallet Functions', () => {
let mockRuntime: IAgentRuntime;

beforeEach(() => {
// Reset mocks
vi.clearAllMocks();

// Setup mock runtime
mockRuntime = {
getSetting: vi.fn((key: string) => {
switch (key) {
case 'WALLET_PRIVATE_KEY':
// Use a valid base58 string (this is just an example, not a real private key)
return '5KQFVpCEW7wEDVzAj6HnE6YpQem6X2L8qzqXNbrWJCVB';
case 'SOLANA_RPC_URL':
return 'https://api.mainnet-beta.solana.com';
default:
return undefined;
}
}),
log: vi.fn(),
error: vi.fn()
};
});

describe('getWalletKeypair', () => {
it('should create a keypair from private key', () => {
const keypair = getWalletKeypair(mockRuntime);
expect(keypair).toBeDefined();
expect(keypair.publicKey).toBeDefined();
expect(keypair.publicKey.toBase58).toBeDefined();
expect(keypair.publicKey.toBase58()).toBe('mocked-public-key');
});

it('should throw error if private key is missing', () => {
mockRuntime.getSetting = vi.fn().mockReturnValue(undefined);
expect(() => getWalletKeypair(mockRuntime)).toThrow('No wallet private key configured');
});

it('should throw error if private key is invalid', () => {
mockRuntime.getSetting = vi.fn().mockReturnValue('invalid-key');
expect(() => getWalletKeypair(mockRuntime)).toThrow();
});
});

describe('getWalletBalance', () => {
it('should return correct SOL balance', async () => {
const balance = await getWalletBalance(mockRuntime);
expect(balance).toBe(1); // 1 SOL (1000000000 lamports)
expect(Connection).toHaveBeenCalledWith('https://api.mainnet-beta.solana.com');
});

it('should use default RPC URL if not provided', async () => {
mockRuntime.getSetting = vi.fn((key: string) => {
if (key === 'WALLET_PRIVATE_KEY') {
return '5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG';
}
return undefined;
});

await getWalletBalance(mockRuntime);
expect(Connection).toHaveBeenCalledWith('https://api.mainnet-beta.solana.com');
});

it('should handle connection errors by returning 0', async () => {
const mockConnection = {
getBalance: vi.fn().mockRejectedValue(new Error('Connection failed'))
};
vi.mocked(Connection).mockImplementation(() => mockConnection as any);

const balance = await getWalletBalance(mockRuntime);
expect(balance).toBe(0);
expect(mockConnection.getBalance).toHaveBeenCalled();
});
});
});
9 changes: 8 additions & 1 deletion packages/plugin-rabbi-trader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@
"tsup": "8.3.5",
"ws": "^8.0.0"
},
"devDependencies": {
"vitest": "^1.2.1",
"@vitest/coverage-v8": "^1.2.1"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch"
"dev": "tsup --format esm --dts --watch",
"test": "vitest run",
"test:watch": "vitest watch",
"test:coverage": "vitest run --coverage"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
Expand Down
15 changes: 15 additions & 0 deletions packages/plugin-rabbi-trader/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['__tests__/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/**/*.ts'],
exclude: ['**/*.d.ts', '**/*.test.ts']
}
}
});
Loading