Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
274 changes: 271 additions & 3 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"@electric-sql/pglite": "^0.3.11",
"@elizaos/plugin-openai": "^1.5.16",
"@elizaos/plugin-openrouter": "^1.5.14",
"@elizaos/plugin-solana": "^1.2.6",
"@elizaos/plugin-sql": "^1.6.3",
"@types/bun": "^1.3.0",
"@types/node": "^24.7.0",
"prettier": "3.5.3",
Expand All @@ -54,7 +56,7 @@
"format:check": "prettier --check ./src",
"check": "biome check --write .",
"test": "elizaos test",
"test:unit": "bun test --bail src/__tests__/unit/categorize-actions.test.ts && bun test --bail src/__tests__/unit/collect-provider-data.test.ts && bun test --bail src/__tests__/unit/execute-analysis-actions.test.ts && bun test --bail src/__tests__/unit/select-relevant-data-actions.test.ts && bun test --bail src/__tests__/unit/generate-analysis.test.ts && bun test --bail src/__tests__/unit/generate-recommendations.test.ts && bun test --bail src/__tests__/unit/save-analysis.test.ts && bun test --bail src/__tests__/unit/process-decisions.test.ts && bun test --bail src/__tests__/unit/db-getters.test.ts",
"test:unit": "bun test --bail src/__tests__/unit/categorize-actions.test.ts && bun test --bail src/__tests__/unit/collect-provider-data.test.ts && bun test --bail src/__tests__/unit/execute-analysis-actions.test.ts && bun test --bail src/__tests__/unit/select-relevant-data-actions.test.ts && bun test --bail src/__tests__/unit/generate-analysis.test.ts && bun test --bail src/__tests__/unit/generate-recommendations.test.ts && bun test --bail src/__tests__/unit/save-analysis.test.ts && bun test --bail src/__tests__/unit/process-decisions.test.ts && bun test --bail src/__tests__/unit/db-getters.test.ts && bun test --bail src/__tests__/unit/wallet-manager.test.ts",
"test:integration": "bun test --bail src/__tests__/integration/run-analysis.test.ts && bun test --bail src/__tests__/integration/api-routes.test.ts",
"test:integration:llm": "USE_REAL_LLM=true bun run test:integration",
"test:all": "bun run test:unit && bun run test:integration",
Expand Down
43 changes: 41 additions & 2 deletions src/__tests__/integration/api-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ describe('API Routes - Integration Tests', () => {
limitActions: useRealLLM ? 10 : undefined,
});

service = new SendoWorkerService(runtime);
await service.initialize(runtime);
// Get service via service loading mechanism
service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService;

// Setup LLM mock using fixture-based system
setupLLMMock(runtime, { useFixtures: true });
Expand Down Expand Up @@ -194,6 +194,45 @@ describe('API Routes - Integration Tests', () => {
});
});

describe('POST /analysis - Wallet Balance Validation', () => {
it('should check wallet balance before running analysis', async () => {
// Import wallet utils
const { checkWalletBalance } = await import('../../utils/walletManager.js');

// Mock checkWalletBalance to verify it's called
// Note: In real scenario, wallet auto-creation happens in service.initialize()
// and balance check happens in route handler before calling runAnalysis

// This test validates the integration flow:
// 1. Wallet should exist (created during initialize)
// 2. Balance check should pass (sufficient funds)
// 3. Analysis should run successfully

const result = await service.runAnalysis(runtime.agentId);
expect(result).toBeDefined();
expect(result.id).toBeDefined();
});

it('should validate wallet utilities work with runtime', async () => {
// Import wallet utils
const { hasWallet, getWalletPublicKey } = await import('../../utils/walletManager.js');

// Check if wallet exists
const walletExists = hasWallet(runtime);

if (walletExists) {
// If wallet exists, public key should be retrievable
const publicKey = getWalletPublicKey(runtime);
expect(publicKey).toBeDefined();
expect(typeof publicKey).toBe('string');
} else {
// If no wallet, public key should be null
const publicKey = getWalletPublicKey(runtime);
expect(publicKey).toBeNull();
}
});
});

describe('GET /analysis/:analysisId/actions', () => {
it('should return all actions for analysis', async () => {
const actions = await service.getActionsByAnalysisId(testAnalysisId);
Expand Down
5 changes: 2 additions & 3 deletions src/__tests__/integration/run-analysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ describe('SendoWorkerService - runAnalysis (Integration)', () => {
limitActions: useRealLLM ? 10 : undefined,
});

// Create service
service = new SendoWorkerService(runtime);
await service.initialize(runtime);
// Get service via service loading mechanism
service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService;

// Setup LLM mock using fixture-based system
setupLLMMock(runtime, { useFixtures: true });
Expand Down
34 changes: 16 additions & 18 deletions src/__tests__/unit/categorize-actions.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
/**
* Unit tests for SendoWorkerService.categorizeActions()
* Unit tests for ActionCategorizer
*
* Tests action categorization with REAL runtime and actions
*/

import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
import { SendoWorkerService } from '../../services/sendoWorkerService';
import { ActionCategorizer } from '../../services/analysis';
import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime';
import { setupLLMMock } from '../helpers/mock-llm';
import type { IAgentRuntime, Action } from '@elizaos/core';
import type { IAgentRuntime } from '@elizaos/core';

describe('SendoWorkerService - categorizeActions', () => {
describe('ActionCategorizer - categorize', () => {
let runtime: IAgentRuntime;
let service: SendoWorkerService;
let categorizer: ActionCategorizer;

beforeAll(async () => {
// Create REAL runtime with test actions
Expand All @@ -22,9 +22,8 @@ describe('SendoWorkerService - categorizeActions', () => {
withActionActions: true, // 2 ACTION actions
});

// Create service
service = new SendoWorkerService(runtime);
await service.initialize(runtime);
// Create categorizer
categorizer = new ActionCategorizer(runtime);

// Setup LLM mock using fixture-based system
setupLLMMock(runtime, {
Expand All @@ -38,7 +37,7 @@ describe('SendoWorkerService - categorizeActions', () => {
});

it('should categorize all available actions', async () => {
const result = await service.categorizeActions();
const result = await categorizer.categorize();

// Verify structure
expect(result).toHaveProperty('dataActions');
Expand All @@ -58,12 +57,12 @@ describe('SendoWorkerService - categorizeActions', () => {
});

it('should correctly categorize DATA actions', async () => {
const result = await service.categorizeActions();
const result = await categorizer.categorize();

// Check DATA actions
const dataActionNames = new Set<string>();
for (const actions of result.dataActions.values()) {
actions.forEach((action) => dataActionNames.add(action.name));
actions.forEach((action: any) => dataActionNames.add(action.name));
}

expect(dataActionNames.has('GET_WALLET_BALANCE')).toBe(true);
Expand All @@ -72,20 +71,20 @@ describe('SendoWorkerService - categorizeActions', () => {
});

it('should correctly categorize ACTION actions', async () => {
const result = await service.categorizeActions();
const result = await categorizer.categorize();

// Check ACTION actions
const actionActionNames = new Set<string>();
for (const actions of result.actionActions.values()) {
actions.forEach((action) => actionActionNames.add(action.name));
actions.forEach((action: any) => actionActionNames.add(action.name));
}

expect(actionActionNames.has('EXECUTE_SWAP')).toBe(true);
expect(actionActionNames.has('REBALANCE_PORTFOLIO')).toBe(true);
});

it('should group actions by type', async () => {
const result = await service.categorizeActions();
const result = await categorizer.categorize();

// DATA actions should be grouped
expect(result.dataActions.size).toBeGreaterThan(0);
Expand All @@ -106,7 +105,7 @@ describe('SendoWorkerService - categorizeActions', () => {
});

it('should include classification metadata', async () => {
const result = await service.categorizeActions();
const result = await categorizer.categorize();

// Check first classification has all required fields
const classification = result.classifications[0];
Expand All @@ -126,13 +125,12 @@ describe('SendoWorkerService - categorizeActions', () => {
it('should handle empty actions gracefully', async () => {
// Create runtime with NO actions
const emptyRuntime = await createTestRuntime({ testId: 'empty-test' });
const emptyService = new SendoWorkerService(emptyRuntime);
await emptyService.initialize(emptyRuntime);
const emptyCategorizer = new ActionCategorizer(emptyRuntime);

// Setup same LLM mock
setupLLMMock(emptyRuntime, { useFixtures: true });

const result = await emptyService.categorizeActions();
const result = await emptyCategorizer.categorize();

expect(result.classifications).toHaveLength(0);
expect(result.dataActions.size).toBe(0);
Expand Down
43 changes: 20 additions & 23 deletions src/__tests__/unit/collect-provider-data.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/**
* Unit tests for SendoWorkerService collectProviderData
* Unit tests for ProviderCollector
*
* Tests the collection of data from registered providers
*/

import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
import { SendoWorkerService } from '../../services/sendoWorkerService';
import { ProviderCollector } from '../../services/data';
import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime';
import type { IAgentRuntime } from '@elizaos/core';

describe('SendoWorkerService - collectProviderData', () => {
describe('ProviderCollector - collect', () => {
let runtime: IAgentRuntime;
let service: SendoWorkerService;
let collector: ProviderCollector;

beforeAll(async () => {
// Create runtime with providers
Expand All @@ -20,24 +20,23 @@ describe('SendoWorkerService - collectProviderData', () => {
withProviders: true,
});

service = new SendoWorkerService(runtime);
await service.initialize(runtime);
collector = new ProviderCollector(runtime);
});

afterAll(async () => {
await cleanupTestRuntime(runtime);
});

it('should collect data from all registered providers', async () => {
const results = await service.collectProviderData();
const results = await collector.collect();

expect(results).toBeDefined();
expect(Array.isArray(results)).toBe(true);
expect(results.length).toBeGreaterThan(0);
});

it('should include provider name, data, and timestamp', async () => {
const results = await service.collectProviderData();
const results = await collector.collect();

for (const result of results) {
expect(result).toHaveProperty('providerName');
Expand All @@ -49,7 +48,7 @@ describe('SendoWorkerService - collectProviderData', () => {
});

it('should return valid ISO timestamp', async () => {
const results = await service.collectProviderData();
const results = await collector.collect();

if (results.length > 0) {
const timestamp = results[0].timestamp;
Expand All @@ -65,10 +64,8 @@ describe('SendoWorkerService - collectProviderData', () => {
withProviders: false,
});

const emptyService = new SendoWorkerService(emptyRuntime);
await emptyService.initialize(emptyRuntime);

const results = await emptyService.collectProviderData();
const emptyCollector = new ProviderCollector(emptyRuntime);
const results = await emptyCollector.collect();

expect(results).toBeDefined();
expect(Array.isArray(results)).toBe(true);
Expand All @@ -78,44 +75,44 @@ describe('SendoWorkerService - collectProviderData', () => {
});

it('should handle provider errors gracefully', async () => {
// This test ensures collectProviderData doesn't throw if a provider fails
// This test ensures collect doesn't throw if a provider fails
// The runtime's composeState should handle errors internally

await expect(service.collectProviderData()).resolves.toBeDefined();
await expect(collector.collect()).resolves.toBeDefined();
});

it('should collect data from multiple providers', async () => {
const results = await service.collectProviderData();
const results = await collector.collect();

// We registered 2 providers in the test runtime
expect(results.length).toBeGreaterThanOrEqual(2);

// Check that provider names are unique
const providerNames = results.map(r => r.providerName);
const providerNames = results.map((r: any) => r.providerName);
const uniqueNames = new Set(providerNames);
expect(uniqueNames.size).toBe(providerNames.length);
});

it('should include expected test providers', async () => {
const results = await service.collectProviderData();
const results = await collector.collect();

const providerNames = results.map(r => r.providerName);
const providerNames = results.map((r: any) => r.providerName);

// Test runtime registers walletContext and timeContext providers
expect(providerNames).toContain('walletContext');
expect(providerNames).toContain('timeContext');
});

it('should return consistent data structure on multiple calls', async () => {
const results1 = await service.collectProviderData();
const results2 = await service.collectProviderData();
const results1 = await collector.collect();
const results2 = await collector.collect();

// Same number of providers
expect(results1.length).toBe(results2.length);

// Same provider names (order may differ)
const names1 = results1.map(r => r.providerName).sort();
const names2 = results2.map(r => r.providerName).sort();
const names1 = results1.map((r: any) => r.providerName).sort();
const names2 = results2.map((r: any) => r.providerName).sort();
expect(names1).toEqual(names2);
});
});
3 changes: 1 addition & 2 deletions src/__tests__/unit/db-getters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ describe('SendoWorkerService - DB Getters', () => {
testId: 'db-getters-test',
});

service = new SendoWorkerService(runtime);
await service.initialize(runtime);
service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService;

// Create test data
const db = (runtime as any).db;
Expand Down
Loading