@elizaos/test-utils
Version:
Utilities and objects for unit testing
1,487 lines (1,475 loc) • 94.5 kB
JavaScript
// 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);