Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 53 additions & 57 deletions __tests__/action.test.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,55 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { beforeEach, describe, expect, it, jest, mock } from 'bun:test';
import * as fs from 'node:fs';
import * as path from 'node:path';
import type { IAgentRuntime, Memory, State, UUID } from '@elizaos/core';
import { processKnowledgeAction } from '../src/actions';
import { KnowledgeService } from '../src/service';
import type { IAgentRuntime, Memory, Content, State, UUID } from '@elizaos/core';
import * as fs from 'fs';
import * as path from 'path';

// Mock @elizaos/core logger and createUniqueUuid
vi.mock('@elizaos/core', async () => {
const actual = await vi.importActual<typeof import('@elizaos/core')>('@elizaos/core');
return {
...actual,
logger: {
warn: vi.fn(),
error: vi.fn(),
info: vi.fn(),
debug: vi.fn(),
},
};
});

// Mock fs and path
vi.mock('fs');
vi.mock('path');
// Mock the fs and path modules
mock.module('node:fs', () => ({
existsSync: jest.fn(),
readFileSync: jest.fn(),
}));

mock.module('node:path', () => ({
basename: jest.fn(),
extname: jest.fn(),
}));

describe('processKnowledgeAction', () => {
let mockRuntime: IAgentRuntime;
let mockKnowledgeService: KnowledgeService;
let mockCallback: Mock;
let mockCallback: jest.Mock;
let mockState: State;

const generateMockUuid = (suffix: string | number): UUID =>
`00000000-0000-0000-0000-${String(suffix).padStart(12, '0')}` as UUID;

beforeEach(() => {
mockKnowledgeService = {
addKnowledge: vi.fn(),
getKnowledge: vi.fn(),
addKnowledge: jest.fn(),
getKnowledge: jest.fn(),
serviceType: 'knowledge-service',
} as unknown as KnowledgeService;

mockRuntime = {
agentId: 'test-agent' as UUID,
getService: vi.fn().mockReturnValue(mockKnowledgeService),
getService: jest.fn().mockReturnValue(mockKnowledgeService),
} as unknown as IAgentRuntime;

mockCallback = vi.fn();
mockCallback = jest.fn();
mockState = {
values: {},
data: {},
text: '',
};
vi.clearAllMocks();
jest.clearAllMocks();
});

describe('handler', () => {
beforeEach(() => {
// Reset and re-mock fs/path functions for each handler test
(fs.existsSync as Mock).mockReset();
(fs.readFileSync as Mock).mockReset();
(path.basename as Mock).mockReset();
(path.extname as Mock).mockReset();
// Reset all mocks for each handler test
jest.clearAllMocks();
});

it('should process a file when a valid path is provided', async () => {
Expand All @@ -73,13 +63,13 @@ describe('processKnowledgeAction', () => {
};

// Mock Date.now() for this test to generate predictable clientDocumentId's
const dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(1749491066994);
const dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1749491066994);

(fs.existsSync as Mock).mockReturnValue(true);
(fs.readFileSync as Mock).mockReturnValue(Buffer.from('file content'));
(path.basename as Mock).mockReturnValue('document.pdf');
(path.extname as Mock).mockReturnValue('.pdf');
(mockKnowledgeService.addKnowledge as Mock).mockResolvedValue({ fragmentCount: 5 });
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('file content'));
(path.basename as jest.Mock).mockReturnValue('document.pdf');
(path.extname as jest.Mock).mockReturnValue('.pdf');
(mockKnowledgeService.addKnowledge as jest.Mock).mockResolvedValue({ fragmentCount: 5 });

await processKnowledgeAction.handler?.(mockRuntime, message, mockState, {}, mockCallback);

Expand Down Expand Up @@ -112,7 +102,7 @@ describe('processKnowledgeAction', () => {
roomId: generateMockUuid(6),
};

(fs.existsSync as Mock).mockReturnValue(false);
(fs.existsSync as jest.Mock).mockReturnValue(false);

await processKnowledgeAction.handler?.(mockRuntime, message, mockState, {}, mockCallback);

Expand All @@ -126,7 +116,7 @@ describe('processKnowledgeAction', () => {

it('should process direct text content when no file path is provided', async () => {
// Mock Date.now() for this test to generate predictable clientDocumentId's
const dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(1749491066994);
const dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1749491066994);

const message: Memory = {
id: generateMockUuid(7),
Expand All @@ -137,7 +127,7 @@ describe('processKnowledgeAction', () => {
roomId: generateMockUuid(9),
};

(mockKnowledgeService.addKnowledge as Mock).mockResolvedValue({});
(mockKnowledgeService.addKnowledge as jest.Mock).mockResolvedValue({ fragmentCount: 1 });

await processKnowledgeAction.handler?.(mockRuntime, message, mockState, {}, mockCallback);

Expand Down Expand Up @@ -188,11 +178,11 @@ describe('processKnowledgeAction', () => {
roomId: generateMockUuid(15),
};

(fs.existsSync as Mock).mockReturnValue(true);
(fs.readFileSync as Mock).mockReturnValue(Buffer.from('error content'));
(path.basename as Mock).mockReturnValue('error.txt');
(path.extname as Mock).mockReturnValue('.txt');
(mockKnowledgeService.addKnowledge as Mock).mockRejectedValue(new Error('Service error'));
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('error content'));
(path.basename as jest.Mock).mockReturnValue('error.txt');
(path.extname as jest.Mock).mockReturnValue('.txt');
(mockKnowledgeService.addKnowledge as jest.Mock).mockRejectedValue(new Error('Service error'));

await processKnowledgeAction.handler?.(mockRuntime, message, mockState, {}, mockCallback);

Expand All @@ -203,7 +193,7 @@ describe('processKnowledgeAction', () => {

it("should generate unique clientDocumentId's for different documents and content", async () => {
// Mock Date.now() for this test to generate predictable clientDocumentId's
const dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(1749491066994);
const dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1749491066994);

// Test with two different files
const fileMessage1: Memory = {
Expand Down Expand Up @@ -235,10 +225,13 @@ describe('processKnowledgeAction', () => {
};

// Setup mocks for file operations
(fs.existsSync as Mock).mockReturnValue(true);
(fs.readFileSync as Mock).mockReturnValue(Buffer.from('file content'));
(path.basename as Mock).mockReturnValueOnce('doc1.pdf').mockReturnValueOnce('doc2.pdf');
(path.extname as Mock).mockReturnValueOnce('.pdf').mockReturnValueOnce('.pdf');
(fs.existsSync as jest.Mock).mockReturnValue(true);
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('file content'));
(path.basename as jest.Mock).mockReturnValueOnce('doc1.pdf').mockReturnValueOnce('doc2.pdf');
(path.extname as jest.Mock).mockReturnValueOnce('.pdf').mockReturnValueOnce('.pdf');

// Mock addKnowledge to return results with fragment counts
(mockKnowledgeService.addKnowledge as jest.Mock).mockResolvedValue({ fragmentCount: 3 });

// Process all three messages
await processKnowledgeAction.handler?.(
Expand All @@ -258,7 +251,7 @@ describe('processKnowledgeAction', () => {
await processKnowledgeAction.handler?.(mockRuntime, textMessage, mockState, {}, mockCallback);

// Get all calls to addKnowledge
const addKnowledgeCalls = (mockKnowledgeService.addKnowledge as Mock).mock.calls;
const addKnowledgeCalls = (mockKnowledgeService.addKnowledge as jest.Mock).mock.calls;

// Extract clientDocumentId's from the knowledgeOptions objects
const clientDocumentIds = addKnowledgeCalls.map((call) => call[0].clientDocumentId);
Expand Down Expand Up @@ -288,7 +281,7 @@ describe('processKnowledgeAction', () => {

it("should generate unique clientDocumentId's for same content but different time", async () => {
// Mock Date.now() for this test to generate predictable clientDocumentId's
const dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(1749491066994);
const dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1749491066994);

// Test with two different files
const textMessage1: Memory = {
Expand All @@ -309,6 +302,9 @@ describe('processKnowledgeAction', () => {
roomId: generateMockUuid(33),
};

// Mock addKnowledge to return results with fragment counts
(mockKnowledgeService.addKnowledge as jest.Mock).mockResolvedValue({ fragmentCount: 1 });

// Process all three messages
await processKnowledgeAction.handler?.(
mockRuntime,
Expand All @@ -320,7 +316,7 @@ describe('processKnowledgeAction', () => {

// Change Date.now() mock to generate a different timestamp
dateNowSpy.mockRestore();
const dateNowSpy2 = vi.spyOn(Date, 'now').mockReturnValue(1749491066995);
const dateNowSpy2 = jest.spyOn(Date, 'now').mockReturnValue(1749491066995);

await processKnowledgeAction.handler?.(
mockRuntime,
Expand All @@ -331,7 +327,7 @@ describe('processKnowledgeAction', () => {
);

// Get all calls to addKnowledge
const addKnowledgeCalls = (mockKnowledgeService.addKnowledge as Mock).mock.calls;
const addKnowledgeCalls = (mockKnowledgeService.addKnowledge as jest.Mock).mock.calls;

// Extract clientDocumentId's from the knowledgeOptions objects
const clientDocumentIds = addKnowledgeCalls.map((call) => call[0].clientDocumentId);
Expand All @@ -354,7 +350,7 @@ describe('processKnowledgeAction', () => {

describe('validate', () => {
beforeEach(() => {
(mockRuntime.getService as Mock).mockReturnValue(mockKnowledgeService);
(mockRuntime.getService as jest.Mock).mockReturnValue(mockKnowledgeService);
});

it('should return true if knowledge keywords are present and service is available', async () => {
Expand Down Expand Up @@ -385,7 +381,7 @@ describe('processKnowledgeAction', () => {
});

it('should return false if service is not available', async () => {
(mockRuntime.getService as Mock).mockReturnValue(null);
(mockRuntime.getService as jest.Mock).mockReturnValue(null);
const message: Memory = {
id: generateMockUuid(22),
content: {
Expand Down
Loading