diff --git a/packages/core/README-TESTS.md b/packages/core/README-TESTS.md new file mode 100644 index 0000000000..85a14eb8f6 --- /dev/null +++ b/packages/core/README-TESTS.md @@ -0,0 +1,35 @@ +# Core Package Tests + +This package contains a test suite for evaluating functionalities using **Jest**. + +## Prerequisites + +1. **pnpm**: Ensure you have `pnpm` installed. If not, you can install it globally using: + ```bash + npm install -g pnpm + ``` + +2. **Environment Variables - NOT REQUIRED** : Set up a `.env` file in the project root (eliza) with the necessary environment variables. Copy .env.example file and add required variables. + +## Setup + +1. Navigate to the `packages/core` directory: + ```bash + cd packages/core + ``` + +2. Install dependencies: + ```bash + pnpm install + ``` + +## Running Tests + +Run all tests using: +```bash +pnpm test +``` + +The test results will be displayed in the terminal. + +--- \ No newline at end of file diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 62c0deeb70..35d5f9f0b3 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -4,7 +4,6 @@ export default { testEnvironment: "node", rootDir: "./src", testMatch: ["**/*.test.ts"], - setupFilesAfterEnv: ["/test_resources/testSetup.ts"], testTimeout: 120000, globals: { __DEV__: true, diff --git a/packages/core/src/embedding.ts b/packages/core/src/embedding.ts index c1fbbe80e6..c98cf8611c 100644 --- a/packages/core/src/embedding.ts +++ b/packages/core/src/embedding.ts @@ -1,4 +1,4 @@ -import { EmbeddingModel, FlagEmbedding } from "fastembed"; +import { FlagEmbedding } from "fastembed"; import path from "path"; import { fileURLToPath } from "url"; import { models } from "./models.ts"; diff --git a/packages/core/src/tests/database.test.ts b/packages/core/src/tests/database.test.ts new file mode 100644 index 0000000000..9922d16930 --- /dev/null +++ b/packages/core/src/tests/database.test.ts @@ -0,0 +1,239 @@ +/* eslint-disable no-dupe-class-members */ +import { DatabaseAdapter } from '../database.ts'; // Adjust the import based on your project structure +import { Memory, Actor, Account, Goal, GoalStatus, Participant, Relationship, UUID } from '../types'; // Adjust based on your types location + +class MockDatabaseAdapter extends DatabaseAdapter { + getMemoryById(_id: UUID): Promise { + throw new Error('Method not implemented.'); + } + log(_params: { body: { [key: string]: unknown; }; userId: UUID; roomId: UUID; type: string; }): Promise { + throw new Error('Method not implemented.'); + } + getActorDetails(_params: { roomId: UUID; }): Promise { + throw new Error('Method not implemented.'); + } + searchMemoriesByEmbedding(_embedding: number[], _params: { match_threshold?: number; count?: number; roomId?: UUID; agentId?: UUID; unique?: boolean; tableName: string; }): Promise { + throw new Error('Method not implemented.'); + } + createMemory(_memory: Memory, _tableName: string, _unique?: boolean): Promise { + throw new Error('Method not implemented.'); + } + removeMemory(_memoryId: UUID, _tableName: string): Promise { + throw new Error('Method not implemented.'); + } + removeAllMemories(_roomId: UUID, _tableName: string): Promise { + throw new Error('Method not implemented.'); + } + countMemories(_roomId: UUID, _unique?: boolean, _tableName?: string): Promise { + throw new Error('Method not implemented.'); + } + getGoals(_params: { roomId: UUID; userId?: UUID | null; onlyInProgress?: boolean; count?: number; }): Promise { + throw new Error('Method not implemented.'); + } + updateGoal(_goal: Goal): Promise { + throw new Error('Method not implemented.'); + } + createGoal(_goal: Goal): Promise { + throw new Error('Method not implemented.'); + } + removeGoal(_goalId: UUID): Promise { + throw new Error('Method not implemented.'); + } + removeAllGoals(_roomId: UUID): Promise { + throw new Error('Method not implemented.'); + } + getRoom(_roomId: UUID): Promise { + throw new Error('Method not implemented.'); + } + createRoom(_roomId?: UUID): Promise { + throw new Error('Method not implemented.'); + } + removeRoom(_roomId: UUID): Promise { + throw new Error('Method not implemented.'); + } + getRoomsForParticipant(_userId: UUID): Promise { + throw new Error('Method not implemented.'); + } + getRoomsForParticipants(_userIds: UUID[]): Promise { + throw new Error('Method not implemented.'); + } + addParticipant(_userId: UUID, _roomId: UUID): Promise { + throw new Error('Method not implemented.'); + } + removeParticipant(_userId: UUID, _roomId: UUID): Promise { + throw new Error('Method not implemented.'); + } + getParticipantsForAccount(userId: UUID): Promise; + getParticipantsForAccount(userId: UUID): Promise; + getParticipantsForAccount(_userId: unknown): Promise { + throw new Error('Method not implemented.'); + } + getParticipantsForRoom(_roomId: UUID): Promise { + throw new Error('Method not implemented.'); + } + getParticipantUserState(_roomId: UUID, _userId: UUID): Promise<'FOLLOWED' | 'MUTED' | null> { + throw new Error('Method not implemented.'); + } + setParticipantUserState(_roomId: UUID, _userId: UUID, _state: 'FOLLOWED' | 'MUTED' | null): Promise { + throw new Error('Method not implemented.'); + } + createRelationship(_params: { userA: UUID; userB: UUID; }): Promise { + throw new Error('Method not implemented.'); + } + getRelationship(_params: { userA: UUID; userB: UUID; }): Promise { + throw new Error('Method not implemented.'); + } + getRelationships(_params: { userId: UUID; }): Promise { + throw new Error('Method not implemented.'); + } + db: any = {}; + + // Mock method for getting memories by room IDs + async getMemoriesByRoomIds(params: { roomIds: `${string}-${string}-${string}-${string}-${string}`[]; agentId?: `${string}-${string}-${string}-${string}-${string}`; tableName: string }): Promise { + return [{ + id: 'memory-id' as UUID, + content: 'Test Memory', + roomId: params.roomIds[0], + userId: 'user-id' as UUID, + agentId: params.agentId ?? 'agent-id' as UUID + }] as unknown as Memory[]; + } + + // Mock method for getting cached embeddings + async getCachedEmbeddings(_params: { query_table_name: string; query_threshold: number; query_input: string; query_field_name: string; query_field_sub_name: string; query_match_count: number }): Promise { + return [{ + embedding: [0.1, 0.2, 0.3], + levenshtein_distance: 0.4 + }]; + } + + // Mock method for searching memories + async searchMemories(params: { tableName: string; roomId: `${string}-${string}-${string}-${string}-${string}`; embedding: number[]; match_threshold: number; match_count: number; unique: boolean }): Promise { + return [{ + id: 'memory-id' as UUID, + content: 'Test Memory', + roomId: params.roomId, + userId: 'user-id' as UUID, + agentId: 'agent-id' as UUID + }] as unknown as Memory[]; + } + + // Mock method for getting account by ID + async getAccountById(userId: UUID): Promise { + return { id: userId, username: 'testuser', name: 'Test Account' } as Account; + } + + // Other methods stay the same... + async createAccount(_account: Account): Promise { + return true; + } + + async getMemories(params: { roomId: UUID; count?: number; unique?: boolean; tableName: string }): Promise { + return [{ + id: 'memory-id' as UUID, + content: 'Test Memory', + roomId: params.roomId, + userId: 'user-id' as UUID, + agentId: 'agent-id' as UUID + }] as unknown as Memory[]; + } + + async getActors(_params: { roomId: UUID }): Promise { + return [{ + id: 'actor-id' as UUID, + name: 'Test Actor', + username: 'testactor', + roomId: 'room-id' as UUID // Ensure roomId is provided + }] as unknown as Actor[]; + } + + async updateGoalStatus(_params: { goalId: UUID, status: GoalStatus }): Promise { + return Promise.resolve(); + } + + async getGoalById(goalId: UUID): Promise { + return { + id: goalId, + status: GoalStatus.IN_PROGRESS, + roomId: 'room-id' as UUID, + userId: 'user-id' as UUID, + name: 'Test Goal', + objectives: [] + } as Goal; + } +} + +// Now, let’s fix the test suite. + +describe('DatabaseAdapter Tests', () => { + let adapter: MockDatabaseAdapter; + const roomId = 'room-id' as UUID; + + beforeEach(() => { + adapter = new MockDatabaseAdapter(); + }); + + it('should return memories by room ID', async () => { + const memories = await adapter.getMemoriesByRoomIds({ + roomIds: ['room-id' as `${string}-${string}-${string}-${string}-${string}`], + tableName: 'test_table' + }); + expect(memories).toHaveLength(1); + expect(memories[0].roomId).toBe('room-id'); + }); + + it('should return cached embeddings', async () => { + const embeddings = await adapter.getCachedEmbeddings({ + query_table_name: 'test_table', + query_threshold: 0.5, + query_input: 'test query', + query_field_name: 'field', + query_field_sub_name: 'subfield', + query_match_count: 5 + }); + expect(embeddings).toHaveLength(1); + expect(embeddings[0].embedding).toEqual([0.1, 0.2, 0.3]); + }); + + it('should search memories based on embedding', async () => { + const memories = await adapter.searchMemories({ + tableName: 'test_table', + roomId: 'room-id' as `${string}-${string}-${string}-${string}-${string}`, + embedding: [0.1, 0.2, 0.3], + match_threshold: 0.5, + match_count: 3, + unique: true + }); + expect(memories).toHaveLength(1); + expect(memories[0].roomId).toBe('room-id'); + }); + + it('should get an account by user ID', async () => { + const account = await adapter.getAccountById('test-user-id' as UUID); + expect(account).not.toBeNull(); + expect(account.username).toBe('testuser'); + }); + + it('should create a new account', async () => { + const newAccount: Account = { id: 'new-user-id' as UUID, username: 'newuser', name: 'New Account' }; + const result = await adapter.createAccount(newAccount); + expect(result).toBe(true); + }); + + it('should update the goal status', async () => { + const goalId = 'goal-id' as UUID; + await expect(adapter.updateGoalStatus({ goalId, status: GoalStatus.IN_PROGRESS })).resolves.toBeUndefined(); + }); + + it('should return actors by room ID', async () => { + const actors = await adapter.getActors({ roomId }); + expect(actors).toHaveLength(1); + }); + + it('should get a goal by ID', async () => { + const goalId = 'goal-id' as UUID; + const goal = await adapter.getGoalById(goalId); + expect(goal).not.toBeNull(); + expect(goal?.status).toBe(GoalStatus.IN_PROGRESS); + }); +}); diff --git a/packages/core/src/tests/defaultCharacters.test.ts b/packages/core/src/tests/defaultCharacters.test.ts new file mode 100644 index 0000000000..32b916b741 --- /dev/null +++ b/packages/core/src/tests/defaultCharacters.test.ts @@ -0,0 +1,48 @@ +import { defaultCharacter } from '../defaultCharacter'; +import { ModelProviderName } from '../types'; + +describe('defaultCharacter', () => { + it('should have the correct name', () => { + expect(defaultCharacter.name).toBe('Eliza'); + }); + + it('should have an empty plugins array', () => { + expect(defaultCharacter.plugins).toEqual([]); + }); + + it('should have an empty clients array', () => { + expect(defaultCharacter.clients).toEqual([]); + }); + + it('should have the correct modelProvider', () => { + expect(defaultCharacter.modelProvider).toBe(ModelProviderName.OPENAI); + }); + + it('should have the correct voice model', () => { + expect(defaultCharacter.settings.voice.model).toBe('en_US-hfc_female-medium'); + }); + + it('should have a system description', () => { + expect(defaultCharacter.system).toBe('Roleplay and generate interesting on behalf of Eliza.'); + }); + + it('should have a bio array with at least one entry', () => { + expect(defaultCharacter.bio.length).toBeGreaterThan(0); + }); + + it('should have a lore array with at least one entry', () => { + expect(defaultCharacter.lore.length).toBeGreaterThan(0); + }); + + it('should have messageExamples array with at least one example', () => { + expect(defaultCharacter.messageExamples.length).toBeGreaterThan(0); + }); + + it('should have a topics array with at least one broad topic', () => { + expect(defaultCharacter.topics).toContain('metaphysics'); + }); + + it('should have style settings with "all" array', () => { + expect(defaultCharacter.style.all.length).toBeGreaterThan(0); + }); +}); diff --git a/packages/core/src/tests/evaluators.test.ts b/packages/core/src/tests/evaluators.test.ts new file mode 100644 index 0000000000..ef9c2995c1 --- /dev/null +++ b/packages/core/src/tests/evaluators.test.ts @@ -0,0 +1,80 @@ +import { formatEvaluatorNames, formatEvaluators, formatEvaluatorExamples, formatEvaluatorExampleDescriptions } from '../evaluators'; +import { Evaluator, HandlerCallback, IAgentRuntime, Memory, State } from '../types'; + +// Mock data for evaluators +const mockEvaluators: Evaluator[] = [ + { + name: "Evaluator1", + description: "This is the first evaluator.", + examples: [ + { + context: "Context 1 with {{user1}}.", + outcome: "Outcome 1 with {{user1}}.", + messages: [ + { user: "user1", content: { text: "Message 1", action: "action1" } }, + { user: "user2", content: { text: "Message 2" } }, + ], + }, + ], + similes: [], + handler: function (_runtime: IAgentRuntime, _message: Memory, _state?: State, _options?: { [key: string]: unknown; }, _callback?: HandlerCallback): Promise { + throw new Error('Function not implemented.'); + }, + validate: function (_runtime: IAgentRuntime, _message: Memory, _state?: State): Promise { + throw new Error('Function not implemented.'); + } + }, + { + name: "Evaluator2", + description: "This is the second evaluator.", + examples: [ + { + context: "Context 2 with {{user1}} and {{user2}}.", + outcome: "Outcome 2 with {{user1}} and {{user2}}.", + messages: [ + { user: "user1", content: { text: "Message 1", action: "action1" } }, + { user: "user2", content: { text: "Message 2" } }, + ], + }, + ], + similes: [], + handler: function (_runtime: IAgentRuntime, _message: Memory, _state?: State, _options?: { [key: string]: unknown; }, _callback?: HandlerCallback): Promise { + throw new Error('Function not implemented.'); + }, + validate: function (_runtime: IAgentRuntime, _message: Memory, _state?: State): Promise { + throw new Error('Function not implemented.'); + } + }, +]; + +// Unit test for formatEvaluatorNames +test('formats evaluator names correctly', () => { + const result = formatEvaluatorNames(mockEvaluators); + expect(result).toBe("'Evaluator1',\n'Evaluator2'"); +}); + +// Unit test for formatEvaluators +test('formats evaluators correctly', () => { + const result = formatEvaluators(mockEvaluators); + expect(result).toBe( + "'Evaluator1: This is the first evaluator.',\n'Evaluator2: This is the second evaluator.'" + ); +}); + +// Unit test for formatEvaluatorExamples +test('formats evaluator examples correctly', () => { + const result = formatEvaluatorExamples(mockEvaluators); + expect(result).toContain('Context:\nContext 1 with'); + expect(result).toContain('Outcome:\nOutcome 1 with'); + expect(result).toContain('Messages:\nuser1: Message 1 (action1)'); +}); + +// Unit test for formatEvaluatorExampleDescriptions +test('formats evaluator example descriptions correctly', () => { + const result = formatEvaluatorExampleDescriptions(mockEvaluators); + expect(result).toBe( + "Evaluator1 Example 1: This is the first evaluator.\n\nEvaluator2 Example 1: This is the second evaluator." + ); +}); + +// Additional tests can be added to ensure edge cases and larger inputs are handled diff --git a/packages/core/src/tests/goals.test.ts b/packages/core/src/tests/goals.test.ts new file mode 100644 index 0000000000..65f0adde2d --- /dev/null +++ b/packages/core/src/tests/goals.test.ts @@ -0,0 +1,209 @@ +import { getGoals, formatGoalsAsString, updateGoal, createGoal } from "../goals"; +import { type Goal, type IAgentRuntime, type UUID, Action, GoalStatus, HandlerCallback, IMemoryManager, Memory, ModelProviderName, Service, State } from "../types"; + +// Mock the database adapter +const mockDatabaseAdapter = { + getGoals: jest.fn(), + updateGoal: jest.fn(), + createGoal: jest.fn(), +}; + +// Mock the runtime +const mockRuntime: IAgentRuntime = { + databaseAdapter: mockDatabaseAdapter as any, + agentId: "qweqew-qweqwe-qweqwe-qweqwe-qweeqw", + serverUrl: "", + token: "", + modelProvider: ModelProviderName.OPENAI, + character: { + id: "qweqew-qweqwe-qweqwe-qweqwe-qweeqw", + name: "", + system: "", + modelProvider: ModelProviderName.OPENAI, + modelEndpointOverride: "", + templates: {}, + bio: "", + lore: [], + messageExamples: [], + postExamples: [], + people: [], + topics: [], + adjectives: [], + knowledge: [], + clients: [], + plugins: [], + settings: { + secrets: {}, + voice: { + model: "", + url: "" + }, + model: "", + embeddingModel: "" + }, + clientConfig: { + discord: { + shouldIgnoreBotMessages: false, + shouldIgnoreDirectMessages: false + }, + telegram: { + shouldIgnoreBotMessages: false, + shouldIgnoreDirectMessages: false + } + }, + style: { + all: [], + chat: [], + post: [] + } + }, + providers: [], + actions: [], + evaluators: [], + messageManager: undefined, + descriptionManager: undefined, + loreManager: undefined, + services: undefined, + registerMemoryManager: function (_manager: IMemoryManager): void { + throw new Error("Function not implemented."); + }, + getMemoryManager: function (_name: string): IMemoryManager | null { + throw new Error("Function not implemented."); + }, + registerService: function (_service: Service): void { + throw new Error("Function not implemented."); + }, + getSetting: function (_key: string): string | null { + throw new Error("Function not implemented."); + }, + getConversationLength: function (): number { + throw new Error("Function not implemented."); + }, + processActions: function (_message: Memory, _responses: Memory[], _state?: State, _callback?: HandlerCallback): Promise { + throw new Error("Function not implemented."); + }, + evaluate: function (_message: Memory, _state?: State, _didRespond?: boolean): Promise { + throw new Error("Function not implemented."); + }, + ensureParticipantExists: function (_userId: UUID, _roomId: UUID): Promise { + throw new Error("Function not implemented."); + }, + ensureUserExists: function (_userId: UUID, _userName: string | null, _name: string | null, _source: string | null): Promise { + throw new Error("Function not implemented."); + }, + registerAction: function (_action: Action): void { + throw new Error("Function not implemented."); + }, + ensureConnection: function (_userId: UUID, _roomId: UUID, _userName?: string, _userScreenName?: string, _source?: string): Promise { + throw new Error("Function not implemented."); + }, + ensureParticipantInRoom: function (_userId: UUID, _roomId: UUID): Promise { + throw new Error("Function not implemented."); + }, + ensureRoomExists: function (_roomId: UUID): Promise { + throw new Error("Function not implemented."); + }, + composeState: function (_message: Memory, _additionalKeys?: { [key: string]: unknown; }): Promise { + throw new Error("Function not implemented."); + }, + updateRecentMessageState: function (_state: State): Promise { + throw new Error("Function not implemented."); + }, + getService: function (_service: string): typeof Service | null { + throw new Error("Function not implemented."); + } +}; + +// Sample data +const sampleGoal: Goal = { + id: "goal-id" as UUID, + name: "Test Goal", + roomId: "room-id" as UUID, + userId: "user-id" as UUID, + objectives: [ + { description: "Objective 1", completed: false }, + { description: "Objective 2", completed: true }, + ], + status: GoalStatus.IN_PROGRESS, +}; + +describe("getGoals", () => { + it("retrieves goals successfully", async () => { + (mockDatabaseAdapter.getGoals as jest.Mock).mockResolvedValue([sampleGoal]); + + const result = await getGoals({ + runtime: mockRuntime, + roomId: "room-id" as UUID, + }); + + expect(result).toEqual([sampleGoal]); + expect(mockDatabaseAdapter.getGoals).toHaveBeenCalledWith({ + roomId: "room-id", + userId: undefined, + onlyInProgress: true, + count: 5, + }); + }); + + it("handles failure to retrieve goals", async () => { + (mockDatabaseAdapter.getGoals as jest.Mock).mockRejectedValue( + new Error("Failed to retrieve goals") + ); + + await expect( + getGoals({ runtime: mockRuntime, roomId: "room-id" as UUID }) + ).rejects.toThrow("Failed to retrieve goals"); + }); +}); + +describe("formatGoalsAsString", () => { + it("formats goals correctly", () => { + const formatted = formatGoalsAsString({ goals: [sampleGoal] }); + expect(formatted).toContain("Goal: Test Goal"); + expect(formatted).toContain("- [ ] Objective 1 (IN PROGRESS)"); + expect(formatted).toContain("- [x] Objective 2 (DONE)"); + }); + + it("handles empty goal list", () => { + const formatted = formatGoalsAsString({ goals: [] }); + expect(formatted).toBe(""); + }); +}); + +describe("updateGoal", () => { + it("updates a goal successfully", async () => { + (mockDatabaseAdapter.updateGoal as jest.Mock).mockResolvedValue(undefined); + + await expect(updateGoal({ runtime: mockRuntime, goal: sampleGoal })).resolves.toBeUndefined(); + expect(mockDatabaseAdapter.updateGoal).toHaveBeenCalledWith(sampleGoal); + }); + + it("handles failure to update a goal", async () => { + (mockDatabaseAdapter.updateGoal as jest.Mock).mockRejectedValue( + new Error("Failed to update goal") + ); + + await expect(updateGoal({ runtime: mockRuntime, goal: sampleGoal })).rejects.toThrow( + "Failed to update goal" + ); + }); +}); + +describe("createGoal", () => { + it("creates a goal successfully", async () => { + (mockDatabaseAdapter.createGoal as jest.Mock).mockResolvedValue(undefined); + + await expect(createGoal({ runtime: mockRuntime, goal: sampleGoal })).resolves.toBeUndefined(); + expect(mockDatabaseAdapter.createGoal).toHaveBeenCalledWith(sampleGoal); + }); + + it("handles failure to create a goal", async () => { + (mockDatabaseAdapter.createGoal as jest.Mock).mockRejectedValue( + new Error("Failed to create goal") + ); + + await expect(createGoal({ runtime: mockRuntime, goal: sampleGoal })).rejects.toThrow( + "Failed to create goal" + ); + }); +}); diff --git a/packages/core/src/tests/messages.test.ts b/packages/core/src/tests/messages.test.ts new file mode 100644 index 0000000000..10f6db22e6 --- /dev/null +++ b/packages/core/src/tests/messages.test.ts @@ -0,0 +1,137 @@ +import { formatActors, formatMessages, getActorDetails, formatTimestamp } from "../messages.ts"; +import { IAgentRuntime, Actor, Content, Memory, UUID } from "../types.ts"; + +describe("Messages Library", () => { + let runtime: IAgentRuntime; + let actors: Actor[]; + let userId: UUID; + + beforeAll(() => { + // Mock runtime with necessary methods + runtime = { + databaseAdapter: { + // Casting to a Jest mock function + getParticipantsForRoom: jest.fn(), + getAccountById: jest.fn(), + }, + } as unknown as IAgentRuntime; + + // Mock user data with proper UUID format + userId = "12345678-1234-1234-1234-123456789abc" as UUID; + actors = [ + { + id: userId, + name: "Test User", + username: "testuser", + details: { + tagline: "A test user", + summary: "This is a test user for the system.", + quote: "" + }, + }, + ]; + }); + + test("getActorDetails should return actors based on roomId", async () => { + // Mocking the database adapter methods + const roomId: UUID = "room1234-1234-1234-1234-123456789abc" as UUID; + + // Properly mocking the resolved values of the mocked methods + (runtime.databaseAdapter.getParticipantsForRoom as jest.Mock).mockResolvedValue([userId]); + (runtime.databaseAdapter.getAccountById as jest.Mock).mockResolvedValue({ + id: userId, + name: "Test User", + username: "testuser", + details: { + tagline: "A test user", + summary: "This is a test user for the system.", + }, + }); + + // Calling the function under test + const result = await getActorDetails({ runtime, roomId }); + + // Assertions + expect(result.length).toBeGreaterThan(0); + expect(result[0].name).toBe("Test User"); + expect(result[0].details?.tagline).toBe("A test user"); + }); + + test("formatActors should format actors into a readable string", () => { + const formattedActors = formatActors({ actors }); + + // Assertions + expect(formattedActors).toContain("Test User"); + expect(formattedActors).toContain("A test user"); + expect(formattedActors).toContain("This is a test user for the system."); + }); + + test("formatMessages should format messages into a readable string", () => { + const messages: Memory[] = [ + { + content: { text: "Hello, world!" } as Content, + userId: userId, + roomId: "room1234-1234-1234-1234-123456789abc" as UUID, + createdAt: new Date().getTime(), + agentId: "" as UUID // assuming agentId is an empty string here + }, + ]; + + const formattedMessages = formatMessages({ messages, actors }); + + // Assertions + expect(formattedMessages).toContain("Hello, world!"); + expect(formattedMessages).toContain("Test User"); + }); + + test("formatTimestamp should return correct time string", () => { + const timestamp = new Date().getTime() - 60000; // 1 minute ago + const result = formatTimestamp(timestamp); + + // Assertions + expect(result).toBe("1 minute ago"); + }); + + test("formatMessages should include attachments if present", () => { + const messages: Memory[] = [ + { + content: { + text: "Check this attachment", + attachments: [ + { id: "1", title: "Image", url: "http://example.com/image.jpg" }, + ], + } as Content, + userId: userId, + roomId: "room1234-1234-1234-1234-123456789abc" as UUID, + createdAt: new Date().getTime(), + agentId: "" as UUID // assuming agentId is an empty string here + }, + ]; + + const formattedMessages = formatMessages({ messages, actors }); + + // Assertions + expect(formattedMessages).toContain("Check this attachment"); + expect(formattedMessages).toContain("Attachments: [1 - Image (http://example.com/image.jpg)]"); + }); + + test("formatMessages should handle empty attachments gracefully", () => { + const messages: Memory[] = [ + { + content: { + text: "No attachments here", + } as Content, + userId: userId, + roomId: "room1234-1234-1234-1234-123456789abc" as UUID, + createdAt: new Date().getTime(), + agentId: "" as UUID // assuming agentId is an empty string here + }, + ]; + + const formattedMessages = formatMessages({ messages, actors }); + + // Assertions + expect(formattedMessages).toContain("No attachments here"); + expect(formattedMessages).not.toContain("Attachments"); + }); +}); diff --git a/packages/core/src/tests/models.test.ts b/packages/core/src/tests/models.test.ts new file mode 100644 index 0000000000..66cbb101d7 --- /dev/null +++ b/packages/core/src/tests/models.test.ts @@ -0,0 +1,37 @@ +import { getModel, getEndpoint } from '../models.ts'; +import { ModelProviderName, ModelClass } from '../types.ts'; + +jest.mock('../settings', () => ({ + loadEnv: jest.fn(), // Mock the loadEnv function +})); + +describe('Model Provider Tests', () => { + test('should retrieve the correct model for OpenAI SMALL', () => { + const model = getModel(ModelProviderName.OPENAI, ModelClass.SMALL); + expect(model).toBe('gpt-4o-mini'); + }); + + test('should retrieve the correct model for Google MEDIUM', () => { + const model = getModel(ModelProviderName.GOOGLE, ModelClass.MEDIUM); + expect(model).toBe('gemini-1.5-flash-latest'); + }); + + test('should retrieve the correct model for Groq LARGE', () => { + const model = getModel(ModelProviderName.GROQ, ModelClass.LARGE); + expect(model).toBe('llama-3.2-90b-text-preview'); + }); + + test('should retrieve the correct endpoint for OpenAI', () => { + const endpoint = getEndpoint(ModelProviderName.OPENAI); + expect(endpoint).toBe('https://api.openai.com/v1'); + }); + + test('should retrieve the correct endpoint for Anthropic', () => { + const endpoint = getEndpoint(ModelProviderName.ANTHROPIC); + expect(endpoint).toBe('https://api.anthropic.com/v1'); + }); + + test('should handle invalid model provider', () => { + expect(() => getModel('INVALID_PROVIDER' as any, ModelClass.SMALL)).toThrow(); + }); +}); diff --git a/packages/core/src/tests/posts.test.ts b/packages/core/src/tests/posts.test.ts new file mode 100644 index 0000000000..f45dd73c6c --- /dev/null +++ b/packages/core/src/tests/posts.test.ts @@ -0,0 +1,100 @@ +import { formatPosts } from '../posts.ts'; +import { Actor, Memory } from '../types.ts'; + +// Mocked data with consistent conversation IDs +const mockActors: Actor[] = [ + { + id: 'f9c8b107-953b-473d-8c87-2894c6e3fe25', name: 'Alice', username: 'alice123', + details: { + tagline: 'The quick brown fox', + summary: 'Lorem ipsum dolor sit amet.', + quote: 'To be or not to be.' + } + }, + { + id: 'e4928cd1-8007-40b1-93ff-7c5da3c39e36', name: 'Bob', username: 'bob456', + details: { + tagline: 'A journey of a thousand miles', + summary: 'Sed ut perspiciatis unde omnis iste.', + quote: 'Knowledge is power.' + } + }, + { + id: 'b62e64da-5699-4c8e-b58c-8d447b2f2014', name: 'Charlie', username: 'charlie789', + details: { + tagline: 'Hello, world!', + summary: 'Lorem ipsum dolor sit.', + quote: 'Live and let live.' + } + }, +]; + +const mockMessages: Memory[] = [ + { + id: '0db429f4-9ad9-44db-b2c6-0cf6d6cb2dfe', + userId: 'f9c8b107-953b-473d-8c87-2894c6e3fe25', + roomId: 'aae8df56-e890-4876-a3ba-2cbfc94cbd97', + createdAt: 2000, + content: { text: 'Hi Bob, how are you?', inReplyTo: 'f9c8b107-953b-473d-8c87-2894c6e3fe25' }, + agentId: 'f9c8b107-953b-473d-8c87-2894c6e3fe25' + }, + { + id: 'cdb70b0f-bcfe-44ea-b940-1d7e7e981768', + userId: 'e4928cd1-8007-40b1-93ff-7c5da3c39e36', + roomId: 'aae8df56-e890-4876-a3ba-2cbfc94cbd97', + createdAt: 2500, + content: { text: 'Hi Alice, how are you?', inReplyTo: 'f9c8b107-953b-473d-8c87-2894c6e3fe25' }, + agentId: 'e4928cd1-8007-40b1-93ff-7c5da3c39e36' + }, + { + id: '88297c98-3d95-4ab5-9c88-b7f01e10f7a7', + userId: 'b62e64da-5699-4c8e-b58c-8d447b2f2014', + roomId: 'c57bc580-dabf-4e56-9526-1ca1982f1d0c', + createdAt: 1500, + content: { text: 'Hello, how’s it going?', inReplyTo: null }, + agentId: 'b62e64da-5699-4c8e-b58c-8d447b2f2014' + }, + { + id: 'f9c8f0f5-2aef-4a07-96d8-43b980cb7325', + userId: 'f9c8b107-953b-473d-8c87-2894c6e3fe25', + roomId: 'aae8df56-e890-4876-a3ba-2cbfc94cbd97', + createdAt: 3000, + content: { text: 'Let’s catch up later.', inReplyTo: 'e4928cd1-8007-40b1-93ff-7c5da3c39e36' }, + agentId: 'f9c8b107-953b-473d-8c87-2894c6e3fe25' + }, +]; + +// Unit tests for formatPosts +test('formats posts correctly with conversation header', () => { + const result = formatPosts({ + messages: mockMessages, + actors: mockActors, + conversationHeader: true, + }); + + expect(result).toContain('Name: Alice (@alice123)'); + expect(result).toContain('ID: 0db429f4-9ad9-44db-b2c6-0cf6d6cb2dfe'); + expect(result).toContain('In reply to: f9c8b107-953b-473d-8c87-2894c6e3fe25'); + expect(result).toContain('Text:\nHi Bob, how are you?'); +}); + +test('formats posts correctly with multiple rooms', () => { + const result = formatPosts({ + messages: mockMessages, + actors: mockActors, + conversationHeader: true, + }); + + expect(result).toContain('Name: Alice (@alice123)'); + expect(result).toContain('Text:\nHello, how’s it going?'); +}); + +test('handles empty messages array', () => { + const result = formatPosts({ + messages: [], + actors: mockActors, + conversationHeader: true, + }); + + expect(result).toBe(''); +}); diff --git a/packages/core/src/tests/providers.test.ts b/packages/core/src/tests/providers.test.ts new file mode 100644 index 0000000000..c031a89ab2 --- /dev/null +++ b/packages/core/src/tests/providers.test.ts @@ -0,0 +1,139 @@ +import { getProviders } from "../providers"; +import { IAgentRuntime, type Memory, type State, type Provider, UUID } from "../types.ts"; + +describe("getProviders", () => { + let runtime: IAgentRuntime; + let roomId: UUID; + + // Mock providers for testing + const MockProvider1: Provider = { + get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { + return "Response from Provider 1"; + }, + }; + + const MockProvider2: Provider = { + get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { + return "Response from Provider 2"; + }, + }; + + const MockProvider3: Provider = { + get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { + return "Response from Provider 3"; + }, + }; + + beforeAll(() => { + // Initialize the runtime with mock providers + runtime = { + providers: [MockProvider1, MockProvider2, MockProvider3], + } as IAgentRuntime; + roomId = "00000000-0000-0000-0000-000000000000" as UUID; // Example UUID + }); + + test("getProviders should call all provider get methods and return concatenated responses", async () => { + const message: Memory = { + userId: "00000000-0000-0000-0000-000000000001", + content: { text: "" }, + roomId: roomId, + agentId: "00000000-0000-0000-0000-000000000002" + }; + + const result = await getProviders(runtime, message, {} as State); + + // Check if the responses are concatenated correctly with newline separators + expect(result).toBe( + "Response from Provider 1\nResponse from Provider 2\nResponse from Provider 3" + ); + }); + + test("getProviders should handle an empty provider list", async () => { + runtime.providers = []; + + const message: Memory = { + userId: "00000000-0000-0000-0000-000000000001", + content: { text: "" }, + roomId: roomId, + agentId: "00000000-0000-0000-0000-000000000002" + }; + + const result = await getProviders(runtime, message, {} as State); + + // No providers, should return an empty string + expect(result).toBe(""); + }); + + test("getProviders should handle providers returning undefined", async () => { + const MockProviderWithUndefinedResponse: Provider = { + get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { + return undefined; // Simulate undefined return + }, + }; + + runtime.providers = [MockProviderWithUndefinedResponse]; + + const message: Memory = { + userId: "00000000-0000-0000-0000-000000000001", + content: { text: "" }, + roomId: roomId, + agentId: "00000000-0000-0000-0000-000000000002" + }; + + const result = await getProviders(runtime, message, {} as State); + + // Should handle undefined return and result in empty string for that provider + expect(result).toBe(""); + }); + + test("getProviders should concatenate valid responses and ignore undefined", async () => { + const MockProviderWithValidAndUndefinedResponse: Provider = { + get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { + return "Valid response"; + }, + }; + + const MockProviderWithUndefinedResponse: Provider = { + get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { + return undefined; + }, + }; + + runtime.providers = [ + MockProviderWithValidAndUndefinedResponse, + MockProviderWithUndefinedResponse, + ]; + + const message: Memory = { + userId: "00000000-0000-0000-0000-000000000001", + content: { text: "" }, + roomId: roomId, + agentId: "00000000-0000-0000-0000-000000000002" + }; + + const result = await getProviders(runtime, message, {} as State); + + // Only the valid response should be concatenated, ignoring undefined + expect(result).toContain("Valid response"); + }); + + test("getProviders should handle error if one provider fails", async () => { + const MockProviderThatThrows: Provider = { + get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { + throw new Error("Provider error"); + }, + }; + + const message: Memory = { + userId: "00000000-0000-0000-0000-000000000001", + content: { text: "" }, + roomId: roomId, + agentId: "00000000-0000-0000-0000-000000000002" + }; + + runtime.providers = [MockProviderThatThrows, MockProvider1]; + + // Expect an error from the first provider but still get the response from the second provider + await expect(getProviders(runtime, message, {} as State)).rejects.toThrow("Provider error"); + }); +}); diff --git a/packages/core/src/tests/relationships.test.ts b/packages/core/src/tests/relationships.test.ts new file mode 100644 index 0000000000..d8b94c8ba8 --- /dev/null +++ b/packages/core/src/tests/relationships.test.ts @@ -0,0 +1,167 @@ +import { + createRelationship, + getRelationship, + getRelationships, + formatRelationships, +} from "../relationships"; +import { IAgentRuntime, type Relationship, type UUID } from "../types"; + +// Mock runtime and databaseAdapter +const mockDatabaseAdapter = { + createRelationship: jest.fn(), + getRelationship: jest.fn(), + getRelationships: jest.fn(), +}; +const mockRuntime: IAgentRuntime = { + databaseAdapter: mockDatabaseAdapter, +} as unknown as IAgentRuntime; + +describe("Relationships Module", () => { + // Helper function to generate random UUIDs + const generateRandomUUID = (): UUID => crypto.randomUUID() as UUID; + + // Randomized UUIDs for each test run + const mockUserA: UUID = generateRandomUUID(); + const mockUserB: UUID = generateRandomUUID(); + const mockUserId: UUID = generateRandomUUID(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("createRelationship", () => { + it("should call createRelationship on the databaseAdapter with correct parameters", async () => { + mockDatabaseAdapter.createRelationship.mockResolvedValue(true); + + const result = await createRelationship({ + runtime: mockRuntime, + userA: mockUserA, + userB: mockUserB, + }); + + expect(mockDatabaseAdapter.createRelationship).toHaveBeenCalledWith({ + userA: mockUserA, + userB: mockUserB, + }); + expect(result).toBe(true); + }); + + it("should handle errors from databaseAdapter", async () => { + mockDatabaseAdapter.createRelationship.mockRejectedValue( + new Error("Database error") + ); + + await expect( + createRelationship({ + runtime: mockRuntime, + userA: mockUserA, + userB: mockUserB, + }) + ).rejects.toThrow("Database error"); + }); + }); + + describe("getRelationship", () => { + it("should call getRelationship on the databaseAdapter with correct parameters", async () => { + const mockRelationship: Relationship = { + userA: mockUserA, + userB: mockUserB, + id: generateRandomUUID(), + userId: generateRandomUUID(), + roomId: generateRandomUUID(), + status: "STATUS" + }; + mockDatabaseAdapter.getRelationship.mockResolvedValue(mockRelationship); + + const result = await getRelationship({ + runtime: mockRuntime, + userA: mockUserA, + userB: mockUserB, + }); + + expect(mockDatabaseAdapter.getRelationship).toHaveBeenCalledWith({ + userA: mockUserA, + userB: mockUserB, + }); + expect(result).toEqual(mockRelationship); + }); + }); + + describe("getRelationships", () => { + it("should call getRelationships on the databaseAdapter with correct parameters", async () => { + const mockRelationships: Relationship[] = [ + { + userA: mockUserA, userB: mockUserB, + id: generateRandomUUID(), + userId: generateRandomUUID(), + roomId: generateRandomUUID(), + status: generateRandomUUID() + }, + { + userA: mockUserB, userB: mockUserId, + id: generateRandomUUID(), + userId: generateRandomUUID(), + roomId: generateRandomUUID(), + status: "" + }, + ]; + mockDatabaseAdapter.getRelationships.mockResolvedValue(mockRelationships); + + const result = await getRelationships({ + runtime: mockRuntime, + userId: mockUserA, + }); + + expect(mockDatabaseAdapter.getRelationships).toHaveBeenCalledWith({ + userId: mockUserA, + }); + expect(result).toEqual(mockRelationships); + }); + }); + + describe("formatRelationships", () => { + it("should format relationships correctly", async () => { + const mockRelationships: Relationship[] = [ + { + userA: mockUserA, userB: mockUserB, + id: generateRandomUUID(), + userId: generateRandomUUID(), + roomId: generateRandomUUID(), + status: "STATUS" + }, + { + userA: mockUserB, userB: mockUserId, + id: generateRandomUUID(), + userId: generateRandomUUID(), + roomId: generateRandomUUID(), + status: "STATUS" + }, + ]; + mockDatabaseAdapter.getRelationships.mockResolvedValue(mockRelationships); + + const result = await formatRelationships({ + runtime: mockRuntime, + userId: mockUserA, + }); + + expect(mockDatabaseAdapter.getRelationships).toHaveBeenCalledWith({ + userId: mockUserA, + }); + expect(result[0]).toEqual(mockUserB); + }); + + it("should return an empty array if no relationships exist", async () => { + mockDatabaseAdapter.getRelationships.mockResolvedValue([]); + + const result = await formatRelationships({ + runtime: mockRuntime, + userId: mockUserId, + }); + + expect(mockDatabaseAdapter.getRelationships).toHaveBeenCalledWith({ + userId: mockUserId, + }); + expect(result).toEqual([]); + }); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 84cf46532c..636eff9b6f 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -21,11 +21,7 @@ "noEmitOnError": false, "moduleDetection": "force", "allowArbitraryExtensions": true, - "typeRoots": [ - "./node_modules/@types", - "./types", - "./node_modules/jest/types" - ] + "types": ["jest"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "src/**/*.d.ts", "types/**/*.test.ts"]