diff --git a/packages/core/package.json b/packages/core/package.json index 08b459287c..b0b5b4dedd 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,7 +38,6 @@ "eslint": "9.13.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.2.1", - "eslint-plugin-vitest": "0.5.4", "jest": "29.7.0", "lint-staged": "15.2.10", "nodemon": "3.1.7", diff --git a/packages/core/src/tests/cache.test.ts b/packages/core/src/tests/cache.test.ts index e9a3bbde6e..339a032e7b 100644 --- a/packages/core/src/tests/cache.test.ts +++ b/packages/core/src/tests/cache.test.ts @@ -1,15 +1,17 @@ -import { CacheManager, MemoryCacheAdapter } from "../cache.ts"; // Adjust the import based on your project structure +import { CacheManager, MemoryCacheAdapter } from "../cache.ts"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -// Now, let’s fix the test suite. - -describe.only("CacheManager", () => { +describe("CacheManager", () => { let cache: CacheManager; - jest.useFakeTimers(); - beforeEach(() => { + vi.useFakeTimers(); cache = new CacheManager(new MemoryCacheAdapter()); - jest.setSystemTime(Date.now()); + vi.setSystemTime(Date.now()); + }); + + afterEach(() => { + vi.useRealTimers(); }); it("should set/get/delete cache", async () => { @@ -17,20 +19,15 @@ describe.only("CacheManager", () => { expect(await cache.get("foo")).toEqual("bar"); - expect(cache.adapter.data.get("foo")).toEqual( - JSON.stringify({ value: "bar", expires: 0 }) - ); - await cache.delete("foo"); expect(await cache.get("foo")).toEqual(undefined); - expect(cache.adapter.data.get("foo")).toEqual(undefined); }); - it("should set/get/delete cache with expiration", async () => { - const expires = Date.now() + 5 * 1000; + it("should handle expiring cache", async () => { + const expires = Date.now() + 1000; - await cache.set("foo", "bar", { expires: expires }); + await cache.set("foo", "bar", { expires }); expect(await cache.get("foo")).toEqual("bar"); @@ -38,7 +35,7 @@ describe.only("CacheManager", () => { JSON.stringify({ value: "bar", expires: expires }) ); - jest.setSystemTime(expires + 1000); + vi.setSystemTime(expires + 1000); expect(await cache.get("foo")).toEqual(undefined); expect(cache.adapter.data.get("foo")).toEqual(undefined); diff --git a/packages/core/src/tests/defaultCharacters.test.ts b/packages/core/src/tests/defaultCharacters.test.ts index e22f9d592a..cd3f4cb9cf 100644 --- a/packages/core/src/tests/defaultCharacters.test.ts +++ b/packages/core/src/tests/defaultCharacters.test.ts @@ -15,7 +15,7 @@ describe("defaultCharacter", () => { }); it("should have the correct modelProvider", () => { - expect(defaultCharacter.modelProvider).toBe(ModelProviderName.OPENAI); + expect(defaultCharacter.modelProvider).toBe(ModelProviderName.OLLAMA); }); it("should have the correct voice model", () => { diff --git a/packages/core/src/tests/goals.test.ts b/packages/core/src/tests/goals.test.ts index 101d5956df..130ff2e848 100644 --- a/packages/core/src/tests/goals.test.ts +++ b/packages/core/src/tests/goals.test.ts @@ -1,26 +1,22 @@ import { CacheManager, MemoryCacheAdapter } from "../cache"; import { - createGoal, - formatGoalsAsString, getGoals, + formatGoalsAsString, updateGoal, -} from "../goals"; + createGoal, +} from "../goals.ts"; import { - type Goal, - type IAgentRuntime, - type UUID, - Action, + Goal, GoalStatus, - HandlerCallback, - IMemoryManager, + IAgentRuntime, Memory, - ModelProviderName, + State, + UUID, Service, ServiceType, - State, } from "../types"; - -import { describe, expect, vi } from "vitest"; +import { CacheManager, MemoryCacheAdapter } from "../cache.ts"; +import { describe, expect, vi, beforeEach } from "vitest"; // Mock the database adapter export const mockDatabaseAdapter = { @@ -28,125 +24,192 @@ export const mockDatabaseAdapter = { updateGoal: vi.fn(), createGoal: vi.fn(), }; + const services = new Map(); + // Mock the runtime export const mockRuntime: IAgentRuntime = { databaseAdapter: mockDatabaseAdapter as any, cacheManager: new CacheManager(new MemoryCacheAdapter()), 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: "", + token: null, + messageManager: { + addEmbeddingToMemory: function (_memory: Memory): Promise { + throw new Error("Function not implemented."); }, - clientConfig: { - discord: { - shouldIgnoreBotMessages: false, - shouldIgnoreDirectMessages: false, - }, - telegram: { - shouldIgnoreBotMessages: false, - shouldIgnoreDirectMessages: false, - }, + getMemories: function (_opts: { + roomId: UUID; + count?: number; + unique?: boolean; + agentId?: UUID; + start?: number; + end?: number; + }): Promise { + throw new Error("Function not implemented."); }, - style: { - all: [], - chat: [], - post: [], + getCachedEmbeddings: function ( + _content: string + ): Promise<{ embedding: number[]; levenshtein_score: number }[]> { + throw new Error("Function not implemented."); + }, + getMemoryById: function (_id: UUID): Promise { + throw new Error("Function not implemented."); + }, + getMemoriesByRoomIds: function (_params: { + roomIds: UUID[]; + agentId?: UUID; + }): Promise { + throw new Error("Function not implemented."); + }, + searchMemoriesByEmbedding: function ( + _embedding: number[], + _opts: { + match_threshold?: number; + count?: number; + roomId: UUID; + unique?: boolean; + agentId?: UUID; + } + ): Promise { + throw new Error("Function not implemented."); + }, + createMemory: function ( + _memory: Memory, + _unique?: boolean + ): Promise { + throw new Error("Function not implemented."); + }, + removeMemory: function (_memoryId: UUID): Promise { + throw new Error("Function not implemented."); + }, + removeAllMemories: function (_roomId: UUID): Promise { + throw new Error("Function not implemented."); + }, + countMemories: function ( + _roomId: UUID, + _unique?: boolean + ): Promise { + throw new Error("Function not implemented."); }, }, - 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 { - services.set(service.serviceType, service); - }, - 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."); + descriptionManager: { + addEmbeddingToMemory: function (_memory: Memory): Promise { + throw new Error("Function not implemented."); + }, + getMemories: function (_opts: { + roomId: UUID; + count?: number; + unique?: boolean; + agentId?: UUID; + start?: number; + end?: number; + }): Promise { + throw new Error("Function not implemented."); + }, + getCachedEmbeddings: function ( + _content: string + ): Promise<{ embedding: number[]; levenshtein_score: number }[]> { + throw new Error("Function not implemented."); + }, + getMemoryById: function (_id: UUID): Promise { + throw new Error("Function not implemented."); + }, + getMemoriesByRoomIds: function (_params: { + roomIds: UUID[]; + agentId?: UUID; + }): Promise { + throw new Error("Function not implemented."); + }, + searchMemoriesByEmbedding: function ( + _embedding: number[], + _opts: { + match_threshold?: number; + count?: number; + roomId: UUID; + unique?: boolean; + agentId?: UUID; + } + ): Promise { + throw new Error("Function not implemented."); + }, + createMemory: function ( + _memory: Memory, + _unique?: boolean + ): Promise { + throw new Error("Function not implemented."); + }, + removeMemory: function (_memoryId: UUID): Promise { + throw new Error("Function not implemented."); + }, + removeAllMemories: function (_roomId: UUID): Promise { + throw new Error("Function not implemented."); + }, + countMemories: function ( + _roomId: UUID, + _unique?: boolean + ): Promise { + throw new Error("Function not implemented."); + }, }, - ensureParticipantInRoom: function ( - _userId: UUID, - _roomId: UUID - ): Promise { - throw new Error("Function not implemented."); + loreManager: { + addEmbeddingToMemory: function (_memory: Memory): Promise { + throw new Error("Function not implemented."); + }, + getMemories: function (_opts: { + roomId: UUID; + count?: number; + unique?: boolean; + agentId?: UUID; + start?: number; + end?: number; + }): Promise { + throw new Error("Function not implemented."); + }, + getCachedEmbeddings: function ( + _content: string + ): Promise<{ embedding: number[]; levenshtein_score: number }[]> { + throw new Error("Function not implemented."); + }, + getMemoryById: function (_id: UUID): Promise { + throw new Error("Function not implemented."); + }, + getMemoriesByRoomIds: function (_params: { + roomIds: UUID[]; + agentId?: UUID; + }): Promise { + throw new Error("Function not implemented."); + }, + searchMemoriesByEmbedding: function ( + _embedding: number[], + _opts: { + match_threshold?: number; + count?: number; + roomId: UUID; + unique?: boolean; + agentId?: UUID; + } + ): Promise { + throw new Error("Function not implemented."); + }, + createMemory: function ( + _memory: Memory, + _unique?: boolean + ): Promise { + throw new Error("Function not implemented."); + }, + removeMemory: function (_memoryId: UUID): Promise { + throw new Error("Function not implemented."); + }, + removeAllMemories: function (_roomId: UUID): Promise { + throw new Error("Function not implemented."); + }, + countMemories: function ( + _roomId: UUID, + _unique?: boolean + ): Promise { + throw new Error("Function not implemented."); + }, }, ensureRoomExists: function (_roomId: UUID): Promise { throw new Error("Function not implemented."); @@ -165,14 +228,18 @@ export const mockRuntime: IAgentRuntime = { ): T | null { return (services.get(serviceType) as T) || null; }, + plugins: [], + initialize: function (): Promise { + 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, + name: "Test Goal", objectives: [ { description: "Objective 1", completed: false }, { description: "Objective 2", completed: true }, @@ -181,6 +248,10 @@ const sampleGoal: Goal = { }; describe("getGoals", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it("retrieves goals successfully", async () => { mockDatabaseAdapter.getGoals.mockResolvedValue([sampleGoal]); @@ -190,26 +261,27 @@ describe("getGoals", () => { }); expect(result).toEqual([sampleGoal]); - expect(mockDatabaseAdapter.getGoals).toHaveBeenCalledWith({ - roomId: "room-id", - userId: undefined, - onlyInProgress: true, - count: 5, - }); }); - it("handles failure to retrieve goals", async () => { + it("handles errors when retrieving goals", async () => { mockDatabaseAdapter.getGoals.mockRejectedValue( new Error("Failed to retrieve goals") ); await expect( - getGoals({ runtime: mockRuntime, roomId: "room-id" as UUID }) + getGoals({ + runtime: mockRuntime, + roomId: "room-id" as UUID, + }) ).rejects.toThrow("Failed to retrieve goals"); }); }); describe("formatGoalsAsString", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it("formats goals correctly", () => { const formatted = formatGoalsAsString({ goals: [sampleGoal] }); expect(formatted).toContain("Goal: Test Goal"); @@ -217,23 +289,28 @@ describe("formatGoalsAsString", () => { expect(formatted).toContain("- [x] Objective 2 (DONE)"); }); - it("handles empty goal list", () => { + it("handles empty goals array", () => { const formatted = formatGoalsAsString({ goals: [] }); expect(formatted).toBe(""); }); }); describe("updateGoal", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it("updates a goal successfully", async () => { mockDatabaseAdapter.updateGoal.mockResolvedValue(undefined); await expect( updateGoal({ runtime: mockRuntime, goal: sampleGoal }) - ).resolves.toBeUndefined(); + ).resolves.not.toThrow(); + expect(mockDatabaseAdapter.updateGoal).toHaveBeenCalledWith(sampleGoal); }); - it("handles failure to update a goal", async () => { + it("handles errors when updating a goal", async () => { mockDatabaseAdapter.updateGoal.mockRejectedValue( new Error("Failed to update goal") ); @@ -245,16 +322,21 @@ describe("updateGoal", () => { }); describe("createGoal", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + it("creates a goal successfully", async () => { mockDatabaseAdapter.createGoal.mockResolvedValue(undefined); await expect( createGoal({ runtime: mockRuntime, goal: sampleGoal }) - ).resolves.toBeUndefined(); + ).resolves.not.toThrow(); + expect(mockDatabaseAdapter.createGoal).toHaveBeenCalledWith(sampleGoal); }); - it("handles failure to create a goal", async () => { + it("handles errors when creating a goal", async () => { mockDatabaseAdapter.createGoal.mockRejectedValue( new Error("Failed to create goal") ); diff --git a/packages/core/src/tests/token.test.ts b/packages/core/src/tests/token.test.ts deleted file mode 100644 index 76f70509dc..0000000000 --- a/packages/core/src/tests/token.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Now import other modules -import { createRuntime } from "../test_resources/createRuntime"; -import { TokenProvider, WalletProvider } from "@ai16z/plugin-solana"; -import { Connection, PublicKey } from "@solana/web3.js"; -import { describe, it, expect, beforeEach, vi } from "vitest"; -import NodeCache from "node-cache"; - -describe("TokenProvider Tests", () => { - let tokenProvider: TokenProvider; - - beforeEach(async () => { - // Clear all mocks before each test - vi.clearAllMocks(); - - const { runtime } = await createRuntime({ - env: process.env, - conversationLength: 10, - }); - - const walletProvider = new WalletProvider( - new Connection(runtime.getSetting("RPC_URL")), - new PublicKey(runtime.getSetting("WALLET_PUBLIC_KEY")) - ); - // Create new instance of TokenProvider - tokenProvider = new TokenProvider( - "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh", - walletProvider - ); - - // Clear the cache and ensure it's empty - (tokenProvider as any).cache.flushAll(); - (tokenProvider as any).cache.close(); - (tokenProvider as any).cache = new NodeCache(); - - // Mock the getCachedData method instead - vi.spyOn(tokenProvider as any, "getCachedData").mockReturnValue(null); - }); - - it.skip("should fetch token security data", async () => { - // Mock the response for the fetchTokenSecurity call - const mockFetchResponse = { - success: true, - data: { - ownerBalance: "100", - creatorBalance: "50", - ownerPercentage: 10, - creatorPercentage: 5, - top10HolderBalance: "200", - top10HolderPercent: 20, - }, - }; - - // Mock fetchWithRetry function - const fetchSpy = vi - .spyOn(tokenProvider as any, "fetchWithRetry") - .mockResolvedValue(mockFetchResponse); - - // Run the fetchTokenSecurity method - const securityData = await tokenProvider.fetchTokenSecurity(); - // Check if the data returned is correct - expect(securityData).toEqual({ - ownerBalance: "100", - creatorBalance: "50", - ownerPercentage: 10, - creatorPercentage: 5, - top10HolderBalance: "200", - top10HolderPercent: 20, - }); - - // Ensure the mock was called with correct URL - expect(fetchSpy).toHaveBeenCalledWith( - expect.stringContaining( - "https://public-api.birdeye.so/defi/token_security?address=2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh" - ) - ); - }); -}); diff --git a/packages/plugin-solana/package.json b/packages/plugin-solana/package.json index e0aac7034b..c4e967a953 100644 --- a/packages/plugin-solana/package.json +++ b/packages/plugin-solana/package.json @@ -15,7 +15,8 @@ "bs58": "6.0.0", "node-cache": "5.1.2", "pumpdotfun-sdk": "1.3.2", - "tsup": "8.3.5" + "tsup": "8.3.5", + "vitest": "2.1.4" }, "devDependencies": { "eslint": "9.13.0", @@ -25,7 +26,8 @@ }, "scripts": { "build": "tsup --format esm --dts", - "lint": "eslint . --fix" + "lint": "eslint . --fix", + "test": "vitest run" }, "peerDependencies": { "whatwg-url": "7.1.0" diff --git a/packages/plugin-solana/src/tests/token.test.ts b/packages/plugin-solana/src/tests/token.test.ts new file mode 100644 index 0000000000..6b799c1c23 --- /dev/null +++ b/packages/plugin-solana/src/tests/token.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; +import { TokenProvider } from "../providers/token.ts"; + +// Mock NodeCache +vi.mock("node-cache", () => { + return { + default: vi.fn().mockImplementation(() => ({ + set: vi.fn(), + get: vi.fn().mockReturnValue(null), + })), + }; +}); + +// Mock path module +vi.mock("path", async () => { + const actual = await vi.importActual("path"); + return { + ...(actual as any), + join: vi.fn().mockImplementation((...args) => args.join("/")), + }; +}); + +// Mock the WalletProvider +const mockWalletProvider = { + fetchPortfolioValue: vi.fn(), +}; + +// Mock the ICacheManager +const mockCacheManager = { + get: vi.fn().mockResolvedValue(null), + set: vi.fn(), +}; + +// Mock fetch globally +const mockFetch = vi.fn(); +global.fetch = mockFetch; + +describe("TokenProvider", () => { + let tokenProvider: TokenProvider; + const TEST_TOKEN_ADDRESS = "2weMjPLLybRMMva1fM3U31goWWrCpF59CHWNhnCJ9Vyh"; + + beforeEach(() => { + vi.clearAllMocks(); + mockCacheManager.get.mockResolvedValue(null); + + // Create new instance of TokenProvider with mocked dependencies + tokenProvider = new TokenProvider( + TEST_TOKEN_ADDRESS, + mockWalletProvider as any, + mockCacheManager as any + ); + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + describe("Cache Management", () => { + it("should use cached data when available", async () => { + const mockData = { test: "data" }; + mockCacheManager.get.mockResolvedValueOnce(mockData); + + const result = await (tokenProvider as any).getCachedData( + "test-key" + ); + + expect(result).toEqual(mockData); + expect(mockCacheManager.get).toHaveBeenCalledTimes(1); + }); + + it("should write data to both caches", async () => { + const testData = { test: "data" }; + + await (tokenProvider as any).setCachedData("test-key", testData); + + expect(mockCacheManager.set).toHaveBeenCalledWith( + expect.stringContaining("test-key"), + testData, + expect.any(Object) + ); + }); + }); + + describe("Wallet Integration", () => { + it("should fetch tokens in wallet", async () => { + const mockItems = [ + { symbol: "SOL", address: "address1" }, + { symbol: "BTC", address: "address2" }, + ]; + + mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ + items: mockItems, + }); + + const result = await tokenProvider.getTokensInWallet({} as any); + + expect(result).toEqual(mockItems); + expect( + mockWalletProvider.fetchPortfolioValue + ).toHaveBeenCalledTimes(1); + }); + + it("should find token in wallet by symbol", async () => { + const mockItems = [ + { symbol: "SOL", address: "address1" }, + { symbol: "BTC", address: "address2" }, + ]; + + mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ + items: mockItems, + }); + + const result = await tokenProvider.getTokenFromWallet( + {} as any, + "SOL" + ); + + expect(result).toBe("address1"); + }); + + it("should return null for token not in wallet", async () => { + mockWalletProvider.fetchPortfolioValue.mockResolvedValueOnce({ + items: [], + }); + + const result = await tokenProvider.getTokenFromWallet( + {} as any, + "NONEXISTENT" + ); + + expect(result).toBeNull(); + }); + }); +});