Added memory module
This commit is contained in:
156
mcpServer/modules/memory/__tests__/file-path.test.ts
Normal file
156
mcpServer/modules/memory/__tests__/file-path.test.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { ensureMemoryFilePath, defaultMemoryPath } from '../index.js';
|
||||
|
||||
describe('ensureMemoryFilePath', () => {
|
||||
const testDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const oldMemoryPath = path.join(testDir, '..', 'memory.json');
|
||||
const newMemoryPath = path.join(testDir, '..', 'memory.jsonl');
|
||||
|
||||
let originalEnv: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
// Save original environment variable
|
||||
originalEnv = process.env.MEMORY_FILE_PATH;
|
||||
// Delete environment variable
|
||||
delete process.env.MEMORY_FILE_PATH;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Restore original environment variable
|
||||
if (originalEnv !== undefined) {
|
||||
process.env.MEMORY_FILE_PATH = originalEnv;
|
||||
} else {
|
||||
delete process.env.MEMORY_FILE_PATH;
|
||||
}
|
||||
|
||||
// Clean up test files
|
||||
try {
|
||||
await fs.unlink(oldMemoryPath);
|
||||
} catch {
|
||||
// Ignore if file doesn't exist
|
||||
}
|
||||
try {
|
||||
await fs.unlink(newMemoryPath);
|
||||
} catch {
|
||||
// Ignore if file doesn't exist
|
||||
}
|
||||
});
|
||||
|
||||
describe('with MEMORY_FILE_PATH environment variable', () => {
|
||||
it('should return absolute path when MEMORY_FILE_PATH is absolute', async () => {
|
||||
const absolutePath = '/tmp/custom-memory.jsonl';
|
||||
process.env.MEMORY_FILE_PATH = absolutePath;
|
||||
|
||||
const result = await ensureMemoryFilePath();
|
||||
|
||||
expect(result).toBe(absolutePath);
|
||||
});
|
||||
|
||||
it('should convert relative path to absolute when MEMORY_FILE_PATH is relative', async () => {
|
||||
const relativePath = 'custom-memory.jsonl';
|
||||
process.env.MEMORY_FILE_PATH = relativePath;
|
||||
|
||||
const result = await ensureMemoryFilePath();
|
||||
|
||||
expect(path.isAbsolute(result)).toBe(true);
|
||||
expect(result).toContain('custom-memory.jsonl');
|
||||
});
|
||||
|
||||
it('should handle Windows absolute paths', async () => {
|
||||
const windowsPath = 'C:\\temp\\memory.jsonl';
|
||||
process.env.MEMORY_FILE_PATH = windowsPath;
|
||||
|
||||
const result = await ensureMemoryFilePath();
|
||||
|
||||
// On Windows, should return as-is; on Unix, will be treated as relative
|
||||
if (process.platform === 'win32') {
|
||||
expect(result).toBe(windowsPath);
|
||||
} else {
|
||||
expect(path.isAbsolute(result)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('without MEMORY_FILE_PATH environment variable', () => {
|
||||
it('should return default path when no files exist', async () => {
|
||||
const result = await ensureMemoryFilePath();
|
||||
|
||||
expect(result).toBe(defaultMemoryPath);
|
||||
});
|
||||
|
||||
it('should migrate from memory.json to memory.jsonl when only old file exists', async () => {
|
||||
// Create old memory.json file
|
||||
await fs.writeFile(oldMemoryPath, '{"test":"data"}');
|
||||
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const result = await ensureMemoryFilePath();
|
||||
|
||||
expect(result).toBe(defaultMemoryPath);
|
||||
|
||||
// Verify migration happened
|
||||
const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false);
|
||||
const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false);
|
||||
|
||||
expect(newFileExists).toBe(true);
|
||||
expect(oldFileExists).toBe(false);
|
||||
|
||||
// Verify console messages
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('DETECTED: Found legacy memory.json file')
|
||||
);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('COMPLETED: Successfully migrated')
|
||||
);
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should use new file when both old and new files exist', async () => {
|
||||
// Create both files
|
||||
await fs.writeFile(oldMemoryPath, '{"old":"data"}');
|
||||
await fs.writeFile(newMemoryPath, '{"new":"data"}');
|
||||
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const result = await ensureMemoryFilePath();
|
||||
|
||||
expect(result).toBe(defaultMemoryPath);
|
||||
|
||||
// Verify no migration happened (both files should still exist)
|
||||
const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false);
|
||||
const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false);
|
||||
|
||||
expect(newFileExists).toBe(true);
|
||||
expect(oldFileExists).toBe(true);
|
||||
|
||||
// Verify no console messages about migration
|
||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should preserve file content during migration', async () => {
|
||||
const testContent = '{"entities": [{"name": "test", "type": "person"}]}';
|
||||
await fs.writeFile(oldMemoryPath, testContent);
|
||||
|
||||
await ensureMemoryFilePath();
|
||||
|
||||
const migratedContent = await fs.readFile(newMemoryPath, 'utf-8');
|
||||
expect(migratedContent).toBe(testContent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultMemoryPath', () => {
|
||||
it('should end with memory.jsonl', () => {
|
||||
expect(defaultMemoryPath).toMatch(/memory\.jsonl$/);
|
||||
});
|
||||
|
||||
it('should be an absolute path', () => {
|
||||
expect(path.isAbsolute(defaultMemoryPath)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
483
mcpServer/modules/memory/__tests__/knowledge-graph.test.ts
Normal file
483
mcpServer/modules/memory/__tests__/knowledge-graph.test.ts
Normal file
@@ -0,0 +1,483 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { KnowledgeGraphManager, Entity, Relation, KnowledgeGraph } from '../index.js';
|
||||
|
||||
describe('KnowledgeGraphManager', () => {
|
||||
let manager: KnowledgeGraphManager;
|
||||
let testFilePath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create a temporary test file path
|
||||
testFilePath = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
`test-memory-${Date.now()}.jsonl`
|
||||
);
|
||||
manager = new KnowledgeGraphManager(testFilePath);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up test file
|
||||
try {
|
||||
await fs.unlink(testFilePath);
|
||||
} catch (error) {
|
||||
// Ignore errors if file doesn't exist
|
||||
}
|
||||
});
|
||||
|
||||
describe('createEntities', () => {
|
||||
it('should create new entities', async () => {
|
||||
const entities: Entity[] = [
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] },
|
||||
{ name: 'Bob', entityType: 'person', observations: ['likes programming'] },
|
||||
];
|
||||
|
||||
const newEntities = await manager.createEntities(entities);
|
||||
expect(newEntities).toHaveLength(2);
|
||||
expect(newEntities).toEqual(entities);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should not create duplicate entities', async () => {
|
||||
const entities: Entity[] = [
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] },
|
||||
];
|
||||
|
||||
await manager.createEntities(entities);
|
||||
const newEntities = await manager.createEntities(entities);
|
||||
|
||||
expect(newEntities).toHaveLength(0);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should handle empty entity arrays', async () => {
|
||||
const newEntities = await manager.createEntities([]);
|
||||
expect(newEntities).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createRelations', () => {
|
||||
it('should create new relations', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
]);
|
||||
|
||||
const relations: Relation[] = [
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
];
|
||||
|
||||
const newRelations = await manager.createRelations(relations);
|
||||
expect(newRelations).toHaveLength(1);
|
||||
expect(newRelations).toEqual(relations);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.relations).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not create duplicate relations', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
]);
|
||||
|
||||
const relations: Relation[] = [
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
];
|
||||
|
||||
await manager.createRelations(relations);
|
||||
const newRelations = await manager.createRelations(relations);
|
||||
|
||||
expect(newRelations).toHaveLength(0);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.relations).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should handle empty relation arrays', async () => {
|
||||
const newRelations = await manager.createRelations([]);
|
||||
expect(newRelations).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addObservations', () => {
|
||||
it('should add observations to existing entities', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] },
|
||||
]);
|
||||
|
||||
const results = await manager.addObservations([
|
||||
{ entityName: 'Alice', contents: ['likes coffee', 'has a dog'] },
|
||||
]);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].entityName).toBe('Alice');
|
||||
expect(results[0].addedObservations).toHaveLength(2);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
const alice = graph.entities.find(e => e.name === 'Alice');
|
||||
expect(alice?.observations).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should not add duplicate observations', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] },
|
||||
]);
|
||||
|
||||
await manager.addObservations([
|
||||
{ entityName: 'Alice', contents: ['likes coffee'] },
|
||||
]);
|
||||
|
||||
const results = await manager.addObservations([
|
||||
{ entityName: 'Alice', contents: ['likes coffee', 'has a dog'] },
|
||||
]);
|
||||
|
||||
expect(results[0].addedObservations).toHaveLength(1);
|
||||
expect(results[0].addedObservations).toContain('has a dog');
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
const alice = graph.entities.find(e => e.name === 'Alice');
|
||||
expect(alice?.observations).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should throw error for non-existent entity', async () => {
|
||||
await expect(
|
||||
manager.addObservations([
|
||||
{ entityName: 'NonExistent', contents: ['some observation'] },
|
||||
])
|
||||
).rejects.toThrow('Entity with name NonExistent not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteEntities', () => {
|
||||
it('should delete entities', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
]);
|
||||
|
||||
await manager.deleteEntities(['Alice']);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(1);
|
||||
expect(graph.entities[0].name).toBe('Bob');
|
||||
});
|
||||
|
||||
it('should cascade delete relations when deleting entities', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
{ name: 'Charlie', entityType: 'person', observations: [] },
|
||||
]);
|
||||
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
{ from: 'Bob', to: 'Charlie', relationType: 'knows' },
|
||||
]);
|
||||
|
||||
await manager.deleteEntities(['Bob']);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(2);
|
||||
expect(graph.relations).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle deleting non-existent entities', async () => {
|
||||
await manager.deleteEntities(['NonExistent']);
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteObservations', () => {
|
||||
it('should delete observations from entities', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme Corp', 'likes coffee'] },
|
||||
]);
|
||||
|
||||
await manager.deleteObservations([
|
||||
{ entityName: 'Alice', observations: ['likes coffee'] },
|
||||
]);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
const alice = graph.entities.find(e => e.name === 'Alice');
|
||||
expect(alice?.observations).toHaveLength(1);
|
||||
expect(alice?.observations).toContain('works at Acme Corp');
|
||||
});
|
||||
|
||||
it('should handle deleting from non-existent entities', async () => {
|
||||
await manager.deleteObservations([
|
||||
{ entityName: 'NonExistent', observations: ['some observation'] },
|
||||
]);
|
||||
// Should not throw error
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteRelations', () => {
|
||||
it('should delete specific relations', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
]);
|
||||
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'works_with' },
|
||||
]);
|
||||
|
||||
await manager.deleteRelations([
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
]);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.relations).toHaveLength(1);
|
||||
expect(graph.relations[0].relationType).toBe('works_with');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readGraph', () => {
|
||||
it('should return empty graph when file does not exist', async () => {
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(0);
|
||||
expect(graph.relations).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should return complete graph with entities and relations', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] },
|
||||
]);
|
||||
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Alice', relationType: 'self' },
|
||||
]);
|
||||
|
||||
const graph = await manager.readGraph();
|
||||
expect(graph.entities).toHaveLength(1);
|
||||
expect(graph.relations).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchNodes', () => {
|
||||
beforeEach(async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme Corp', 'likes programming'] },
|
||||
{ name: 'Bob', entityType: 'person', observations: ['works at TechCo'] },
|
||||
{ name: 'Acme Corp', entityType: 'company', observations: ['tech company'] },
|
||||
]);
|
||||
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Acme Corp', relationType: 'works_at' },
|
||||
{ from: 'Bob', to: 'Acme Corp', relationType: 'competitor' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should search by entity name', async () => {
|
||||
const result = await manager.searchNodes('Alice');
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0].name).toBe('Alice');
|
||||
});
|
||||
|
||||
it('should search by entity type', async () => {
|
||||
const result = await manager.searchNodes('company');
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0].name).toBe('Acme Corp');
|
||||
});
|
||||
|
||||
it('should search by observation content', async () => {
|
||||
const result = await manager.searchNodes('programming');
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0].name).toBe('Alice');
|
||||
});
|
||||
|
||||
it('should be case insensitive', async () => {
|
||||
const result = await manager.searchNodes('ALICE');
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0].name).toBe('Alice');
|
||||
});
|
||||
|
||||
it('should include relations between matched entities', async () => {
|
||||
const result = await manager.searchNodes('Acme');
|
||||
expect(result.entities).toHaveLength(2); // Alice and Acme Corp
|
||||
expect(result.relations).toHaveLength(1); // Only Alice -> Acme Corp relation
|
||||
});
|
||||
|
||||
it('should return empty graph for no matches', async () => {
|
||||
const result = await manager.searchNodes('NonExistent');
|
||||
expect(result.entities).toHaveLength(0);
|
||||
expect(result.relations).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openNodes', () => {
|
||||
beforeEach(async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
{ name: 'Charlie', entityType: 'person', observations: [] },
|
||||
]);
|
||||
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
{ from: 'Bob', to: 'Charlie', relationType: 'knows' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should open specific nodes by name', async () => {
|
||||
const result = await manager.openNodes(['Alice', 'Bob']);
|
||||
expect(result.entities).toHaveLength(2);
|
||||
expect(result.entities.map(e => e.name)).toContain('Alice');
|
||||
expect(result.entities.map(e => e.name)).toContain('Bob');
|
||||
});
|
||||
|
||||
it('should include relations between opened nodes', async () => {
|
||||
const result = await manager.openNodes(['Alice', 'Bob']);
|
||||
expect(result.relations).toHaveLength(1);
|
||||
expect(result.relations[0].from).toBe('Alice');
|
||||
expect(result.relations[0].to).toBe('Bob');
|
||||
});
|
||||
|
||||
it('should exclude relations to unopened nodes', async () => {
|
||||
const result = await manager.openNodes(['Bob']);
|
||||
expect(result.relations).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle opening non-existent nodes', async () => {
|
||||
const result = await manager.openNodes(['NonExistent']);
|
||||
expect(result.entities).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle empty node list', async () => {
|
||||
const result = await manager.openNodes([]);
|
||||
expect(result.entities).toHaveLength(0);
|
||||
expect(result.relations).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('file persistence', () => {
|
||||
it('should persist data across manager instances', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['persistent data'] },
|
||||
]);
|
||||
|
||||
// Create new manager instance with same file path
|
||||
const manager2 = new KnowledgeGraphManager(testFilePath);
|
||||
const graph = await manager2.readGraph();
|
||||
|
||||
expect(graph.entities).toHaveLength(1);
|
||||
expect(graph.entities[0].name).toBe('Alice');
|
||||
});
|
||||
|
||||
it('should handle JSONL format correctly', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
]);
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Alice', relationType: 'self' },
|
||||
]);
|
||||
|
||||
// Read file directly
|
||||
const fileContent = await fs.readFile(testFilePath, 'utf-8');
|
||||
const lines = fileContent.split('\n').filter(line => line.trim());
|
||||
|
||||
expect(lines).toHaveLength(2);
|
||||
expect(JSON.parse(lines[0])).toHaveProperty('type', 'entity');
|
||||
expect(JSON.parse(lines[1])).toHaveProperty('type', 'relation');
|
||||
});
|
||||
|
||||
it('should strip type field from entities when loading from file', async () => {
|
||||
// Create entities and relations (these get saved with type field)
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['test observation'] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
]);
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
]);
|
||||
|
||||
// Verify file contains type field (order may vary)
|
||||
const fileContent = await fs.readFile(testFilePath, 'utf-8');
|
||||
const fileLines = fileContent.split('\n').filter(line => line.trim());
|
||||
const fileItems = fileLines.map(line => JSON.parse(line));
|
||||
const fileEntity = fileItems.find(item => item.type === 'entity');
|
||||
const fileRelation = fileItems.find(item => item.type === 'relation');
|
||||
expect(fileEntity).toBeDefined();
|
||||
expect(fileEntity).toHaveProperty('type', 'entity');
|
||||
expect(fileRelation).toBeDefined();
|
||||
expect(fileRelation).toHaveProperty('type', 'relation');
|
||||
|
||||
// Create new manager instance to force reload from file
|
||||
const manager2 = new KnowledgeGraphManager(testFilePath);
|
||||
const graph = await manager2.readGraph();
|
||||
|
||||
// Verify loaded entities don't have type field
|
||||
expect(graph.entities).toHaveLength(2);
|
||||
graph.entities.forEach(entity => {
|
||||
expect(entity).not.toHaveProperty('type');
|
||||
expect(entity).toHaveProperty('name');
|
||||
expect(entity).toHaveProperty('entityType');
|
||||
expect(entity).toHaveProperty('observations');
|
||||
});
|
||||
|
||||
// Verify loaded relations don't have type field
|
||||
expect(graph.relations).toHaveLength(1);
|
||||
graph.relations.forEach(relation => {
|
||||
expect(relation).not.toHaveProperty('type');
|
||||
expect(relation).toHaveProperty('from');
|
||||
expect(relation).toHaveProperty('to');
|
||||
expect(relation).toHaveProperty('relationType');
|
||||
});
|
||||
});
|
||||
|
||||
it('should strip type field from searchNodes results', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: ['works at Acme'] },
|
||||
]);
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Alice', relationType: 'self' },
|
||||
]);
|
||||
|
||||
// Create new manager instance to force reload from file
|
||||
const manager2 = new KnowledgeGraphManager(testFilePath);
|
||||
const result = await manager2.searchNodes('Alice');
|
||||
|
||||
// Verify search results don't have type field
|
||||
expect(result.entities).toHaveLength(1);
|
||||
expect(result.entities[0]).not.toHaveProperty('type');
|
||||
expect(result.entities[0].name).toBe('Alice');
|
||||
|
||||
expect(result.relations).toHaveLength(1);
|
||||
expect(result.relations[0]).not.toHaveProperty('type');
|
||||
expect(result.relations[0].from).toBe('Alice');
|
||||
});
|
||||
|
||||
it('should strip type field from openNodes results', async () => {
|
||||
await manager.createEntities([
|
||||
{ name: 'Alice', entityType: 'person', observations: [] },
|
||||
{ name: 'Bob', entityType: 'person', observations: [] },
|
||||
]);
|
||||
await manager.createRelations([
|
||||
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
|
||||
]);
|
||||
|
||||
// Create new manager instance to force reload from file
|
||||
const manager2 = new KnowledgeGraphManager(testFilePath);
|
||||
const result = await manager2.openNodes(['Alice', 'Bob']);
|
||||
|
||||
// Verify open results don't have type field
|
||||
expect(result.entities).toHaveLength(2);
|
||||
result.entities.forEach(entity => {
|
||||
expect(entity).not.toHaveProperty('type');
|
||||
});
|
||||
|
||||
expect(result.relations).toHaveLength(1);
|
||||
expect(result.relations[0]).not.toHaveProperty('type');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user