UNPKG

@elizaos/test-utils

Version:

Utilities and objects for unit testing

1,487 lines (1,475 loc) 94.5 kB
// src/realRuntime.ts import { AgentRuntime, logger as logger3, stringToUuid } from "@elizaos/core"; // ../../node_modules/uuid/dist-node/stringify.js var byteToHex = []; for (let i = 0;i < 256; ++i) { byteToHex.push((i + 256).toString(16).slice(1)); } function unsafeStringify(arr, offset = 0) { return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); } // ../../node_modules/uuid/dist-node/rng.js import { randomFillSync } from "node:crypto"; var rnds8Pool = new Uint8Array(256); var poolPtr = rnds8Pool.length; function rng() { if (poolPtr > rnds8Pool.length - 16) { randomFillSync(rnds8Pool); poolPtr = 0; } return rnds8Pool.slice(poolPtr, poolPtr += 16); } // ../../node_modules/uuid/dist-node/native.js import { randomUUID } from "node:crypto"; var native_default = { randomUUID }; // ../../node_modules/uuid/dist-node/v4.js function _v4(options, buf, offset) { options = options || {}; const rnds = options.random ?? options.rng?.() ?? rng(); if (rnds.length < 16) { throw new Error("Random bytes length must be >= 16"); } rnds[6] = rnds[6] & 15 | 64; rnds[8] = rnds[8] & 63 | 128; if (buf) { offset = offset || 0; if (offset < 0 || offset + 16 > buf.length) { throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); } for (let i = 0;i < 16; ++i) { buf[offset + i] = rnds[i]; } return buf; } return unsafeStringify(rnds); } function v4(options, buf, offset) { if (native_default.randomUUID && !buf && !options) { return native_default.randomUUID(); } return _v4(options, buf, offset); } var v4_default = v4; // src/testDatabase.ts import { logger } from "@elizaos/core"; class TestDatabaseManager { testDatabases = new Map; tempPaths = new Set; async createIsolatedDatabase(testId) { try { logger.debug({ src: "test-utils:test-database", testId }, `Creating isolated test database for ${testId}`); let adapter; try { logger.debug({ src: "test-utils:test-database", testId }, `Attempting to load PostgreSQL adapter for ${testId}`); if (process.env.FORCE_MOCK_DB === "true") { logger.warn({ src: "test-utils:test-database" }, "FORCE_MOCK_DB is set - using mock database"); adapter = this.createMockDatabase(testId); } else { try { const sqlPlugin = globalThis.__elizaOS_sqlPlugin; if (!sqlPlugin?.createDatabaseAdapter) { throw new Error("SQL plugin not available - falling back to mock database"); } const postgresUrl = process.env.TEST_POSTGRES_URL || process.env.POSTGRES_URL; if (!postgresUrl) { throw new Error("PostgreSQL URL not available - falling back to mock database"); } adapter = await sqlPlugin.createDatabaseAdapter({ postgresUrl }, "11111111-2222-3333-4444-555555555555"); logger.debug({ src: "test-utils:test-database", testId }, `Successfully created PostgreSQL adapter for ${testId}`); } catch (importError) { logger.warn({ src: "test-utils:test-database", error: importError instanceof Error ? importError.message : String(importError) }, `SQL plugin not available: ${importError instanceof Error ? importError.message : String(importError)} - falling back to mock database`); adapter = this.createMockDatabase(testId); } } } catch (postgresError) { logger.warn({ src: "test-utils:test-database", error: postgresError instanceof Error ? postgresError.message : String(postgresError) }, `Failed to create PostgreSQL database: ${postgresError instanceof Error ? postgresError.message : String(postgresError)} - falling back to mock database`); adapter = this.createMockDatabase(testId); } await adapter.init(); this.testDatabases.set(testId, adapter); logger.debug({ src: "test-utils:test-database", testId }, `Successfully created isolated database for ${testId}`); return adapter; } catch (error) { logger.error({ src: "test-utils:test-database", testId, error: error instanceof Error ? error.message : String(error) }, `Failed to create test database for ${testId}: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Test database creation failed: ${error instanceof Error ? error.message : String(error)}`); } } createMockDatabase(testId) { const storage = { agents: new Map, entities: new Map, memories: new Map, relationships: new Map, rooms: new Map, participants: new Map, cache: new Map, worlds: new Map, tasks: new Map, logs: new Map }; const adapter = { db: null, async initialize() { logger.debug({ src: "test-utils:test-database", testId }, `Initialized mock database for ${testId}`); }, async init() { logger.debug({ src: "test-utils:test-database", testId }, `Initialized mock database for ${testId}`); }, async runMigrations() {}, async isReady() { return true; }, async close() { storage.agents.clear(); storage.entities.clear(); storage.memories.clear(); storage.relationships.clear(); storage.rooms.clear(); storage.participants.clear(); storage.cache.clear(); storage.worlds.clear(); storage.tasks.clear(); }, async getConnection() { return null; }, async ensureEmbeddingDimension() {}, async getAgent(agentId) { return storage.agents.get(agentId) || null; }, async getAgents() { return Array.from(storage.agents.values()); }, async createAgent(agent) { const id = agent.id || v4_default(); const fullAgent = { ...agent, id }; storage.agents.set(id, fullAgent); return true; }, async updateAgent(agentId, agent) { if (storage.agents.has(agentId)) { storage.agents.set(agentId, { ...agent, id: agentId }); return true; } return false; }, async deleteAgent(agentId) { return storage.agents.delete(agentId); }, async createEntity(entity) { const id = entity.id || v4_default(); const fullEntity = { ...entity, id }; storage.entities.set(id, fullEntity); return fullEntity; }, async createEntities(entities) { for (const entity of entities) { const id = entity.id || v4_default(); const fullEntity = { ...entity, id }; storage.entities.set(id, fullEntity); } return true; }, async getEntityById(id) { return storage.entities.get(id) || null; }, async updateEntity(entity) { if (!entity.id || !storage.entities.has(entity.id)) { throw new Error("Entity not found"); } storage.entities.set(entity.id, entity); }, async getEntitiesForRoom(roomId) { const participants = Array.from(storage.participants.values()).filter((p) => p.roomId === roomId); const entities = []; for (const participant of participants) { const entity = storage.entities.get(participant.entityId); if (entity) { entities.push(entity); } } return entities; }, async createMemory(memory, tableName = "messages", _unique = false) { const id = memory.id || v4_default(); const fullMemory = { ...memory, id, createdAt: memory.createdAt || Date.now() }; if (!storage.memories.has(tableName)) { storage.memories.set(tableName, new Map); } storage.memories.get(tableName).set(id, fullMemory); return id; }, async getMemories(params) { const tableName = params.tableName || "messages"; const tableData = storage.memories.get(tableName); if (!tableData) { return []; } let memories = Array.from(tableData.values()); if (params.roomId) { memories = memories.filter((m) => m.roomId === params.roomId); } if (params.entityId) { memories = memories.filter((m) => m.entityId === params.entityId); } memories.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); if (params.count) { memories = memories.slice(0, params.count); } return memories; }, async searchMemories(params) { const tableName = params.tableName || "messages"; const tableData = storage.memories.get(tableName); if (!tableData) { return []; } let memories = Array.from(tableData.values()); if (params.roomId) { memories = memories.filter((m) => m.roomId === params.roomId); } if (params.query) { memories = memories.filter((m) => typeof m.content === "object" && m.content !== null && ("text" in m.content) && typeof m.content.text === "string" ? m.content.text.toLowerCase().includes(params.query.toLowerCase()) : false); } return memories.slice(0, params.count || 10); }, async getMemoryById(id) { for (const [_tableName, tableData] of storage.memories) { const memory = tableData.get(id); if (memory) { return memory; } } return null; }, async getMemoriesByIds(ids, tableName = "messages") { const tableData = storage.memories.get(tableName); if (!tableData) { return []; } const memories = []; for (const id of ids) { const memory = tableData.get(id); if (memory) { memories.push(memory); } } return memories; }, async getMemoriesByRoomIds(params) { const tableData = storage.memories.get(params.tableName); if (!tableData) { return []; } let memories = Array.from(tableData.values()).filter((m) => params.roomIds.includes(m.roomId)); if (params.limit) { memories = memories.slice(0, params.limit); } return memories; }, async getCachedEmbeddings() { return []; }, async log(params) { if (!storage.logs) { storage.logs = new Map; } const logId = v4_default(); storage.logs.set(logId, { id: logId, ...params, createdAt: new Date }); }, async getLogs() { if (!storage.logs) { return []; } return Array.from(storage.logs.values()); }, async deleteMemory(memoryId, tableName = "messages") { const tableData = storage.memories.get(tableName); if (tableData) { return tableData.delete(memoryId); } return false; }, async createRoom(room) { const id = room.id || v4_default(); const fullRoom = { ...room, id }; storage.rooms.set(id, fullRoom); }, async getRoom(roomId) { return storage.rooms.get(roomId) || null; }, async getRooms(worldId) { return Array.from(storage.rooms.values()).filter((room) => !worldId || room.worldId === worldId); }, async addParticipant(entityId, roomId) { const participantId = `${entityId}-${roomId}`; storage.participants.set(participantId, { entityId, roomId }); return true; }, async removeParticipant(entityId, roomId) { const participantId = `${entityId}-${roomId}`; return storage.participants.delete(participantId); }, async getParticipantsForRoom(roomId) { return Array.from(storage.participants.values()).filter((p) => p.roomId === roomId).map((p) => p.entityId); }, async setCache(key, value) { storage.cache.set(key, { value, createdAt: Date.now() }); return true; }, async getCache(key) { const cached = storage.cache.get(key); return cached ? cached.value : null; }, async deleteCache(key) { return storage.cache.delete(key); }, async createWorld(world) { const id = world.id || v4_default(); const fullWorld = { ...world, id }; storage.worlds.set(id, fullWorld); return id; }, async getWorld(worldId) { return storage.worlds.get(worldId) || null; }, async getAllWorlds() { return Array.from(storage.worlds.values()); }, async createTask(task) { const id = task.id || v4_default(); const fullTask = { ...task, id, updatedAt: task.updatedAt || Date.now() }; storage.tasks.set(id, fullTask); return id; }, async getTasks(params) { let tasks = Array.from(storage.tasks.values()); if (params.roomId) { tasks = tasks.filter((task) => task.roomId === params.roomId); } if (params.tags) { tasks = tasks.filter((task) => params.tags.some((tag) => task.tags.includes(tag))); } return tasks; }, async deleteTask(taskId) { storage.tasks.delete(taskId); }, async createRelationship(relationship) { const id = v4_default(); const fullRelationship = { ...relationship, id }; storage.relationships.set(id, fullRelationship); return true; }, async getRelationships(params) { let relationships = Array.from(storage.relationships.values()); if (params.entityId) { relationships = relationships.filter((rel) => rel.sourceEntityId === params.entityId || rel.targetEntityId === params.entityId); } return relationships; }, async getEntitiesByIds(ids) { const entities = []; for (const id of ids) { const entity = storage.entities.get(id); if (entity) { entities.push(entity); } } return entities; }, async updateMemory(memory) { const tableName = "messages"; const tableData = storage.memories.get(tableName); if (tableData && tableData.has(memory.id)) { const existing = tableData.get(memory.id); const updated = { ...existing, ...memory }; tableData.set(memory.id, updated); return updated; } return null; }, async countMemories(roomId, unique, tableName = "messages") { const tableData = storage.memories.get(tableName); if (!tableData) { return 0; } if (!roomId) { return tableData.size; } let memories = Array.from(tableData.values()).filter((m) => m.roomId === roomId); if (unique) { memories = memories.filter((m) => m.unique === true); } return memories.length; }, async getMemoriesByEntityIds(entityIds, tableName = "messages") { const tableData = storage.memories.get(tableName); if (!tableData) { return []; } return Array.from(tableData.values()).filter((m) => entityIds.includes(m.entityId)); }, async removeAllMemories(roomId, tableName = "messages") { const tableData = storage.memories.get(tableName); if (!tableData) { return; } const toDelete = []; for (const [id, memory] of tableData) { if (memory.roomId === roomId) { toDelete.push(id); } } for (const id of toDelete) { tableData.delete(id); } }, async updateRoom(room) { if (!room.id || !storage.rooms.has(room.id)) { throw new Error("Room not found"); } storage.rooms.set(room.id, room); }, async deleteRoom(roomId) { storage.rooms.delete(roomId); }, async getRoomsByIds(roomIds) { const rooms = []; for (const id of roomIds) { const room = storage.rooms.get(id); if (room) { rooms.push(room); } } return rooms; }, async createRooms(rooms) { const ids = []; for (const room of rooms) { const id = room.id || v4_default(); const fullRoom = { ...room, id }; storage.rooms.set(id, fullRoom); ids.push(id); } return ids; }, async getRoomsByWorld(worldId) { return Array.from(storage.rooms.values()).filter((room) => room.worldId === worldId); }, async deleteRoomsByWorldId(worldId) { const toDelete = []; for (const [id, room] of storage.rooms) { if (room.worldId === worldId) { toDelete.push(id); } } for (const id of toDelete) { storage.rooms.delete(id); } }, async getWorlds(params) { let worlds = Array.from(storage.worlds.values()); if (params?.agentId) { worlds = worlds.filter((w) => w.agentId === params.agentId); } return worlds; }, async removeWorld(worldId) { storage.worlds.delete(worldId); }, async updateWorld(world) { if (!world.id || !storage.worlds.has(world.id)) { throw new Error("World not found"); } storage.worlds.set(world.id, world); }, async createComponent(component) { const entityComponents = storage.entities.get(component.entityId)?.components || []; entityComponents.push(component); const entity = storage.entities.get(component.entityId); if (entity) { entity.components = entityComponents; storage.entities.set(component.entityId, entity); } return true; }, async getComponents(entityId) { const entity = storage.entities.get(entityId); return entity?.components || []; }, async updateComponent(component) { const entity = storage.entities.get(component.entityId); if (entity && entity.components) { const index = entity.components.findIndex((c) => c.id === component.id); if (index >= 0) { entity.components[index] = component; storage.entities.set(component.entityId, entity); return; } } throw new Error("Component not found"); }, async deleteComponent(componentId) { for (const entity of storage.entities.values()) { if (entity.components) { const index = entity.components.findIndex((c) => c.id === componentId); if (index >= 0) { entity.components.splice(index, 1); storage.entities.set(entity.id, entity); return; } } } }, async getComponent(entityId, type, worldId, sourceEntityId) { const entity = storage.entities.get(entityId); if (!entity?.components) { return null; } return entity.components.find((c) => c.type === type && (!worldId || c.worldId === worldId) && (!sourceEntityId || c.sourceEntityId === sourceEntityId)) || null; }, async getParticipantsForEntity(entityId) { return Array.from(storage.participants.values()).filter((p) => p.entityId === entityId); }, async getRoomsForParticipant(entityId) { return Array.from(storage.participants.values()).filter((p) => p.entityId === entityId).map((p) => p.roomId); }, async getRoomsForParticipants(userIds) { const roomIds = new Set; for (const participant of storage.participants.values()) { if (userIds.includes(participant.entityId)) { roomIds.add(participant.roomId); } } return Array.from(roomIds); }, async getParticipantUserState(_roomId, _entityId) { return null; }, async setParticipantUserState(_roomId, _entityId, _state) {}, async getRelationship(params) { const relationships = Array.from(storage.relationships.values()); return relationships.find((r) => r.sourceEntityId === params.sourceEntityId && r.targetEntityId === params.targetEntityId) || null; }, async updateRelationship(relationship) { if (!relationship.id || !storage.relationships.has(relationship.id)) { throw new Error("Relationship not found"); } storage.relationships.set(relationship.id, relationship); }, async getTask(id) { return storage.tasks.get(id) || null; }, async getTasksByName(name) { return Array.from(storage.tasks.values()).filter((task) => task.name === name); }, async updateTask(id, updates) { const task = storage.tasks.get(id); if (!task) { throw new Error("Task not found"); } const updated = { ...task, ...updates, updatedAt: Date.now() }; storage.tasks.set(id, updated); }, async deleteLog(logId) { if (storage.logs) { storage.logs.delete(logId); } }, async getMemoriesByWorldId(params) { const tableName = params.tableName || "messages"; const tableData = storage.memories.get(tableName); if (!tableData) { return []; } let memories = Array.from(tableData.values()).filter((m) => m.worldId === params.worldId); if (params.count) { memories = memories.slice(0, params.count); } return memories; }, async deleteManyMemories(memoryIds) { for (const [_tableName, tableData] of storage.memories) { for (const id of memoryIds) { tableData.delete(id); } } }, async deleteAllMemories(roomId, _tableName) { const tableData = storage.memories.get(_tableName); if (!tableData) { return; } const toDelete = []; for (const [id, memory] of tableData) { if (memory.roomId === roomId) { toDelete.push(id); } } for (const id of toDelete) { tableData.delete(id); } }, async addParticipantsRoom(entityIds, roomId) { for (const entityId of entityIds) { const participantId = `${entityId}-${roomId}`; storage.participants.set(participantId, { entityId, roomId }); } return true; }, async isRoomParticipant(roomId, entityId) { const participantId = `${entityId}-${roomId}`; return storage.participants.has(participantId); } }; return adapter; } async cleanupDatabase(testId) { try { const adapter = this.testDatabases.get(testId); if (adapter) { await adapter.close(); this.testDatabases.delete(testId); logger.debug({ src: "test-utils:test-database", testId }, `Cleaned up database for ${testId}`); } } catch (error) { logger.warn({ src: "test-utils:test-database", testId, error: error instanceof Error ? error.message : String(error) }, `Error cleaning up database ${testId}: ${error instanceof Error ? error.message : String(error)}`); } } async cleanup() { logger.debug({ src: "test-utils:test-database" }, "Cleaning up all test databases"); const cleanupPromises = Array.from(this.testDatabases.keys()).map((testId) => this.cleanupDatabase(testId)); await Promise.all(cleanupPromises); this.tempPaths.clear(); this.testDatabases.clear(); logger.debug({ src: "test-utils:test-database" }, "Successfully cleaned up all test databases"); } getStats() { return { activeDatabases: this.testDatabases.size, tempPaths: Array.from(this.tempPaths), memoryUsage: `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB` }; } } async function createTestDatabase(testId) { const actualTestId = testId || `test-${v4_default().slice(0, 8)}`; const manager = new TestDatabaseManager; const adapter = await manager.createIsolatedDatabase(actualTestId); return { adapter, manager, testId: actualTestId }; } // src/testModels.ts import { logger as logger2 } from "@elizaos/core"; class TestModelProvider { responses = new Map; patterns = []; defaultResponse; contextHistory = []; constructor(defaultResponse = "I understand and will help with that.", _options = {}) { this.defaultResponse = defaultResponse; this.addDefaultPatterns(); } addDefaultPatterns() { const defaultPatterns = [ { pattern: /^(hello|hi|hey|greetings)/i, response: "Hello! How can I help you today?" }, { pattern: /(good morning|good afternoon|good evening)/i, response: "Good day! What can I assist you with?" }, { pattern: /(create|add|make).*?(todo|task|reminder)/i, response: "I'll create that task for you right away. Let me add it to your todo list." }, { pattern: /(schedule|plan|organize)/i, response: "I'll help you schedule that. Let me organize this for you." }, { pattern: /(search|find|look|query).*?(for|about)/i, response: "Let me search for that information. I'll look into it right away." }, { pattern: /(what|how|when|where|why)/i, response: "Let me find that information for you. I'll provide a detailed answer." }, { pattern: /(analyze|review|examine|check)/i, response: "I'll analyze this carefully and provide my assessment with detailed insights." }, { pattern: /(explain|describe|tell me about)/i, response: "I'll explain that in detail for you. Here's what you need to know." }, { pattern: /(send|email|message|notify)/i, response: "I'll send that message for you. Let me take care of the communication." }, { pattern: /(delete|remove|cancel)/i, response: "I'll remove that for you. Let me handle the deletion safely." }, { pattern: /(save|store|backup)/i, response: "I'll save that information securely. Your data will be stored properly." }, { pattern: /(load|open|access)/i, response: "I'll access that resource for you. Let me retrieve the information." }, { pattern: /(fix|repair|solve|troubleshoot)/i, response: "I'll help troubleshoot this issue. Let me analyze the problem and find a solution." }, { pattern: /(help|assist|support)/i, response: "I'm here to help! Let me assist you with whatever you need." }, { pattern: /(should|recommend|suggest|advise)/i, response: "Based on the information provided, I'd recommend the following approach." }, { pattern: /(choose|select|decide)/i, response: "Let me help you make that decision. Here are the key factors to consider." }, { pattern: /(yes|ok|okay|sure|agreed)/i, response: "Understood! I'll proceed with that as requested." }, { pattern: /(no|stop|cancel|abort)/i, response: "Alright, I'll stop that process. Is there anything else I can help with?" }, { pattern: /(if.*then|because|therefore|since)/i, response: "I understand the logic. Let me work through this step by step." }, { pattern: /(compare|contrast|difference|similar)/i, response: "I'll compare these options and highlight the key differences and similarities." }, { pattern: /(error|problem|issue|broken|failed)/i, response: "I see there's an issue. Let me investigate the problem and find a solution." } ]; this.patterns.push(...defaultPatterns); } async generateText(params) { const prompt = params.prompt; try { const exactMatch = this.responses.get(prompt); if (exactMatch) { this.addToHistory(prompt, exactMatch); return exactMatch; } for (const { pattern, response } of this.patterns) { if (pattern.test(prompt)) { const contextualResponse = this.makeContextual(response, prompt); this.addToHistory(prompt, contextualResponse); return contextualResponse; } } const intelligentResponse = this.generateIntelligentDefault(prompt); this.addToHistory(prompt, intelligentResponse); return intelligentResponse; } catch (error) { logger2.warn({ src: "test-utils:test-models", error: error instanceof Error ? error.message : String(error) }, `Error in test model provider: ${error instanceof Error ? error.message : String(error)}`); return this.defaultResponse; } } async generateEmbedding(params) { const text = params.text; const hash = this.simpleHash(text); const embedding = []; for (let i = 0;i < 1536; i++) { const value = Math.sin(hash + i) * 0.5; embedding.push(value); } return embedding; } async generateObject(params) { const prompt = params.prompt; if (prompt.includes("thought") || prompt.includes("reasoning")) { return { thought: "I need to think about this carefully and provide a helpful response.", reasoning: "Based on the context provided, here's my analysis.", confidence: 0.85 }; } if (prompt.includes("action") || prompt.includes("execute")) { return { action: "RESPOND", parameters: {}, confidence: 0.9 }; } if (prompt.includes("memory") || prompt.includes("remember")) { return { shouldStore: true, importance: 0.7, category: "conversation" }; } return { response: await this.generateText({ prompt, ...params }), confidence: 0.8, metadata: { timestamp: Date.now(), model: "test-model" } }; } makeContextual(response, prompt) { const keyTerms = this.extractKeyTerms(prompt); if (keyTerms.length > 0) { const term = keyTerms[0]; return response.replace(/that|this|it/g, term.length > 20 ? `that ${term.substring(0, 20)}...` : `that ${term}`); } return response; } generateIntelligentDefault(prompt) { const lowerPrompt = prompt.toLowerCase(); if (lowerPrompt.includes("?")) { return "That's a great question. Let me provide you with a comprehensive answer based on the available information."; } if (lowerPrompt.match(/^(please|can you|could you|would you)/)) { return "Of course! I'll take care of that for you right away."; } if (lowerPrompt.length > 200) { return "I understand this is a complex request. Let me work through this systematically and provide you with a detailed response."; } if (lowerPrompt.match(/(angry|sad|frustrated|excited|happy|worried)/)) { return "I understand how you're feeling. Let me help you work through this thoughtfully."; } return this.defaultResponse; } extractKeyTerms(prompt) { const commonWords = new Set([ "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "i", "you", "he", "she", "it", "we", "they", "this", "that", "these", "those", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", "can", "may", "might", "must" ]); const words = prompt.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((word) => word.length > 2 && !commonWords.has(word)); return [...new Set(words)].slice(0, 3); } simpleHash(str) { let hash = 0; for (let i = 0;i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return Math.abs(hash); } addToHistory(prompt, response) { this.contextHistory.push({ prompt, response }); if (this.contextHistory.length > 10) { this.contextHistory.shift(); } } setResponse(prompt, response) { this.responses.set(prompt, response); } addPattern(pattern, response) { this.patterns.unshift({ pattern, response }); } setDefaultResponse(response) { this.defaultResponse = response; } clear() { this.responses.clear(); this.patterns.length = 0; this.contextHistory.length = 0; this.addDefaultPatterns(); } getHistory() { return [...this.contextHistory]; } } function createTestModelProvider(scenarios = [], defaultResponse) { const provider = new TestModelProvider(defaultResponse); for (const scenario of scenarios) { if (scenario.prompt instanceof RegExp) { provider.addPattern(scenario.prompt, scenario.response); } else { provider.setResponse(scenario.prompt, scenario.response); } } return provider; } function createSpecializedModelProvider(type) { const provider = new TestModelProvider; switch (type) { case "conversational": provider.addPattern(/.*/, "That's interesting! Let me respond thoughtfully to what you've shared."); break; case "analytical": provider.addPattern(/.*/, "Let me analyze this systematically. Based on the data and context provided, here's my assessment."); break; case "creative": provider.addPattern(/.*/, "What a creative challenge! Let me think outside the box and explore innovative possibilities."); break; case "factual": provider.addPattern(/.*/, "Based on factual information and established knowledge, here's an accurate response."); break; } return provider; } function createModelHandler(provider) { return async (runtime, params) => { const modelType = params.modelType || "TEXT_LARGE"; switch (modelType) { case "TEXT_SMALL": case "TEXT_LARGE": return await provider.generateText(params); case "TEXT_EMBEDDING": return await provider.generateEmbedding(params); case "OBJECT_SMALL": case "OBJECT_LARGE": return await provider.generateObject(params); default: return await provider.generateText(params); } }; } class TestScenarioBuilder { scenarios = []; addGreeting(response = "Hello! How can I help you?") { this.scenarios.push({ prompt: /^(hello|hi|hey)/i, response }); return this; } addTaskCreation(response = "I'll create that task for you.") { this.scenarios.push({ prompt: /(create|add|make).*?(todo|task)/i, response }); return this; } addSearch(response = "Let me search for that information.") { this.scenarios.push({ prompt: /(search|find|look)/i, response }); return this; } addCustom(prompt, response) { this.scenarios.push({ prompt, response }); return this; } build(defaultResponse) { return createTestModelProvider(this.scenarios, defaultResponse); } } function scenarios() { return new TestScenarioBuilder; } // src/realRuntime.ts class RuntimeTestHarness { runtimes = new Map; databaseManager; testId; constructor(testId) { this.testId = testId || `test-${v4_default().slice(0, 8)}`; this.databaseManager = new TestDatabaseManager; } async createTestRuntime(config) { try { logger3.info({ src: "test-utils:real-runtime", testId: this.testId }, `Creating real test runtime for ${this.testId}`); const databaseAdapter = await this.databaseManager.createIsolatedDatabase(`${this.testId}-${Date.now()}`); const runtime = new AgentRuntime({ character: config.character, adapter: databaseAdapter }); for (const plugin of config.plugins) { if (typeof plugin === "string") { const loadedPlugin = await this.loadPlugin(plugin); await runtime.registerPlugin(loadedPlugin); } else { await runtime.registerPlugin(plugin); } } await runtime.initialize(); this.runtimes.set(runtime.agentId, runtime); logger3.info({ src: "test-utils:real-runtime", agentId: runtime.agentId }, `Successfully created real runtime ${runtime.agentId}`); return runtime; } catch (error) { logger3.error({ src: "test-utils:real-runtime", error: error instanceof Error ? error.message : String(error) }, `Failed to create test runtime: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Test runtime creation failed: ${error instanceof Error ? error.message : String(error)}`); } } async loadPlugin(pluginName) { try { logger3.info({ src: "test-utils:real-runtime", pluginName }, `Attempting to load plugin: ${pluginName}`); let pluginModule; try { pluginModule = await import(pluginName); } catch (importError) { logger3.warn({ src: "test-utils:real-runtime", pluginName, error: importError instanceof Error ? importError.message : String(importError) }, `Could not import ${pluginName}: ${importError instanceof Error ? importError.message : String(importError)}`); throw importError; } const plugin = pluginModule.default || pluginModule[Object.keys(pluginModule)[0]]; if (!plugin || typeof plugin !== "object") { throw new Error(`Invalid plugin export from ${pluginName}`); } logger3.info({ src: "test-utils:real-runtime", pluginName }, `Successfully loaded plugin: ${pluginName}`); return plugin; } catch (error) { logger3.error({ src: "test-utils:real-runtime", pluginName, error: error instanceof Error ? error.message : String(error) }, `Failed to load plugin ${pluginName}: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Plugin ${pluginName} must be available for testing. Install it before running tests. Error: ${error instanceof Error ? error.message : String(error)}`); } } createRealisticModelProvider(scenarios2) { const defaultScenarios = [ { prompt: /hello|hi|hey/i, response: "Hello! How can I help you today?" }, { prompt: /create.*todo|add.*task/i, response: "I'll create that todo item for you right away." }, { prompt: /search|find|look/i, response: "Let me search for that information." }, { prompt: /analyze|review/i, response: "I'll analyze this carefully and provide my assessment." } ]; return createTestModelProvider(scenarios2 || defaultScenarios); } async processTestMessage(runtime, messageText, options = {}) { const startTime = Date.now(); const roomId = options.roomId || stringToUuid(`test-room-${v4_default()}`); const entityId = options.entityId || stringToUuid(`test-user-${v4_default()}`); try { const memory = { id: stringToUuid(`message-${v4_default()}`), entityId, roomId, content: { text: messageText, source: "test" }, createdAt: Date.now() }; const _messageId = await runtime.createMemory(memory, "messages"); const responses = await runtime.processActions(memory, [], undefined, undefined); const responseTime = Date.now() - startTime; const result = { scenarioName: `Process: "${messageText}"`, passed: true, errors: [], executedActions: [], createdMemories: Array.isArray(responses) ? responses.length : 0, responseTime }; if (options.expectedActions && options.expectedActions.length > 0) { const executedActions = await this.getExecutedActions(runtime, roomId); const missingActions = options.expectedActions.filter((action) => !executedActions.includes(action)); if (missingActions.length > 0) { result.passed = false; result.errors.push(`Missing expected actions: ${missingActions.join(", ")}`); } result.executedActions = executedActions; } if (options.timeoutMs && responseTime > options.timeoutMs) { result.passed = false; result.errors.push(`Response time ${responseTime}ms exceeded timeout ${options.timeoutMs}ms`); } return result; } catch (error) { return { scenarioName: `Process: "${messageText}"`, passed: false, errors: [`Runtime error: ${error instanceof Error ? error.message : String(error)}`], executedActions: [], createdMemories: 0, responseTime: Date.now() - startTime }; } } async getExecutedActions(runtime, roomId) { try { const memories = await runtime.getMemories({ roomId, count: 10, tableName: "messages" }); const actions = []; for (const memory of memories) { if (memory.content.actions && Array.isArray(memory.content.actions)) { actions.push(...memory.content.actions); } } return [...new Set(actions)]; } catch (error) { logger3.warn({ src: "test-utils:real-runtime", error: error instanceof Error ? error.message : String(error) }, `Could not retrieve executed actions: ${error instanceof Error ? error.message : String(error)}`); return []; } } async validateRuntimeHealth(runtime) { const issues = []; const services = []; const plugins = []; try { if (!runtime.agentId) { issues.push("Runtime missing agentId"); } if (!runtime.character) { issues.push("Runtime missing character"); } try { const healthMemory = { id: stringToUuid("health-check-message"), entityId: stringToUuid("health-check-entity"), roomId: stringToUuid("health-check-room"), content: { text: "Health check", source: "test" }, createdAt: Date.now() }; await runtime.createMemory(healthMemory, "test"); } catch (error) { issues.push(`Database not functional: ${error instanceof Error ? error.message : String(error)}`); } try { const serviceMap = runtime.services || new Map; for (const [name, service] of serviceMap) { services.push(name); if (!service) { issues.push(`Service ${name} is null/undefined`); } } } catch (error) { issues.push(`Services not accessible: ${error instanceof Error ? error.message : String(error)}`); } try { plugins.push(...runtime.plugins?.map((p) => p.name) || []); } catch (error) { issues.push(`Plugins not accessible: ${error instanceof Error ? error.message : String(error)}`); } return { healthy: issues.length === 0, issues, services, plugins }; } catch (error) { return { healthy: false, issues: [ `Runtime health check failed: ${error instanceof Error ? error.message : String(error)}` ], services, plugins }; } } async cleanup() { try { logger3.info({ src: "test-utils:real-runtime", testId: this.testId }, `Cleaning up test harness ${this.testId}`); for (const [runtimeId, runtime] of this.runtimes) { try { await runtime.stop(); logger3.debug({ src: "test-utils:real-runtime", runtimeId }, `Stopped runtime ${runtimeId}`); } catch (error) { logger3.warn({ src: "test-utils:real-runtime", runtimeId, error: error instanceof Error ? error.message : String(error) }, `Error stopping runtime ${runtimeId}: ${error instanceof Error ? error.message : String(error)}`); } } this.runtimes.clear(); await this.databaseManager.cleanup(); logger3.info({ src: "test-utils:real-runtime", testId: this.testId }, `Successfully cleaned up test harness ${this.testId}`); } catch (error) { logger3.error({ src: "test-utils:real-runtime", error: error instanceof Error ? error.message : String(error) }, `Error during cleanup: ${error instanceof Error ? error.message : String(error)}`); throw error; } } } async function createTestRuntime(config = {}) { const harness = new RuntimeTestHarness; const defaultCharacter = { name: "TestAgent", system: "You are a helpful test agent.", bio: ["I am a test agent used for integration testing."], messageExamples: [], postExamples: [], topics: ["testing"], knowledge: [], plugins: [] }; const runtime = await harness.createTestRuntime({ character: defaultCharacter, plugins: [], apiKeys: { OPENAI_API_KEY: "test-key" }, ...config }); return { runtime, harness }; } async function runIntegrationTest(testName, testFn, config) { const startTime = Date.now(); let harness; try { const { runtime, harness: testHarness } = await createTestRuntime(config); harness = testHarness; await testFn(runtime); return { scenarioName: testName, passed: true, errors: [], executedActions: [], createdMemories: 0, responseTime: Date.now() - startTime }; } catch (error) { return { scenarioName: testName, passed: false, errors: [error instanceof Error ? error.message : String(error)], executedActions: [], createdMemories: 0, responseTime: Date.now() - startTime }; } finally { if (harness) { await harness.cleanup(); } } } // src/templates.ts import { logger as logger4 } from "@elizaos/core"; class TestTemplate { config; runtime; constructor(config) { this.config = config; } async setup() { if (this.config.setup) { await this.config.setup(); } } async teardown() { if (this.config.teardown) { await this.config.teardown(); } } shouldSkip() { return this.config.skipCondition?.() ?? false; } getConfig() { return this.config; } } class UnitTestTemplate extends TestTemplate { testFunction; constructor(config, testFunction) { super(config); this.testFunction = testFunction; } async execute() { const startTime = Date.now(); if (this.shouldSkip()) { return { name: this.config.name, passed: true, duration: 0, warnings: ["Test skipped due to skip condition"] }; } try { await this.setup(); await this.testFunction(); return { name: this.config.name, passed: true, duration: Date.now() - startTime }; } catch (error) { return { name: this.config.name, passed: false, duration: Date.now() - startTime, error: error instanceof Error ? error : new Error(String(error)) }; } finally { await this.teardown(); } } } class IntegrationTestTemplate extends TestTemplate { testFunction; character; constructor(config, testFunction, character) { super(config); this.testFunction = testFunction; this.character = character; } async execute() { const startTime = Date.now(); if (this.shouldSkip()) { return { name: this.config.name, passed: true, duration: 0, warnings: ["Test skipped due to skip condition"] }; } try { await this.setup(); const { runtime } = await createTestRuntime({ character: this.character }); this.runtime = runtime; await this.testFunction(runtime); return { name: this.config.name, passed: true, duration: Date.now() - startTime }; } catch (error) { return { name: this.config.name, passed: false, duration: Date.now() - startTime, error: error instanceof Error ? error : new Error(String(error)) }; } finally { await this.teardown(); } } } class PluginTestTemplate extends TestTemplate { plugin; testFunction; constructor(config, plugin, testFunction) { super(config); this.plugin = plugin; this.testFunction = testFunction; } async execute() { const startTime = Date.now(); if (this.shouldSkip()) { return { name: this.config.name, passed: true, duration: 0, warnings: ["Test skipped due to skip condition"] }; } try { await this.setup(); const { runtime } = await createTestRuntime({ character: { name: "Test Agent", bio: "Test agent for plugin testing", plugins: [this.plugin.name] }, plugins: [this.plugin] }); await this.testFunction(runtime, this.plugin);