Skip to content

Commit

Permalink
Merge pull request elizaOS#862 from ai16z-demirix/main
Browse files Browse the repository at this point in the history
test: adding environment and knowledge tests
  • Loading branch information
jkbrooks authored Dec 6, 2024
2 parents fa6a03a + cc6383d commit 3f3f478
Show file tree
Hide file tree
Showing 2 changed files with 343 additions and 0 deletions.
182 changes: 182 additions & 0 deletions packages/core/src/tests/environment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { validateEnv, validateCharacterConfig } from '../environment';
import { Clients, ModelProviderName } from '../types';

describe('Environment Configuration', () => {
const originalEnv = process.env;

beforeEach(() => {
process.env = {
...originalEnv,
OPENAI_API_KEY: 'sk-test123',
REDPILL_API_KEY: 'test-key',
GROK_API_KEY: 'test-key',
GROQ_API_KEY: 'gsk_test123',
OPENROUTER_API_KEY: 'test-key',
GOOGLE_GENERATIVE_AI_API_KEY: 'test-key',
ELEVENLABS_XI_API_KEY: 'test-key',
};
});

afterEach(() => {
process.env = originalEnv;
});

it('should validate correct environment variables', () => {
expect(() => validateEnv()).not.toThrow();
});

it('should throw error for invalid OpenAI API key format', () => {
process.env.OPENAI_API_KEY = 'invalid-key';
expect(() => validateEnv()).toThrow("OpenAI API key must start with 'sk-'");
});

it('should throw error for invalid GROQ API key format', () => {
process.env.GROQ_API_KEY = 'invalid-key';
expect(() => validateEnv()).toThrow("GROQ API key must start with 'gsk_'");
});

it('should throw error for missing required keys', () => {
delete process.env.REDPILL_API_KEY;
expect(() => validateEnv()).toThrow('REDPILL_API_KEY: Required');
});

it('should throw error for multiple missing required keys', () => {
delete process.env.REDPILL_API_KEY;
delete process.env.GROK_API_KEY;
delete process.env.OPENROUTER_API_KEY;
expect(() => validateEnv()).toThrow(
'Environment validation failed:\n' +
'REDPILL_API_KEY: Required\n' +
'GROK_API_KEY: Required\n' +
'OPENROUTER_API_KEY: Required'
);
});
});

describe('Character Configuration', () => {
const validCharacterConfig = {
name: 'Test Character',
modelProvider: ModelProviderName.OPENAI,
bio: 'Test bio',
lore: ['Test lore'],
messageExamples: [[
{
user: 'user1',
content: {
text: 'Hello',
}
}
]],
postExamples: ['Test post'],
topics: ['topic1'],
adjectives: ['friendly'],
clients: [Clients.DISCORD],
plugins: ['test-plugin'],
style: {
all: ['style1'],
chat: ['chat-style'],
post: ['post-style']
}
};

it('should validate correct character configuration', () => {
expect(() => validateCharacterConfig(validCharacterConfig)).not.toThrow();
});

it('should validate configuration with optional fields', () => {
const configWithOptionals = {
...validCharacterConfig,
id: '123e4567-e89b-12d3-a456-426614174000',
system: 'Test system',
templates: {
greeting: 'Hello!'
},
knowledge: ['fact1'],
settings: {
secrets: {
key: 'value'
},
voice: {
model: 'test-model',
url: 'http://example.com'
}
}
};
expect(() => validateCharacterConfig(configWithOptionals)).not.toThrow();
});

it('should throw error for missing required fields', () => {
const invalidConfig = { ...validCharacterConfig };
delete (invalidConfig as any).name;
expect(() => validateCharacterConfig(invalidConfig)).toThrow();
});

it('should validate plugin objects in plugins array', () => {
const configWithPluginObjects = {
...validCharacterConfig,
plugins: [{
name: 'test-plugin',
description: 'Test description'
}]
};
expect(() => validateCharacterConfig(configWithPluginObjects)).not.toThrow();
});

it('should validate client-specific configurations', () => {
const configWithClientConfig = {
...validCharacterConfig,
clientConfig: {
discord: {
shouldIgnoreBotMessages: true,
shouldIgnoreDirectMessages: false
},
telegram: {
shouldIgnoreBotMessages: true,
shouldIgnoreDirectMessages: true
}
}
};
expect(() => validateCharacterConfig(configWithClientConfig)).not.toThrow();
});

it('should validate twitter profile configuration', () => {
const configWithTwitter = {
...validCharacterConfig,
twitterProfile: {
username: 'testuser',
screenName: 'Test User',
bio: 'Test bio',
nicknames: ['test']
}
};
expect(() => validateCharacterConfig(configWithTwitter)).not.toThrow();
});

it('should validate model endpoint override', () => {
const configWithEndpoint = {
...validCharacterConfig,
modelEndpointOverride: 'custom-endpoint'
};
expect(() => validateCharacterConfig(configWithEndpoint)).not.toThrow();
});

it('should validate message examples with additional properties', () => {
const configWithComplexMessage = {
...validCharacterConfig,
messageExamples: [[{
user: 'user1',
content: {
text: 'Hello',
action: 'wave',
source: 'chat',
url: 'http://example.com',
inReplyTo: '123e4567-e89b-12d3-a456-426614174000',
attachments: ['file1'],
customField: 'value'
}
}]]
};
expect(() => validateCharacterConfig(configWithComplexMessage)).not.toThrow();
});
});
161 changes: 161 additions & 0 deletions packages/core/src/tests/knowledge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import knowledge from '../knowledge';
import { AgentRuntime } from '../runtime';
import { KnowledgeItem, Memory } from '../types';
import { getEmbeddingZeroVector } from '../embedding';

// Mock dependencies
vi.mock('../embedding', () => ({
embed: vi.fn().mockResolvedValue(new Float32Array(1536).fill(0)),
getEmbeddingZeroVector: vi.fn().mockReturnValue(new Float32Array(1536).fill(0))
}));

vi.mock('../generation', () => ({
splitChunks: vi.fn().mockImplementation(async (text) => [text])
}));

vi.mock('../uuid', () => ({
stringToUuid: vi.fn().mockImplementation((str) => str)
}));

describe('Knowledge Module', () => {
describe('preprocess', () => {
it('should handle invalid inputs', () => {
expect(knowledge.preprocess(null)).toBe('');
expect(knowledge.preprocess(undefined)).toBe('');
expect(knowledge.preprocess('')).toBe('');
});

it('should remove code blocks and inline code', () => {
const input = 'Here is some code: ```const x = 1;``` and `inline code`';
expect(knowledge.preprocess(input)).toBe('here is some code: and');
});

it('should handle markdown formatting', () => {
const input = '# Header\n## Subheader\n[Link](http://example.com)\n![Image](image.jpg)';
expect(knowledge.preprocess(input)).toBe('header subheader link image');
});

it('should simplify URLs', () => {
const input = 'Visit https://www.example.com/path?param=value';
expect(knowledge.preprocess(input)).toBe('visit example.com/path?param=value');
});

it('should remove Discord mentions and HTML tags', () => {
const input = 'Hello <@123456789> and <div>HTML content</div>';
expect(knowledge.preprocess(input)).toBe('hello and html content');
});

it('should normalize whitespace and newlines', () => {
const input = 'Multiple spaces\n\n\nand\nnewlines';
expect(knowledge.preprocess(input)).toBe('multiple spaces and newlines');
});

it('should remove comments', () => {
const input = '/* Block comment */ Normal text // Line comment';
expect(knowledge.preprocess(input)).toBe('normal text');
});
});

describe('get and set', () => {
let mockRuntime: AgentRuntime;

beforeEach(() => {
mockRuntime = {
agentId: 'test-agent',
knowledgeManager: {
searchMemoriesByEmbedding: vi.fn().mockResolvedValue([
{
content: { text: 'test fragment', source: 'source1' },
similarity: 0.9
}
]),
createMemory: vi.fn().mockResolvedValue(undefined)
},
documentsManager: {
getMemoryById: vi.fn().mockResolvedValue({
id: 'source1',
content: { text: 'test document' }
}),
createMemory: vi.fn().mockResolvedValue(undefined)
}
} as unknown as AgentRuntime;
});

describe('get', () => {
it('should handle invalid messages', async () => {
const invalidMessage = {} as Memory;
const result = await knowledge.get(mockRuntime, invalidMessage);
expect(result).toEqual([]);
});

it('should retrieve knowledge items based on message content', async () => {
const message: Memory = {
agentId: 'test-agent',
content: { text: 'test query' }
} as unknown as Memory;

const result = await knowledge.get(mockRuntime, message);

expect(result).toHaveLength(1);
expect(result[0]).toEqual({
id: 'source1',
content: { text: 'test document' }
});
});

it('should handle empty processed text', async () => {
const message: Memory = {
agentId: 'test-agent',
content: { text: '```code only```' }
} as unknown as Memory;

const result = await knowledge.get(mockRuntime, message);
expect(result).toEqual([]);
});
});

describe('set', () => {
it('should store knowledge item and its fragments', async () => {
const item: KnowledgeItem = {
id: 'test-id-1234-5678-9101-112131415161',
content: { text: 'test content' }
};

await knowledge.set(mockRuntime, item);

// Check if document was created
expect(mockRuntime.documentsManager.createMemory).toHaveBeenCalledWith(
expect.objectContaining({
id: item.id,
content: item.content,
embedding: getEmbeddingZeroVector()
})
);

// Check if fragment was created
expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledWith(
expect.objectContaining({
content: {
source: item.id,
text: expect.any(String)
},
embedding: expect.any(Float32Array)
})
);
});

it('should use default chunk size and bleed', async () => {
const item: KnowledgeItem = {
id: 'test-id-1234-5678-9101-112131415161',
content: { text: 'test content' }
};

await knowledge.set(mockRuntime, item);

// Verify default parameters were used
expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledTimes(1);
});
});
});
});

0 comments on commit 3f3f478

Please sign in to comment.