Files
IdentityDB/tests/queries.test.ts

179 lines
6.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { IdentityDB } from '../src/core/identity-db';
async function seedMemoryGraph(db: IdentityDB, spaceName?: string): Promise<void> {
await db.addFact({
statement: 'I have worked with TypeScript since 2025.',
spaceName,
topics: [
{ name: 'I', category: 'entity', granularity: 'concrete', role: 'subject' },
{ name: 'TypeScript', category: 'entity', granularity: 'concrete', role: 'object' },
{ name: '2025', category: 'temporal', granularity: 'concrete', role: 'time' },
],
});
await db.addFact({
statement: 'TypeScript is a programming language.',
spaceName,
topics: [
{ name: 'TypeScript', category: 'entity', granularity: 'concrete', role: 'subject' },
{ name: 'programming language', category: 'concept', granularity: 'abstract', role: 'classification' },
],
});
await db.linkTopics({
parentName: 'software technology',
childName: 'programming language',
spaceName,
});
await db.linkTopics({
parentName: 'programming language',
childName: 'TypeScript',
spaceName,
});
}
describe('IdentityDB queries', () => {
let db: IdentityDB;
beforeEach(async () => {
db = await IdentityDB.connect({ client: 'sqlite', filename: ':memory:' });
await db.initialize();
await seedMemoryGraph(db);
});
afterEach(async () => {
await db.close();
});
it('gets a topic with its facts', async () => {
const topic = await db.getTopicByName('TypeScript', { includeFacts: true });
expect(topic).not.toBeNull();
expect(topic?.name).toBe('TypeScript');
expect(topic?.facts).toHaveLength(2);
expect(topic?.facts.map((fact) => fact.statement)).toEqual([
'I have worked with TypeScript since 2025.',
'TypeScript is a programming language.',
]);
});
it('gets only the facts linked to another topic', async () => {
const facts = await db.getTopicFactsLinkedTo('TypeScript', '2025');
expect(facts).toHaveLength(1);
expect(facts[0]?.statement).toBe('I have worked with TypeScript since 2025.');
});
it('lists topics without expanding facts', async () => {
const topics = await db.listTopics({ includeFacts: false });
expect(topics.map((topic) => topic.name)).toEqual([
'2025',
'I',
'programming language',
'software technology',
'TypeScript',
]);
expect('facts' in topics[0]!).toBe(false);
});
it('finds connected topics with shared fact counts', async () => {
const connectedTopics = await db.findConnectedTopics('TypeScript');
expect(connectedTopics).toEqual([
expect.objectContaining({ name: '2025', sharedFactCount: 1 }),
expect.objectContaining({ name: 'I', sharedFactCount: 1 }),
expect.objectContaining({ name: 'programming language', sharedFactCount: 1 }),
]);
});
it('finds facts that connect all requested topics', async () => {
const facts = await db.findFactsConnectingTopics(['I', 'TypeScript', '2025']);
expect(facts).toHaveLength(1);
expect(facts[0]?.statement).toBe('I have worked with TypeScript since 2025.');
});
it('lists direct child topics for a parent topic', async () => {
const children = await db.getTopicChildren('programming language');
expect(children.map((topic) => topic.name)).toEqual(['TypeScript']);
});
it('lists direct parent topics for a child topic', async () => {
const parents = await db.getTopicParents('TypeScript');
expect(parents.map((topic) => topic.name)).toEqual(['programming language']);
});
it('returns lineage from nearest parent outward', async () => {
const lineage = await db.getTopicLineage('TypeScript');
expect(lineage.map((topic) => topic.name)).toEqual([
'programming language',
'software technology',
]);
});
it('keeps hierarchy and fact queries isolated per space', async () => {
const isolatedDb = await IdentityDB.connect({ client: 'sqlite', filename: ':memory:' });
try {
await isolatedDb.initialize();
await seedMemoryGraph(isolatedDb, 'A');
await isolatedDb.addFact({
statement: 'TypeScript is a typed superset.',
spaceName: 'B',
topics: [
{ name: 'TypeScript', category: 'entity', granularity: 'concrete', role: 'subject' },
{ name: 'superset', category: 'concept', granularity: 'abstract', role: 'classification' },
],
});
await isolatedDb.linkTopics({
parentName: 'language family',
childName: 'TypeScript',
spaceName: 'B',
});
const alphaTopic = await isolatedDb.getTopicByName('TypeScript', {
includeFacts: true,
spaceName: 'A',
});
const betaTopic = await isolatedDb.getTopicByName('TypeScript', {
includeFacts: true,
spaceName: 'B',
});
const alphaParents = await isolatedDb.getTopicParents('TypeScript', { spaceName: 'A' });
const betaParents = await isolatedDb.getTopicParents('TypeScript', { spaceName: 'B' });
const alphaConnected = await isolatedDb.findConnectedTopics('TypeScript', { spaceName: 'A' });
const betaConnected = await isolatedDb.findConnectedTopics('TypeScript', { spaceName: 'B' });
expect(alphaTopic?.facts.map((fact) => fact.statement)).toEqual([
'I have worked with TypeScript since 2025.',
'TypeScript is a programming language.',
]);
expect(betaTopic?.facts.map((fact) => fact.statement)).toEqual([
'TypeScript is a typed superset.',
]);
expect(alphaParents.map((topic) => topic.name)).toEqual(['programming language']);
expect(betaParents.map((topic) => topic.name)).toEqual(['language family']);
expect(alphaConnected.map((topic) => topic.name)).toEqual(['2025', 'I', 'programming language']);
expect(betaConnected.map((topic) => topic.name)).toEqual(['superset']);
} finally {
await isolatedDb.close();
}
});
it('resolves alias names in topic lookups', async () => {
await db.addTopicAlias('TypeScript', 'TS');
const topic = await db.getTopicByName('ts');
expect(topic?.name).toBe('TypeScript');
});
});