diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..cb39ae17 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,26 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + collectCoverage: true, + coverageReporters: ['text', 'lcov', 'json-summary'], + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80 + } + }, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/*.interface.ts', + '!src/**/*.test.ts' + ] +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..461140a1 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "multi-agent-chat-platform", + "version": "0.1.0", + "description": "Multi-agent interactive chat platform", + "scripts": { + "test": "jest", + "test:coverage": "jest --coverage" + }, + "devDependencies": { + "@types/jest": "^27.5.0", + "jest": "^27.5.1", + "ts-jest": "^27.1.4", + "typescript": "^4.6.4" + } +} \ No newline at end of file diff --git a/src/docs/component-interface-spec.md b/src/docs/component-interface-spec.md new file mode 100644 index 00000000..ced5da80 --- /dev/null +++ b/src/docs/component-interface-spec.md @@ -0,0 +1,123 @@ +# Multi-Agent Chat Platform: Component Interface Specification + +## Overview +This document provides a comprehensive specification for component interfaces, public methods, and testing strategies. + +## 1. Personality Data Manager Interface + +### Public Methods +- `loadProfile(id: string)`: Retrieve a personality profile +- `saveProfile(profile: PersonalityProfile)`: Save or update a profile +- `validateProfile(profile: PersonalityProfile)`: Validate profile schema +- `listProfiles()`: Retrieve all available profiles +- `deleteProfile(id: string)`: Remove a profile + +### Input/Output JSON Schemas + +#### Profile Load Request Schema +```json +{ + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the personality profile" + } + }, + "required": ["id"] +} +``` + +#### Profile Load Response Schema +```json +{ + "type": "object", + "properties": { + "id": {"type": "string"}, + "name": {"type": "string"}, + "tone": {"type": "string"}, + "samplePrompts": { + "type": "array", + "items": {"type": "string"} + }, + "metadata": {"type": "object"} + }, + "required": ["id", "name", "tone"] +} +``` + +### Error Handling Scenarios +1. Profile Not Found + - Return `null` or throw `ProfileNotFoundException` +2. Invalid Profile Schema + - Return detailed validation errors +3. Duplicate Profile Creation + - Prevent duplicate entries, return error + +### Unit Test Scenarios +1. Profile Loading + - Successfully load existing profile + - Handle non-existent profile + - Verify returned profile matches schema + +2. Profile Validation + - Validate complete profile + - Reject profiles with missing required fields + - Check metadata optional field handling + +3. Profile Saving + - Save new profile successfully + - Update existing profile + - Prevent duplicate profile creation + +4. Error Scenarios + - Handle invalid input types + - Test boundary conditions + - Validate error message specificity + +## 2. Chatbot Engine Adapter Interface + +### Public Methods +- `generateResponse(profile: PersonalityProfile, context: ConversationContext)`: Generate agent response +- `estimateTokenUsage(prompt: string)`: Calculate token consumption +- `validatePrompt(prompt: string)`: Ensure prompt meets requirements + +### Test Coverage Goals +- Happy path: 100% coverage +- Error paths: Comprehensive scenarios +- Edge cases: Unexpected inputs + +## 3. Conversation Orchestrator Interface + +### Public Methods +- `initializeConversation(agents: string[])`: Start multi-agent dialogue +- `processUserMessage(message: string)`: Route and generate responses +- `getConversationHistory(sessionId: string)`: Retrieve dialogue context + +### Key Testing Focus Areas +- Message routing logic +- Agent interaction sequences +- State management +- Error resilience + +## Testing Coverage Recommendations + +### Quantitative Coverage Goals +- Unit Tests: ≥80% code coverage +- Integration Tests: Key interaction paths +- Error Handling: 90% scenario coverage + +### Coverage Measurement +- Line Coverage: ≥80% +- Branch Coverage: ≥75% +- Function Coverage: ≥80% + +### Measurement Tools +- Jest for JavaScript/TypeScript +- Istanbul for code coverage reporting +- Continuous Integration hooks + +### Reporting Requirements +- Detailed coverage reports +- Visualization of untested code paths +- Recommendations for additional test scenarios diff --git a/src/interfaces/personality-data-manager.interface.ts b/src/interfaces/personality-data-manager.interface.ts new file mode 100644 index 00000000..dbdf3325 --- /dev/null +++ b/src/interfaces/personality-data-manager.interface.ts @@ -0,0 +1,109 @@ +/** + * Custom error classes for more specific error handling + */ +export class ProfileNotFoundException extends Error { + constructor(message: string) { + super(message); + this.name = 'ProfileNotFoundException'; + } +} + +export class InvalidProfileException extends Error { + public validationErrors: ValidationError[]; + + constructor(message: string, errors: ValidationError[]) { + super(message); + this.name = 'InvalidProfileException'; + this.validationErrors = errors; + } +} + +/** + * Comprehensive interface for Personality Data Manager + */ +export interface PersonalityDataManagerInterface { + /** + * Load a personality profile by its unique identifier + * @param id - Unique identifier for the personality profile + * @throws {ProfileNotFoundException} If profile with given ID does not exist + */ + loadProfile(id: string): Promise; + + /** + * List all available personality profiles + * @returns Array of profile metadata + */ + listProfiles(): Promise; + + /** + * Validate a personality profile's schema + * @param profile - Personality profile to validate + * @throws {InvalidProfileException} If profile fails validation + */ + validateProfile(profile: PersonalityProfile): void; + + /** + * Save a new or update an existing personality profile + * @param profile - Personality profile to save + * @returns Unique identifier of the saved profile + * @throws {InvalidProfileException} If profile is invalid + */ + saveProfile(profile: PersonalityProfile): Promise; + + /** + * Delete a personality profile + * @param id - Unique identifier of profile to delete + * @throws {ProfileNotFoundException} If profile does not exist + */ + deleteProfile(id: string): Promise; +} + +/** + * Comprehensive Personality Profile definition + */ +export interface PersonalityProfile { + id?: string; + name: string; + tone: string; + samplePrompts: string[]; + description?: string; + traits?: Record; + metadata?: Record; +} + +/** + * Metadata for profile listing and management + */ +export interface PersonalityProfileMetadata { + id: string; + name: string; + createdAt: Date; + updatedAt: Date; +} + +/** + * Detailed validation error structure + */ +export interface ValidationError { + field: string; + code: string; + message: string; +} + +/** + * Validation rules and constraints + */ +export const PersonalityProfileValidationRules = { + name: { + minLength: 2, + maxLength: 100 + }, + tone: { + allowedValues: ['Philosophical', 'Humorous', 'Serious', 'Empathetic'] + }, + samplePrompts: { + minPrompts: 1, + maxPrompts: 10, + maxPromptLength: 200 + } +}; \ No newline at end of file diff --git a/src/tests/personality-data-manager.test.ts b/src/tests/personality-data-manager.test.ts new file mode 100644 index 00000000..1b491838 --- /dev/null +++ b/src/tests/personality-data-manager.test.ts @@ -0,0 +1,148 @@ +import { + PersonalityDataManagerInterface, + PersonalityProfile, + ProfileNotFoundException, + InvalidProfileException, + PersonalityProfileValidationRules +} from '../interfaces/personality-data-manager.interface'; + +class MockPersonalityDataManager implements PersonalityDataManagerInterface { + private profiles: Map = new Map(); + + async loadProfile(id: string): Promise { + const profile = this.profiles.get(id); + if (!profile) { + throw new ProfileNotFoundException(`Profile with ID ${id} not found`); + } + return profile; + } + + async listProfiles(): Promise<{ id: string; name: string; createdAt: Date; updatedAt: Date }[]> { + return Array.from(this.profiles.entries()).map(([id, profile]) => ({ + id, + name: profile.name, + createdAt: new Date(), + updatedAt: new Date() + })); + } + + validateProfile(profile: PersonalityProfile): void { + const errors = []; + + // Name validation + if (profile.name.length < PersonalityProfileValidationRules.name.minLength || + profile.name.length > PersonalityProfileValidationRules.name.maxLength) { + errors.push({ + field: 'name', + code: 'LENGTH_INVALID', + message: 'Name must be between 2 and 100 characters' + }); + } + + // Tone validation + if (!PersonalityProfileValidationRules.tone.allowedValues.includes(profile.tone)) { + errors.push({ + field: 'tone', + code: 'INVALID_TONE', + message: 'Tone must be one of: Philosophical, Humorous, Serious, Empathetic' + }); + } + + // Sample prompts validation + if (profile.samplePrompts.length < PersonalityProfileValidationRules.samplePrompts.minPrompts || + profile.samplePrompts.length > PersonalityProfileValidationRules.samplePrompts.maxPrompts) { + errors.push({ + field: 'samplePrompts', + code: 'PROMPT_COUNT_INVALID', + message: 'Must have between 1 and 10 sample prompts' + }); + } + + const invalidPrompts = profile.samplePrompts.filter( + prompt => prompt.length > PersonalityProfileValidationRules.samplePrompts.maxPromptLength + ); + if (invalidPrompts.length > 0) { + errors.push({ + field: 'samplePrompts', + code: 'PROMPT_LENGTH_EXCEEDED', + message: 'Some prompts exceed 200 characters' + }); + } + + if (errors.length > 0) { + throw new InvalidProfileException('Profile validation failed', errors); + } + } + + async saveProfile(profile: PersonalityProfile): Promise { + this.validateProfile(profile); + const id = profile.id || Date.now().toString(); + this.profiles.set(id, { ...profile, id }); + return id; + } + + async deleteProfile(id: string): Promise { + if (!this.profiles.has(id)) { + throw new ProfileNotFoundException(`Profile with ID ${id} not found`); + } + this.profiles.delete(id); + } +} + +describe('PersonalityDataManager', () => { + let dataManager: MockPersonalityDataManager; + + beforeEach(() => { + dataManager = new MockPersonalityDataManager(); + }); + + // Positive Test Scenarios + describe('Profile Creation and Loading', () => { + it('should successfully create and load a valid profile', async () => { + const profile: PersonalityProfile = { + name: 'Test Disciple', + tone: 'Philosophical', + samplePrompts: ['What is wisdom?'] + }; + + const savedId = await dataManager.saveProfile(profile); + const loadedProfile = await dataManager.loadProfile(savedId); + + expect(loadedProfile).toEqual(expect.objectContaining(profile)); + }); + }); + + // Validation Test Scenarios + describe('Profile Validation', () => { + it('should reject profile with invalid name length', async () => { + const invalidProfile: PersonalityProfile = { + name: 'A', // Too short + tone: 'Philosophical', + samplePrompts: ['Prompt'] + }; + + await expect(dataManager.saveProfile(invalidProfile)).rejects.toThrow(InvalidProfileException); + }); + + it('should reject profile with invalid tone', async () => { + const invalidProfile: PersonalityProfile = { + name: 'Valid Name', + tone: 'InvalidTone', // Not in allowed values + samplePrompts: ['Prompt'] + }; + + await expect(dataManager.saveProfile(invalidProfile)).rejects.toThrow(InvalidProfileException); + }); + }); + + // Error Handling Test Scenarios + describe('Error Handling', () => { + it('should throw ProfileNotFoundException when loading non-existent profile', async () => { + await expect(dataManager.loadProfile('non-existent-id')).rejects.toThrow(ProfileNotFoundException); + }); + + it('should throw ProfileNotFoundException when deleting non-existent profile', async () => { + await expect(dataManager.deleteProfile('non-existent-id')).rejects.toThrow(ProfileNotFoundException); + }); + }); +}); \ No newline at end of file diff --git a/src/tests/testing-strategy.md b/src/tests/testing-strategy.md new file mode 100644 index 00000000..ab443154 --- /dev/null +++ b/src/tests/testing-strategy.md @@ -0,0 +1,88 @@ +# Comprehensive Testing Strategy for Multi-Agent Chat Platform + +## Testing Philosophy +Our testing approach focuses on: +- Thorough coverage +- Realistic scenarios +- Robust error handling +- Performance and reliability + +## Coverage Goals +- Unit Test Coverage: ≥80% +- Integration Test Coverage: ≥75% +- Error Path Coverage: ≥90% + +## Testing Dimensions + +### 1. Unit Testing +- Validate individual component methods +- Test all public interface methods +- Cover happy paths and error scenarios +- Use comprehensive input validation + +### 2. Error Handling Tests +- Simulate unexpected inputs +- Test boundary conditions +- Verify precise error messages +- Ensure no unhandled exceptions + +### 3. Performance Considerations +- Measure method execution times +- Test with large input datasets +- Verify memory efficiency + +## Specific Component Testing Approaches + +### Personality Data Manager +1. Profile Creation Tests + - Valid profile creation + - Duplicate prevention + - Metadata preservation + +2. Validation Tests + - Name length constraints + - Tone value restrictions + - Sample prompt rules + +3. Error Scenario Tests + - Non-existent profile retrieval + - Invalid profile rejection + - Deletion error handling + +### Chatbot Engine Adapter +1. Response Generation + - Prompt templating + - Context preservation + - Multi-agent coordination + +2. Token Management + - Usage estimation + - Limit enforcement + - Efficiency tracking + +### Conversation Orchestrator +1. Dialogue Management + - Multi-agent message routing + - State preservation + - Interaction sequencing + +2. Complex Interaction Scenarios + - Overlapping agent responses + - Conflict resolution + - Contextual understanding + +## Reporting and Monitoring +- Detailed coverage reports +- Performance metrics +- Trend analysis of test results + +## Tools and Frameworks +- Jest: Primary testing framework +- Istanbul: Code coverage +- TypeScript: Type safety +- Sinon: Mocking and stubbing + +## Continuous Improvement +- Regular review of test suites +- Periodic complexity analysis +- Adapt to emerging requirements diff --git a/src/utils/json-schema-validator.ts b/src/utils/json-schema-validator.ts new file mode 100644 index 00000000..3e14a2a9 --- /dev/null +++ b/src/utils/json-schema-validator.ts @@ -0,0 +1,96 @@ +import Ajv, { JSONSchemaType } from 'ajv'; +import addFormats from 'ajv-formats'; + +/** + * Advanced JSON Schema Validator + * Provides robust validation with detailed error reporting + */ +export class JSONSchemaValidator { + private ajv: Ajv; + + constructor() { + this.ajv = new Ajv({ + allErrors: true, + strict: true + }); + addFormats(this.ajv); + } + + /** + * Validate data against a JSON schema + * @param schema - JSON schema definition + * @param data - Data to validate + * @returns Validation result with detailed errors + */ + validate(schema: JSONSchemaType, data: unknown): ValidatorResult { + const validate = this.ajv.compile(schema); + const isValid = validate(data); + + return { + isValid, + errors: isValid ? [] : validate.errors?.map(this.formatError) || [], + data: isValid ? data as T : undefined + }; + } + + /** + * Format validation error for human-readable output + */ + private formatError(error: Ajv.ErrorObject) { + return { + path: error.instancePath, + message: error.message || 'Validation failed', + code: error.keyword, + params: error.params + }; + } + + /** + * Predefined schemas for common types + */ + static schemas = { + /** + * Personality Profile Schema + */ + personalityProfile: { + type: 'object', + properties: { + id: { type: 'string', minLength: 1, maxLength: 100 }, + name: { type: 'string', minLength: 2, maxLength: 100 }, + tone: { + type: 'string', + enum: ['Philosophical', 'Humorous', 'Serious', 'Empathetic'] + }, + samplePrompts: { + type: 'array', + items: { type: 'string', maxLength: 200 }, + minItems: 1, + maxItems: 10 + }, + metadata: { type: 'object', nullable: true } + }, + required: ['name', 'tone', 'samplePrompts'], + additionalProperties: false + } + }; +} + +/** + * Validation Result Type + * Provides type-safe validation with comprehensive error reporting + */ +export interface ValidatorResult { + isValid: boolean; + errors: ValidationError[]; + data?: T; +} + +/** + * Detailed Validation Error Structure + */ +export interface ValidationError { + path: string; + message: string; + code: string; + params?: Record; +}