Skip to content

Commit 6a9938c

Browse files
committed
tests: adding environment and knowledge tests
1 parent 0a03140 commit 6a9938c

File tree

2 files changed

+343
-0
lines changed

2 files changed

+343
-0
lines changed
+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2+
import { validateEnv, validateCharacterConfig } from '../environment';
3+
import { Clients, ModelProviderName } from '../types';
4+
5+
describe('Environment Configuration', () => {
6+
const originalEnv = process.env;
7+
8+
beforeEach(() => {
9+
process.env = {
10+
...originalEnv,
11+
OPENAI_API_KEY: 'sk-test123',
12+
REDPILL_API_KEY: 'test-key',
13+
GROK_API_KEY: 'test-key',
14+
GROQ_API_KEY: 'gsk_test123',
15+
OPENROUTER_API_KEY: 'test-key',
16+
GOOGLE_GENERATIVE_AI_API_KEY: 'test-key',
17+
ELEVENLABS_XI_API_KEY: 'test-key',
18+
};
19+
});
20+
21+
afterEach(() => {
22+
process.env = originalEnv;
23+
});
24+
25+
it('should validate correct environment variables', () => {
26+
expect(() => validateEnv()).not.toThrow();
27+
});
28+
29+
it('should throw error for invalid OpenAI API key format', () => {
30+
process.env.OPENAI_API_KEY = 'invalid-key';
31+
expect(() => validateEnv()).toThrow("OpenAI API key must start with 'sk-'");
32+
});
33+
34+
it('should throw error for invalid GROQ API key format', () => {
35+
process.env.GROQ_API_KEY = 'invalid-key';
36+
expect(() => validateEnv()).toThrow("GROQ API key must start with 'gsk_'");
37+
});
38+
39+
it('should throw error for missing required keys', () => {
40+
delete process.env.REDPILL_API_KEY;
41+
expect(() => validateEnv()).toThrow('REDPILL_API_KEY: Required');
42+
});
43+
44+
it('should throw error for multiple missing required keys', () => {
45+
delete process.env.REDPILL_API_KEY;
46+
delete process.env.GROK_API_KEY;
47+
delete process.env.OPENROUTER_API_KEY;
48+
expect(() => validateEnv()).toThrow(
49+
'Environment validation failed:\n' +
50+
'REDPILL_API_KEY: Required\n' +
51+
'GROK_API_KEY: Required\n' +
52+
'OPENROUTER_API_KEY: Required'
53+
);
54+
});
55+
});
56+
57+
describe('Character Configuration', () => {
58+
const validCharacterConfig = {
59+
name: 'Test Character',
60+
modelProvider: ModelProviderName.OPENAI,
61+
bio: 'Test bio',
62+
lore: ['Test lore'],
63+
messageExamples: [[
64+
{
65+
user: 'user1',
66+
content: {
67+
text: 'Hello',
68+
}
69+
}
70+
]],
71+
postExamples: ['Test post'],
72+
topics: ['topic1'],
73+
adjectives: ['friendly'],
74+
clients: [Clients.DISCORD],
75+
plugins: ['test-plugin'],
76+
style: {
77+
all: ['style1'],
78+
chat: ['chat-style'],
79+
post: ['post-style']
80+
}
81+
};
82+
83+
it('should validate correct character configuration', () => {
84+
expect(() => validateCharacterConfig(validCharacterConfig)).not.toThrow();
85+
});
86+
87+
it('should validate configuration with optional fields', () => {
88+
const configWithOptionals = {
89+
...validCharacterConfig,
90+
id: '123e4567-e89b-12d3-a456-426614174000',
91+
system: 'Test system',
92+
templates: {
93+
greeting: 'Hello!'
94+
},
95+
knowledge: ['fact1'],
96+
settings: {
97+
secrets: {
98+
key: 'value'
99+
},
100+
voice: {
101+
model: 'test-model',
102+
url: 'http://example.com'
103+
}
104+
}
105+
};
106+
expect(() => validateCharacterConfig(configWithOptionals)).not.toThrow();
107+
});
108+
109+
it('should throw error for missing required fields', () => {
110+
const invalidConfig = { ...validCharacterConfig };
111+
delete (invalidConfig as any).name;
112+
expect(() => validateCharacterConfig(invalidConfig)).toThrow();
113+
});
114+
115+
it('should validate plugin objects in plugins array', () => {
116+
const configWithPluginObjects = {
117+
...validCharacterConfig,
118+
plugins: [{
119+
name: 'test-plugin',
120+
description: 'Test description'
121+
}]
122+
};
123+
expect(() => validateCharacterConfig(configWithPluginObjects)).not.toThrow();
124+
});
125+
126+
it('should validate client-specific configurations', () => {
127+
const configWithClientConfig = {
128+
...validCharacterConfig,
129+
clientConfig: {
130+
discord: {
131+
shouldIgnoreBotMessages: true,
132+
shouldIgnoreDirectMessages: false
133+
},
134+
telegram: {
135+
shouldIgnoreBotMessages: true,
136+
shouldIgnoreDirectMessages: true
137+
}
138+
}
139+
};
140+
expect(() => validateCharacterConfig(configWithClientConfig)).not.toThrow();
141+
});
142+
143+
it('should validate twitter profile configuration', () => {
144+
const configWithTwitter = {
145+
...validCharacterConfig,
146+
twitterProfile: {
147+
username: 'testuser',
148+
screenName: 'Test User',
149+
bio: 'Test bio',
150+
nicknames: ['test']
151+
}
152+
};
153+
expect(() => validateCharacterConfig(configWithTwitter)).not.toThrow();
154+
});
155+
156+
it('should validate model endpoint override', () => {
157+
const configWithEndpoint = {
158+
...validCharacterConfig,
159+
modelEndpointOverride: 'custom-endpoint'
160+
};
161+
expect(() => validateCharacterConfig(configWithEndpoint)).not.toThrow();
162+
});
163+
164+
it('should validate message examples with additional properties', () => {
165+
const configWithComplexMessage = {
166+
...validCharacterConfig,
167+
messageExamples: [[{
168+
user: 'user1',
169+
content: {
170+
text: 'Hello',
171+
action: 'wave',
172+
source: 'chat',
173+
url: 'http://example.com',
174+
inReplyTo: '123e4567-e89b-12d3-a456-426614174000',
175+
attachments: ['file1'],
176+
customField: 'value'
177+
}
178+
}]]
179+
};
180+
expect(() => validateCharacterConfig(configWithComplexMessage)).not.toThrow();
181+
});
182+
});
+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import knowledge from '../knowledge';
3+
import { AgentRuntime } from '../runtime';
4+
import { KnowledgeItem, Memory } from '../types';
5+
import { getEmbeddingZeroVector } from '../embedding';
6+
7+
// Mock dependencies
8+
vi.mock('../embedding', () => ({
9+
embed: vi.fn().mockResolvedValue(new Float32Array(1536).fill(0)),
10+
getEmbeddingZeroVector: vi.fn().mockReturnValue(new Float32Array(1536).fill(0))
11+
}));
12+
13+
vi.mock('../generation', () => ({
14+
splitChunks: vi.fn().mockImplementation(async (text) => [text])
15+
}));
16+
17+
vi.mock('../uuid', () => ({
18+
stringToUuid: vi.fn().mockImplementation((str) => str)
19+
}));
20+
21+
describe('Knowledge Module', () => {
22+
describe('preprocess', () => {
23+
it('should handle invalid inputs', () => {
24+
expect(knowledge.preprocess(null)).toBe('');
25+
expect(knowledge.preprocess(undefined)).toBe('');
26+
expect(knowledge.preprocess('')).toBe('');
27+
});
28+
29+
it('should remove code blocks and inline code', () => {
30+
const input = 'Here is some code: ```const x = 1;``` and `inline code`';
31+
expect(knowledge.preprocess(input)).toBe('here is some code: and');
32+
});
33+
34+
it('should handle markdown formatting', () => {
35+
const input = '# Header\n## Subheader\n[Link](http://example.com)\n![Image](image.jpg)';
36+
expect(knowledge.preprocess(input)).toBe('header subheader link image');
37+
});
38+
39+
it('should simplify URLs', () => {
40+
const input = 'Visit https://www.example.com/path?param=value';
41+
expect(knowledge.preprocess(input)).toBe('visit example.com/path?param=value');
42+
});
43+
44+
it('should remove Discord mentions and HTML tags', () => {
45+
const input = 'Hello <@123456789> and <div>HTML content</div>';
46+
expect(knowledge.preprocess(input)).toBe('hello and html content');
47+
});
48+
49+
it('should normalize whitespace and newlines', () => {
50+
const input = 'Multiple spaces\n\n\nand\nnewlines';
51+
expect(knowledge.preprocess(input)).toBe('multiple spaces and newlines');
52+
});
53+
54+
it('should remove comments', () => {
55+
const input = '/* Block comment */ Normal text // Line comment';
56+
expect(knowledge.preprocess(input)).toBe('normal text');
57+
});
58+
});
59+
60+
describe('get and set', () => {
61+
let mockRuntime: AgentRuntime;
62+
63+
beforeEach(() => {
64+
mockRuntime = {
65+
agentId: 'test-agent',
66+
knowledgeManager: {
67+
searchMemoriesByEmbedding: vi.fn().mockResolvedValue([
68+
{
69+
content: { text: 'test fragment', source: 'source1' },
70+
similarity: 0.9
71+
}
72+
]),
73+
createMemory: vi.fn().mockResolvedValue(undefined)
74+
},
75+
documentsManager: {
76+
getMemoryById: vi.fn().mockResolvedValue({
77+
id: 'source1',
78+
content: { text: 'test document' }
79+
}),
80+
createMemory: vi.fn().mockResolvedValue(undefined)
81+
}
82+
} as unknown as AgentRuntime;
83+
});
84+
85+
describe('get', () => {
86+
it('should handle invalid messages', async () => {
87+
const invalidMessage = {} as Memory;
88+
const result = await knowledge.get(mockRuntime, invalidMessage);
89+
expect(result).toEqual([]);
90+
});
91+
92+
it('should retrieve knowledge items based on message content', async () => {
93+
const message: Memory = {
94+
agentId: 'test-agent',
95+
content: { text: 'test query' }
96+
} as unknown as Memory;
97+
98+
const result = await knowledge.get(mockRuntime, message);
99+
100+
expect(result).toHaveLength(1);
101+
expect(result[0]).toEqual({
102+
id: 'source1',
103+
content: { text: 'test document' }
104+
});
105+
});
106+
107+
it('should handle empty processed text', async () => {
108+
const message: Memory = {
109+
agentId: 'test-agent',
110+
content: { text: '```code only```' }
111+
} as unknown as Memory;
112+
113+
const result = await knowledge.get(mockRuntime, message);
114+
expect(result).toEqual([]);
115+
});
116+
});
117+
118+
describe('set', () => {
119+
it('should store knowledge item and its fragments', async () => {
120+
const item: KnowledgeItem = {
121+
id: 'test-id-1234-5678-9101-112131415161',
122+
content: { text: 'test content' }
123+
};
124+
125+
await knowledge.set(mockRuntime, item);
126+
127+
// Check if document was created
128+
expect(mockRuntime.documentsManager.createMemory).toHaveBeenCalledWith(
129+
expect.objectContaining({
130+
id: item.id,
131+
content: item.content,
132+
embedding: getEmbeddingZeroVector()
133+
})
134+
);
135+
136+
// Check if fragment was created
137+
expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledWith(
138+
expect.objectContaining({
139+
content: {
140+
source: item.id,
141+
text: expect.any(String)
142+
},
143+
embedding: expect.any(Float32Array)
144+
})
145+
);
146+
});
147+
148+
it('should use default chunk size and bleed', async () => {
149+
const item: KnowledgeItem = {
150+
id: 'test-id-1234-5678-9101-112131415161',
151+
content: { text: 'test content' }
152+
};
153+
154+
await knowledge.set(mockRuntime, item);
155+
156+
// Verify default parameters were used
157+
expect(mockRuntime.knowledgeManager.createMemory).toHaveBeenCalledTimes(1);
158+
});
159+
});
160+
});
161+
});

0 commit comments

Comments
 (0)