Initial commit

This commit is contained in:
root
2026-01-26 08:34:48 +01:00
commit 387314581f
216 changed files with 81204 additions and 0 deletions
+464
View File
@@ -0,0 +1,464 @@
/**
* Unit tests for Personality Configuration Module
*
* Tests validation, serialization, and prompt building functionality.
*/
import { describe, it, expect } from 'vitest';
import {
validateSystemPrompt,
validateTemperature,
validateMaxTokens,
validateResponseStyle,
validatePersonalityConfig,
isValidPersonalityConfig,
serializePersonalityConfig,
deserializePersonalityConfig,
buildPromptWithPersonality,
getPersonalityPreset,
getAllPersonalityPresets,
createDefaultPersonalityConfig,
PersonalityConfig,
PersonalityError,
MIN_SYSTEM_PROMPT_LENGTH,
MAX_SYSTEM_PROMPT_LENGTH,
MIN_TEMPERATURE,
MAX_TEMPERATURE,
MIN_MAX_TOKENS,
MAX_MAX_TOKENS,
} from './personality';
describe('Personality Configuration Module', () => {
// ============================================
// SYSTEM PROMPT VALIDATION
// ============================================
describe('validateSystemPrompt', () => {
it('should accept valid system prompts', () => {
const errors = validateSystemPrompt('You are a helpful assistant bot.');
expect(errors).toHaveLength(0);
});
it('should reject undefined system prompt', () => {
const errors = validateSystemPrompt(undefined);
expect(errors).toContain('System prompt is required');
});
it('should reject null system prompt', () => {
const errors = validateSystemPrompt(null);
expect(errors).toContain('System prompt is required');
});
it('should reject non-string system prompt', () => {
const errors = validateSystemPrompt(123);
expect(errors).toContain('System prompt must be a string');
});
it('should reject system prompt that is too short', () => {
const errors = validateSystemPrompt('Hi');
expect(errors).toContain(`System prompt must be at least ${MIN_SYSTEM_PROMPT_LENGTH} characters`);
});
it('should reject system prompt that is too long', () => {
const longPrompt = 'a'.repeat(MAX_SYSTEM_PROMPT_LENGTH + 1);
const errors = validateSystemPrompt(longPrompt);
expect(errors).toContain(`System prompt must be ${MAX_SYSTEM_PROMPT_LENGTH} characters or less`);
});
it('should accept system prompt at minimum length', () => {
const minPrompt = 'a'.repeat(MIN_SYSTEM_PROMPT_LENGTH);
const errors = validateSystemPrompt(minPrompt);
expect(errors).toHaveLength(0);
});
it('should accept system prompt at maximum length', () => {
const maxPrompt = 'a'.repeat(MAX_SYSTEM_PROMPT_LENGTH);
const errors = validateSystemPrompt(maxPrompt);
expect(errors).toHaveLength(0);
});
});
// ============================================
// TEMPERATURE VALIDATION
// ============================================
describe('validateTemperature', () => {
it('should accept valid temperature values', () => {
expect(validateTemperature(0)).toHaveLength(0);
expect(validateTemperature(0.5)).toHaveLength(0);
expect(validateTemperature(1)).toHaveLength(0);
expect(validateTemperature(1.5)).toHaveLength(0);
expect(validateTemperature(2)).toHaveLength(0);
});
it('should reject undefined temperature', () => {
const errors = validateTemperature(undefined);
expect(errors).toContain('Temperature is required');
});
it('should reject null temperature', () => {
const errors = validateTemperature(null);
expect(errors).toContain('Temperature is required');
});
it('should reject non-number temperature', () => {
const errors = validateTemperature('0.5');
expect(errors).toContain('Temperature must be a number');
});
it('should reject NaN temperature', () => {
const errors = validateTemperature(NaN);
expect(errors).toContain('Temperature must be a valid number');
});
it('should reject temperature below minimum', () => {
const errors = validateTemperature(-0.1);
expect(errors).toContain(`Temperature must be at least ${MIN_TEMPERATURE}`);
});
it('should reject temperature above maximum', () => {
const errors = validateTemperature(2.1);
expect(errors).toContain(`Temperature must be at most ${MAX_TEMPERATURE}`);
});
});
// ============================================
// MAX TOKENS VALIDATION
// ============================================
describe('validateMaxTokens', () => {
it('should accept valid maxTokens values', () => {
expect(validateMaxTokens(1)).toHaveLength(0);
expect(validateMaxTokens(100)).toHaveLength(0);
expect(validateMaxTokens(1000)).toHaveLength(0);
expect(validateMaxTokens(100000)).toHaveLength(0);
});
it('should reject undefined maxTokens', () => {
const errors = validateMaxTokens(undefined);
expect(errors).toContain('Max tokens is required');
});
it('should reject null maxTokens', () => {
const errors = validateMaxTokens(null);
expect(errors).toContain('Max tokens is required');
});
it('should reject non-number maxTokens', () => {
const errors = validateMaxTokens('100');
expect(errors).toContain('Max tokens must be a number');
});
it('should reject NaN maxTokens', () => {
const errors = validateMaxTokens(NaN);
expect(errors).toContain('Max tokens must be a valid number');
});
it('should reject non-integer maxTokens', () => {
const errors = validateMaxTokens(100.5);
expect(errors).toContain('Max tokens must be an integer');
});
it('should reject maxTokens below minimum', () => {
const errors = validateMaxTokens(0);
expect(errors).toContain(`Max tokens must be at least ${MIN_MAX_TOKENS}`);
});
it('should reject maxTokens above maximum', () => {
const errors = validateMaxTokens(100001);
expect(errors).toContain(`Max tokens must be at most ${MAX_MAX_TOKENS}`);
});
});
// ============================================
// RESPONSE STYLE VALIDATION
// ============================================
describe('validateResponseStyle', () => {
it('should accept undefined response style (optional)', () => {
const errors = validateResponseStyle(undefined);
expect(errors).toHaveLength(0);
});
it('should accept null response style (optional)', () => {
const errors = validateResponseStyle(null);
expect(errors).toHaveLength(0);
});
it('should accept valid response styles', () => {
expect(validateResponseStyle('formal')).toHaveLength(0);
expect(validateResponseStyle('casual')).toHaveLength(0);
expect(validateResponseStyle('custom style')).toHaveLength(0);
});
it('should reject non-string response style', () => {
const errors = validateResponseStyle(123);
expect(errors).toContain('Response style must be a string');
});
it('should reject empty response style', () => {
const errors = validateResponseStyle('');
expect(errors).toContain('Response style cannot be empty if provided');
});
it('should reject whitespace-only response style', () => {
const errors = validateResponseStyle(' ');
expect(errors).toContain('Response style cannot be empty if provided');
});
it('should reject response style that is too long', () => {
const longStyle = 'a'.repeat(101);
const errors = validateResponseStyle(longStyle);
expect(errors).toContain('Response style must be 100 characters or less');
});
});
// ============================================
// COMPLETE CONFIG VALIDATION
// ============================================
describe('validatePersonalityConfig', () => {
const validConfig: PersonalityConfig = {
systemPrompt: 'You are a helpful assistant bot.',
temperature: 0.7,
maxTokens: 500,
};
it('should accept valid configuration', () => {
const result = validatePersonalityConfig(validConfig);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should accept valid configuration with response style', () => {
const result = validatePersonalityConfig({
...validConfig,
responseStyle: 'formal',
});
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject non-object configuration', () => {
const result = validatePersonalityConfig('not an object');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Personality configuration must be an object');
});
it('should reject null configuration', () => {
const result = validatePersonalityConfig(null);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Personality configuration must be an object');
});
it('should collect all validation errors', () => {
const result = validatePersonalityConfig({
systemPrompt: 'Hi', // too short
temperature: 3, // too high
maxTokens: 0, // too low
responseStyle: '', // empty
});
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(1);
});
});
describe('isValidPersonalityConfig', () => {
it('should return true for valid config', () => {
const config: PersonalityConfig = {
systemPrompt: 'You are a helpful assistant bot.',
temperature: 0.7,
maxTokens: 500,
};
expect(isValidPersonalityConfig(config)).toBe(true);
});
it('should return false for invalid config', () => {
expect(isValidPersonalityConfig(null)).toBe(false);
expect(isValidPersonalityConfig({ systemPrompt: 'Hi' })).toBe(false);
});
});
// ============================================
// SERIALIZATION
// ============================================
describe('serializePersonalityConfig', () => {
it('should serialize config to JSON string', () => {
const config: PersonalityConfig = {
systemPrompt: 'You are a helpful assistant bot.',
temperature: 0.7,
maxTokens: 500,
};
const serialized = serializePersonalityConfig(config);
expect(typeof serialized).toBe('string');
expect(JSON.parse(serialized)).toEqual(config);
});
it('should preserve all fields including optional ones', () => {
const config: PersonalityConfig = {
systemPrompt: 'You are a helpful assistant bot.',
temperature: 0.7,
maxTokens: 500,
responseStyle: 'formal',
};
const serialized = serializePersonalityConfig(config);
const parsed = JSON.parse(serialized);
expect(parsed.responseStyle).toBe('formal');
});
});
describe('deserializePersonalityConfig', () => {
it('should deserialize valid JSON to config', () => {
const config: PersonalityConfig = {
systemPrompt: 'You are a helpful assistant bot.',
temperature: 0.7,
maxTokens: 500,
};
const json = JSON.stringify(config);
const deserialized = deserializePersonalityConfig(json);
expect(deserialized).toEqual(config);
});
it('should throw PersonalityError for invalid JSON', () => {
expect(() => deserializePersonalityConfig('not json')).toThrow(PersonalityError);
});
it('should throw PersonalityError for invalid config', () => {
const invalidJson = JSON.stringify({ systemPrompt: 'Hi' });
expect(() => deserializePersonalityConfig(invalidJson)).toThrow(PersonalityError);
});
});
// ============================================
// PROMPT BUILDING
// ============================================
describe('buildPromptWithPersonality', () => {
const personality: PersonalityConfig = {
systemPrompt: 'You are a helpful assistant bot.',
temperature: 0.7,
maxTokens: 500,
};
it('should build basic prompt with personality', () => {
const result = buildPromptWithPersonality(personality);
expect(result.systemMessage).toContain('You are a helpful assistant bot.');
expect(result.temperature).toBe(0.7);
expect(result.maxTokens).toBe(500);
expect(result.messages).toHaveLength(1);
expect(result.messages[0].role).toBe('system');
});
it('should include response style in system message', () => {
const personalityWithStyle: PersonalityConfig = {
...personality,
responseStyle: 'formal',
};
const result = buildPromptWithPersonality(personalityWithStyle);
expect(result.systemMessage).toContain('Response Style: formal');
});
it('should include context in system message', () => {
const result = buildPromptWithPersonality(personality, {
context: 'This is additional context.',
});
expect(result.systemMessage).toContain('Additional Context:');
expect(result.systemMessage).toContain('This is additional context.');
});
it('should include user message', () => {
const result = buildPromptWithPersonality(personality, {
userMessage: 'Hello, how are you?',
});
expect(result.messages).toHaveLength(2);
expect(result.messages[1].role).toBe('user');
expect(result.messages[1].content).toBe('Hello, how are you?');
});
it('should include source content', () => {
const result = buildPromptWithPersonality(personality, {
sourceContent: {
title: 'Test Article',
content: 'Article content here.',
url: 'https://example.com/article',
},
});
expect(result.messages).toHaveLength(2);
expect(result.messages[1].role).toBe('user');
expect(result.messages[1].content).toContain('Test Article');
expect(result.messages[1].content).toContain('Article content here.');
expect(result.messages[1].content).toContain('https://example.com/article');
});
it('should include conversation history', () => {
const result = buildPromptWithPersonality(personality, {
conversationHistory: [
{ role: 'user', content: 'Hello' },
{ role: 'assistant', content: 'Hi there!' },
],
userMessage: 'How are you?',
});
expect(result.messages).toHaveLength(4);
expect(result.messages[1].role).toBe('user');
expect(result.messages[1].content).toBe('Hello');
expect(result.messages[2].role).toBe('assistant');
expect(result.messages[2].content).toBe('Hi there!');
expect(result.messages[3].role).toBe('user');
expect(result.messages[3].content).toBe('How are you?');
});
});
// ============================================
// PRESETS
// ============================================
describe('getPersonalityPreset', () => {
it('should return preset by ID', () => {
const preset = getPersonalityPreset('news-curator');
expect(preset).toBeDefined();
expect(preset?.name).toBe('News Curator');
});
it('should return undefined for unknown preset', () => {
const preset = getPersonalityPreset('unknown-preset');
expect(preset).toBeUndefined();
});
});
describe('getAllPersonalityPresets', () => {
it('should return all presets', () => {
const presets = getAllPersonalityPresets();
expect(presets.length).toBeGreaterThan(0);
expect(presets.every(p => p.id && p.name && p.config)).toBe(true);
});
it('should return a copy of presets array', () => {
const presets1 = getAllPersonalityPresets();
const presets2 = getAllPersonalityPresets();
expect(presets1).not.toBe(presets2);
});
it('should have valid configs for all presets', () => {
const presets = getAllPersonalityPresets();
for (const preset of presets) {
expect(isValidPersonalityConfig(preset.config)).toBe(true);
}
});
});
describe('createDefaultPersonalityConfig', () => {
it('should create a valid default config', () => {
const config = createDefaultPersonalityConfig();
expect(isValidPersonalityConfig(config)).toBe(true);
});
it('should have reasonable default values', () => {
const config = createDefaultPersonalityConfig();
expect(config.systemPrompt.length).toBeGreaterThan(0);
expect(config.temperature).toBeGreaterThanOrEqual(0);
expect(config.temperature).toBeLessThanOrEqual(2);
expect(config.maxTokens).toBeGreaterThan(0);
});
});
});