diff --git a/tests/identity-db.test.ts b/tests/identity-db.test.ts new file mode 100644 index 0000000..52a592e --- /dev/null +++ b/tests/identity-db.test.ts @@ -0,0 +1,54 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { IdentityDB } from '../src/core/identity-db'; + +describe('IdentityDB topic and fact writes', () => { + let db: IdentityDB; + + beforeEach(async () => { + db = await IdentityDB.connect({ client: 'sqlite', filename: ':memory:' }); + await db.initialize(); + }); + + afterEach(async () => { + await db.close(); + }); + + it('deduplicates topics by normalized name during upsert', async () => { + const first = await db.upsertTopic({ + name: 'TypeScript', + category: 'entity', + granularity: 'concrete', + }); + + const second = await db.upsertTopic({ + name: ' typescript ', + category: 'entity', + granularity: 'concrete', + }); + + expect(second.id).toBe(first.id); + expect(second.normalizedName).toBe('typescript'); + + const topics = await db.listTopics({ includeFacts: false }); + expect(topics).toHaveLength(1); + }); + + it('adds one fact that links multiple topics', async () => { + const fact = await db.addFact({ + statement: 'I have worked with TypeScript since 2025.', + 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' }, + ], + }); + + expect(fact.statement).toBe('I have worked with TypeScript since 2025.'); + expect(fact.topics.map((topic) => topic.name)).toEqual(['I', 'TypeScript', '2025']); + + const typeScriptFacts = await db.getTopicFacts('TypeScript'); + expect(typeScriptFacts).toHaveLength(1); + expect(typeScriptFacts[0]?.statement).toBe('I have worked with TypeScript since 2025.'); + }); +}); diff --git a/tests/queries.test.ts b/tests/queries.test.ts new file mode 100644 index 0000000..97a9d02 --- /dev/null +++ b/tests/queries.test.ts @@ -0,0 +1,84 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { IdentityDB } from '../src/core/identity-db'; + +async function seedMemoryGraph(db: IdentityDB): Promise { + await db.addFact({ + statement: 'I have worked with TypeScript since 2025.', + 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.', + topics: [ + { name: 'TypeScript', category: 'entity', granularity: 'concrete', role: 'subject' }, + { name: 'programming language', category: 'concept', granularity: 'abstract', role: 'classification' }, + ], + }); +} + +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', + '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.'); + }); +});