/** * Test Factory - Comprehensive test data generation and utilities * Provides consistent, realistic test data for all Brainy tests */ import { v4 as uuidv4 } from '../../src/universal/uuid' import type { NounType, VerbType } from '../../src/types/graphTypes' import type { Entity, Relation, Result, AddParams, UpdateParams, RelateParams, FindParams, BrainyConfig, } from '../../src/types/brainy.types' import type { Vector } from '../../src/coreTypes' /** * Test Data Generators */ // Generate a unique test ID export function generateTestId(prefix = 'test'): string { return `${prefix}_${uuidv4().slice(0, 8)}_${Date.now()}` } // Generate test vector (384-dimensional vector matching all-MiniLM-L6-v2) export function generateTestVector(dimension = 384): Vector { // Generate a deterministic but varied embedding const vector = new Array(dimension) for (let i = 0; i < dimension; i++) { // Create varied but deterministic values vector[i] = Math.sin(i * 0.1) * 0.5 + Math.cos(i * 0.05) * 0.3 } // Normalize to unit vector const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0)) return vector.map(val => val / magnitude) } // Generate realistic CUSTOM metadata. // // NOTE: this deliberately contains only custom (consumer-owned) fields. Reserved // Brainy fields (`createdAt`, `updatedAt`, `confidence`, `weight`, `subtype`, // `visibility`, `service`, `createdBy`, `data`, `_rev`) must NOT live in the // metadata bag — Brainy 8.0's default `reservedFieldPolicy: 'throw'` rejects any // reserved key passed through `metadata`. (Reserved values belong in their // dedicated top-level params, set by the add/relate factories below.) export function generateTestMetadata(overrides: any = {}): any { return { name: `Test Item ${generateTestId()}`, description: 'Test description', tags: ['test', 'automated'], version: 1, source: 'test-factory', ...overrides, } } // Generate a test entity export function createTestEntity(overrides: Partial = {}): Entity { const id = overrides.id || generateTestId('entity') const now = Date.now() return { id, type: 'person' as NounType, vector: generateTestVector(), metadata: generateTestMetadata(), service: 'test', createdAt: now, updatedAt: now, ...overrides, } } // Generate multiple test entities export function createTestEntities(count: number, baseOverrides: Partial = {}): Entity[] { return Array.from({ length: count }, (_, i) => createTestEntity({ ...baseOverrides, metadata: { ...generateTestMetadata(), name: `Test Entity ${i + 1}`, index: i, }, }) ) } // Generate a test relation export function createTestRelation(overrides: Partial = {}): Relation { const id = overrides.id || generateTestId('relation') const now = Date.now() return { id, from: overrides.from || generateTestId('from'), to: overrides.to || generateTestId('to'), type: (overrides.type || 'RelatedTo') as VerbType, weight: 1.0, metadata: generateTestMetadata(), service: 'test', createdAt: now, updatedAt: now, ...overrides, } } // Generate multiple test relations export function createTestRelations(count: number, baseOverrides: Partial = {}): Relation[] { return Array.from({ length: count }, (_, i) => createTestRelation({ ...baseOverrides, metadata: { ...generateTestMetadata(), index: i, }, }) ) } // Generate a test result (entity with score) export function createTestResult( score: number = 0.95, entityOverrides: Partial = {} ): Result { return { id: entityOverrides.id || generateTestId('result'), score, entity: createTestEntity(entityOverrides), } } // Generate add parameters export function createAddParams(overrides: Partial = {}): AddParams { return { data: 'Test content for embedding', type: 'thing' as NounType, metadata: generateTestMetadata(), service: 'test', ...overrides, } } // Generate update parameters export function createUpdateParams(id: string, overrides: Partial = {}): UpdateParams { return { id, metadata: { updated: true }, merge: true, ...overrides, } } // Generate relate parameters export function createRelateParams(from: string, to: string, overrides: Partial = {}): RelateParams { return { from, to, type: 'relatedTo' as VerbType, weight: 1.0, metadata: generateTestMetadata(), service: 'test', ...overrides, } } // Generate find parameters export function createFindParams(overrides: Partial = {}): FindParams { return { query: 'test query', limit: 10, offset: 0, explain: false, includeRelations: false, service: 'test', ...overrides, } } // Generate test configuration. Brainy 8.0 defaults `requireSubtype: true`; // tests opt out unless they specifically exercise subtype enforcement. export function createTestConfig(overrides: Partial = {}): BrainyConfig { return { requireSubtype: false, storage: { type: 'memory' }, model: { type: 'fast' }, index: { m: 16, efConstruction: 200, efSearch: 50, }, cache: { maxSize: 1000, ttl: 3600 }, ...overrides, } } /** * Test Data Sets - Pre-built realistic scenarios */ // Create a social network graph export function createSocialNetworkTestData() { const alice = createTestEntity({ id: 'alice', type: 'person' as NounType, metadata: { name: 'Alice', age: 30 } }) const bob = createTestEntity({ id: 'bob', type: 'person' as NounType, metadata: { name: 'Bob', age: 25 } }) const charlie = createTestEntity({ id: 'charlie', type: 'person' as NounType, metadata: { name: 'Charlie', age: 35 } }) const diana = createTestEntity({ id: 'diana', type: 'person' as NounType, metadata: { name: 'Diana', age: 28 } }) const entities = [alice, bob, charlie, diana] const relations = [ createTestRelation({ from: 'alice', to: 'bob', type: 'friendOf' as VerbType }), createTestRelation({ from: 'alice', to: 'charlie', type: 'friendOf' as VerbType }), createTestRelation({ from: 'bob', to: 'charlie', type: 'worksWith' as VerbType }), createTestRelation({ from: 'charlie', to: 'diana', type: 'mentors' as VerbType }), createTestRelation({ from: 'alice', to: 'diana', type: 'likes' as VerbType }), ] return { entities, relations } } // Create a knowledge graph export function createKnowledgeGraphTestData() { const entities = [ createTestEntity({ id: 'earth', type: 'location' as NounType, metadata: { name: 'Earth', type: 'planet' } }), createTestEntity({ id: 'sun', type: 'thing' as NounType, metadata: { name: 'Sun', type: 'star' } }), createTestEntity({ id: 'moon', type: 'thing' as NounType, metadata: { name: 'Moon', type: 'satellite' } }), createTestEntity({ id: 'mars', type: 'location' as NounType, metadata: { name: 'Mars', type: 'planet' } }), ] const relations = [ createTestRelation({ from: 'earth', to: 'sun', type: 'dependsOn' as VerbType }), createTestRelation({ from: 'moon', to: 'earth', type: 'dependsOn' as VerbType }), createTestRelation({ from: 'mars', to: 'sun', type: 'dependsOn' as VerbType }), createTestRelation({ from: 'earth', to: 'moon', type: 'contains' as VerbType }), ] return { entities, relations } } // Create large dataset for performance testing export function createLargeTestDataset(entityCount = 1000, relationCount = 5000) { const entities = createTestEntities(entityCount) const relations: Relation[] = [] const verbTypes = ['relatedTo', 'friendOf', 'worksWith', 'creates', 'owns'] as VerbType[] // Create random relations between entities for (let i = 0; i < relationCount; i++) { const fromIdx = Math.floor(Math.random() * entityCount) const toIdx = Math.floor(Math.random() * entityCount) if (fromIdx !== toIdx) { relations.push( createTestRelation({ from: entities[fromIdx].id, to: entities[toIdx].id, type: verbTypes[Math.floor(Math.random() * verbTypes.length)], }) ) } } return { entities, relations } } /** * Test Assertion Helpers */ export function assertEntity(entity: any): asserts entity is Entity { if (!entity || typeof entity !== 'object') { throw new Error('Entity must be an object') } if (!entity.id || typeof entity.id !== 'string') { throw new Error('Entity must have a string id') } if (!entity.type || typeof entity.type !== 'string') { throw new Error('Entity must have a string type') } if (!entity.vector || !Array.isArray(entity.vector)) { throw new Error('Entity must have an array vector') } if (typeof entity.createdAt !== 'number') { throw new Error('Entity must have a numeric createdAt timestamp') } } export function assertRelation(relation: any): asserts relation is Relation { if (!relation || typeof relation !== 'object') { throw new Error('Relation must be an object') } if (!relation.id || typeof relation.id !== 'string') { throw new Error('Relation must have a string id') } if (!relation.from || typeof relation.from !== 'string') { throw new Error('Relation must have a string from') } if (!relation.to || typeof relation.to !== 'string') { throw new Error('Relation must have a string to') } if (!relation.type || typeof relation.type !== 'string') { throw new Error('Relation must have a string type') } if (typeof relation.createdAt !== 'number') { throw new Error('Relation must have a numeric createdAt timestamp') } } export function assertResult(result: any): asserts result is Result { if (!result || typeof result !== 'object') { throw new Error('Result must be an object') } if (!result.id || typeof result.id !== 'string') { throw new Error('Result must have a string id') } if (typeof result.score !== 'number') { throw new Error('Result must have a numeric score') } assertEntity(result.entity) } /** * Test Cleanup Helpers */ export class TestCleanup { private cleanupFunctions: Array<() => Promise> = [] register(fn: () => Promise) { this.cleanupFunctions.push(fn) } async cleanup() { for (const fn of this.cleanupFunctions.reverse()) { try { await fn() } catch (error) { console.error('Cleanup error:', error) } } this.cleanupFunctions = [] } } /** * Test Timing Helpers */ export async function measureExecutionTime( fn: () => Promise, label?: string ): Promise<{ result: T; duration: number }> { const start = performance.now() const result = await fn() const duration = performance.now() - start if (label) { console.log(`${label}: ${duration.toFixed(2)}ms`) } return { result, duration } } /** * Test Delay Helper */ export function delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)) } /** * Mock Storage Helper */ export function createMockStorage() { const store = new Map() return { get: async (key: string) => store.get(key), set: async (key: string, value: any) => { store.set(key, value); return true }, delete: async (key: string) => store.delete(key), has: async (key: string) => store.has(key), clear: async () => store.clear(), size: () => store.size, entries: () => Array.from(store.entries()), } } /** * Test Error Generators */ export function createTestError(message = 'Test error', code = 'TEST_ERROR') { const error = new Error(message) as any error.code = code return error } export function createNetworkError() { return createTestError('Network request failed', 'NETWORK_ERROR') } export function createTimeoutError() { return createTestError('Operation timed out', 'TIMEOUT_ERROR') } export function createValidationError(field: string) { return createTestError(`Validation failed for field: ${field}`, 'VALIDATION_ERROR') } // Export everything as a namespace for convenience export const TestFactory = { // IDs generateTestId, // Core data generateTestVector, generateTestMetadata, createTestEntity, createTestEntities, createTestRelation, createTestRelations, createTestResult, // Parameters createAddParams, createUpdateParams, createRelateParams, createFindParams, // Config createTestConfig, // Datasets createSocialNetworkTestData, createKnowledgeGraphTestData, createLargeTestDataset, // Assertions assertEntity, assertRelation, assertResult, // Utilities TestCleanup, measureExecutionTime, delay, createMockStorage, // Errors createTestError, createNetworkError, createTimeoutError, createValidationError, } export default TestFactory