Skip to content

Commit

Permalink
feat: adding tests for instagram client (elizaOS#2454)
Browse files Browse the repository at this point in the history
Co-authored-by: Sayo <hi@sayo.wtf>
  • Loading branch information
ai16z-demirix and wtfsayo authored Jan 17, 2025
1 parent 9e0af0e commit 5a85d7f
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 2 deletions.
90 changes: 90 additions & 0 deletions packages/client-instagram/__tests__/environment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect, vi } from 'vitest';
import { validateInstagramConfig, instagramEnvSchema } from '../src/environment';
import { IAgentRuntime } from '@elizaos/core';

describe('Instagram Environment Configuration', () => {
const mockRuntime: IAgentRuntime = {
getSetting: vi.fn(),
} as unknown as IAgentRuntime;

it('validates correct Instagram configuration', async () => {
const validConfig = {
INSTAGRAM_DRY_RUN: false,
INSTAGRAM_USERNAME: 'test_user',
INSTAGRAM_PASSWORD: 'test_password',
INSTAGRAM_APP_ID: 'test_app_id',
INSTAGRAM_APP_SECRET: 'test_app_secret',
INSTAGRAM_POST_INTERVAL_MIN: 60,
INSTAGRAM_POST_INTERVAL_MAX: 120,
INSTAGRAM_ENABLE_ACTION_PROCESSING: false,
INSTAGRAM_ACTION_INTERVAL: 5,
INSTAGRAM_MAX_ACTIONS: 1,
};

vi.mocked(mockRuntime.getSetting).mockImplementation((key: string) => {
if (key === 'INSTAGRAM_DRY_RUN') return 'false';
if (key === 'INSTAGRAM_ENABLE_ACTION_PROCESSING') return 'false';
return validConfig[key as keyof typeof validConfig];
});

const config = await validateInstagramConfig(mockRuntime);
expect(config).toEqual(validConfig);
});

it('validates configuration with optional business account', async () => {
const validConfig = {
INSTAGRAM_DRY_RUN: false,
INSTAGRAM_USERNAME: 'test_user',
INSTAGRAM_PASSWORD: 'test_password',
INSTAGRAM_APP_ID: 'test_app_id',
INSTAGRAM_APP_SECRET: 'test_app_secret',
INSTAGRAM_BUSINESS_ACCOUNT_ID: 'business_123',
INSTAGRAM_POST_INTERVAL_MIN: 60,
INSTAGRAM_POST_INTERVAL_MAX: 120,
INSTAGRAM_ENABLE_ACTION_PROCESSING: false,
INSTAGRAM_ACTION_INTERVAL: 5,
INSTAGRAM_MAX_ACTIONS: 1,
};

vi.mocked(mockRuntime.getSetting).mockImplementation((key: string) => {
if (key === 'INSTAGRAM_DRY_RUN') return 'false';
if (key === 'INSTAGRAM_ENABLE_ACTION_PROCESSING') return 'false';
return validConfig[key as keyof typeof validConfig];
});

const config = await validateInstagramConfig(mockRuntime);
expect(config).toEqual(validConfig);
});

it('throws error for invalid username format', async () => {
const invalidConfig = {
INSTAGRAM_DRY_RUN: false,
INSTAGRAM_USERNAME: 'invalid@username', // Invalid characters
INSTAGRAM_PASSWORD: 'test_password',
INSTAGRAM_APP_ID: 'test_app_id',
INSTAGRAM_APP_SECRET: 'test_app_secret',
};

vi.mocked(mockRuntime.getSetting).mockImplementation((key: string) => {
if (key === 'INSTAGRAM_DRY_RUN') return 'false';
return invalidConfig[key as keyof typeof invalidConfig];
});

await expect(validateInstagramConfig(mockRuntime)).rejects.toThrow();
});

it('throws error for missing required fields', async () => {
const invalidConfig = {
INSTAGRAM_DRY_RUN: false,
INSTAGRAM_USERNAME: 'test_user',
// Missing password and other required fields
};

vi.mocked(mockRuntime.getSetting).mockImplementation((key: string) => {
if (key === 'INSTAGRAM_DRY_RUN') return 'false';
return invalidConfig[key as keyof typeof invalidConfig];
});

await expect(validateInstagramConfig(mockRuntime)).rejects.toThrow();
});
});
120 changes: 120 additions & 0 deletions packages/client-instagram/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { InstagramClientInterface } from '../src';
import { IAgentRuntime, elizaLogger } from '@elizaos/core';
import { InstagramInteractionService } from '../src/services/interaction';
import { InstagramPostService } from '../src/services/post';

// Mock dependencies
vi.mock('@elizaos/core', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
elizaLogger: {
log: vi.fn(),
error: vi.fn(),
},
parseBooleanFromText: (value: string | undefined) => value === 'true',
};
});

// Mock service instances
const mockPostService = {
start: vi.fn().mockResolvedValue(undefined),
};

const mockInteractionService = {
start: vi.fn().mockResolvedValue(undefined),
};

vi.mock('../src/lib/auth', () => ({
initializeClient: vi.fn().mockResolvedValue({
ig: {},
config: {
INSTAGRAM_DRY_RUN: false,
INSTAGRAM_ENABLE_ACTION_PROCESSING: true,
},
}),
}));

vi.mock('../src/services/post', () => ({
InstagramPostService: vi.fn().mockImplementation(() => mockPostService),
}));

vi.mock('../src/services/interaction', () => ({
InstagramInteractionService: vi.fn().mockImplementation(() => mockInteractionService),
}));

describe('InstagramClientInterface', () => {
let mockRuntime: IAgentRuntime;
const mockConfig = {
INSTAGRAM_DRY_RUN: false,
INSTAGRAM_USERNAME: 'test_user',
INSTAGRAM_PASSWORD: 'test_password',
INSTAGRAM_APP_ID: 'test_app_id',
INSTAGRAM_APP_SECRET: 'test_app_secret',
INSTAGRAM_POST_INTERVAL_MIN: 60,
INSTAGRAM_POST_INTERVAL_MAX: 120,
INSTAGRAM_ENABLE_ACTION_PROCESSING: true,
INSTAGRAM_ACTION_INTERVAL: 5,
INSTAGRAM_MAX_ACTIONS: 1,
};

beforeEach(() => {
vi.clearAllMocks();
mockRuntime = {
getSetting: vi.fn((key: string) => {
if (key === 'INSTAGRAM_DRY_RUN' || key === 'INSTAGRAM_ENABLE_ACTION_PROCESSING') {
return String(mockConfig[key as keyof typeof mockConfig]);
}
return mockConfig[key as keyof typeof mockConfig];
}),
} as unknown as IAgentRuntime;
});

it('starts successfully with all services', async () => {
const result = await InstagramClientInterface.start(mockRuntime);

expect(result).toBeDefined();
expect(result.post).toBeDefined();
expect(result.interaction).toBeDefined();
expect(InstagramPostService).toHaveBeenCalled();
expect(InstagramInteractionService).toHaveBeenCalled();
expect(result.post.start).toHaveBeenCalled();
expect(result.interaction.start).toHaveBeenCalled();
expect(elizaLogger.log).toHaveBeenCalledWith('Instagram client configuration validated');
expect(elizaLogger.log).toHaveBeenCalledWith('Instagram client initialized');
expect(elizaLogger.log).toHaveBeenCalledWith('Instagram post service started');
expect(elizaLogger.log).toHaveBeenCalledWith('Instagram interaction service started');
});

it('starts in dry-run mode', async () => {
const dryRunConfig = { ...mockConfig, INSTAGRAM_DRY_RUN: true };
mockRuntime.getSetting = vi.fn((key: string) => {
if (key === 'INSTAGRAM_DRY_RUN') return 'true';
if (key === 'INSTAGRAM_ENABLE_ACTION_PROCESSING') return String(dryRunConfig.INSTAGRAM_ENABLE_ACTION_PROCESSING);
return dryRunConfig[key as keyof typeof dryRunConfig];
});

const result = await InstagramClientInterface.start(mockRuntime);

expect(result).toBeDefined();
expect(elizaLogger.log).toHaveBeenCalledWith('Instagram client running in dry-run mode');
expect(mockPostService.start).not.toHaveBeenCalled();
expect(mockInteractionService.start).not.toHaveBeenCalled();
});

it('handles errors during startup', async () => {
const error = new Error('Startup failed');
vi.mocked(mockRuntime.getSetting).mockImplementation(() => {
throw error;
});

await expect(InstagramClientInterface.start(mockRuntime)).rejects.toThrow('Startup failed');
expect(elizaLogger.error).toHaveBeenCalledWith('Failed to start Instagram client:', error);
});

it('stops gracefully', async () => {
await InstagramClientInterface.stop(mockRuntime);
expect(elizaLogger.log).toHaveBeenCalledWith('Stopping Instagram client services...');
});
});
7 changes: 5 additions & 2 deletions packages/client-instagram/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
},
"devDependencies": {
"tsup": "8.3.5",
"@types/sharp": "^0.32.0"
"@types/sharp": "^0.32.0",
"vitest": "^1.2.1"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache ."
"lint": "eslint --fix --cache .",
"test": "vitest run",
"test:watch": "vitest"
}
}

0 comments on commit 5a85d7f

Please sign in to comment.