Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
202 lines
6.2 KiB
TypeScript
202 lines
6.2 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
|
|
import type { Skill, Person } from '../schemas/types';
|
|
import {
|
|
getEntityDir,
|
|
getEntityPath,
|
|
readEntity,
|
|
writeEntity,
|
|
listEntities,
|
|
deleteEntity,
|
|
entityExists,
|
|
} from './entity-files';
|
|
|
|
describe('entity-files', () => {
|
|
let tempDir: string;
|
|
|
|
beforeEach(async () => {
|
|
tempDir = await mkdtemp(join(tmpdir(), 'kb-test-'));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('getEntityDir', () => {
|
|
it('maps person to kb/profiles/', () => {
|
|
expect(getEntityDir('person', '/base')).toBe(join('/base', 'kb', 'profiles'));
|
|
});
|
|
|
|
it('maps experience to kb/experiences/', () => {
|
|
expect(getEntityDir('experience', '/base')).toBe(join('/base', 'kb', 'experiences'));
|
|
});
|
|
|
|
it('maps skill to kb/skills/', () => {
|
|
expect(getEntityDir('skill', '/base')).toBe(join('/base', 'kb', 'skills'));
|
|
});
|
|
|
|
it('maps organization to kb/organizations/', () => {
|
|
expect(getEntityDir('organization', '/base')).toBe(join('/base', 'kb', 'organizations'));
|
|
});
|
|
|
|
it('maps project to kb/projects/', () => {
|
|
expect(getEntityDir('project', '/base')).toBe(join('/base', 'kb', 'projects'));
|
|
});
|
|
|
|
it('maps certification to kb/certifications/', () => {
|
|
expect(getEntityDir('certification', '/base')).toBe(join('/base', 'kb', 'certifications'));
|
|
});
|
|
|
|
it('maps tandem to kb/tandems/', () => {
|
|
expect(getEntityDir('tandem', '/base')).toBe(join('/base', 'kb', 'tandems'));
|
|
});
|
|
});
|
|
|
|
describe('getEntityPath', () => {
|
|
it('constructs correct file path', () => {
|
|
expect(getEntityPath('skill', 'python', '/base')).toBe(join('/base', 'kb', 'skills', 'python.yaml'));
|
|
});
|
|
});
|
|
|
|
describe('writeEntity + readEntity round-trip', () => {
|
|
const skill: Skill = {
|
|
id: 'typescript',
|
|
type: 'skill',
|
|
created: '2025-01-15',
|
|
modified: '2025-01-15',
|
|
name: 'TypeScript',
|
|
category: 'programming-language',
|
|
level: 'expert',
|
|
years: 5,
|
|
};
|
|
|
|
it('writes and reads back an entity', async () => {
|
|
await writeEntity(skill, tempDir);
|
|
const result = await readEntity('skill', 'typescript', tempDir);
|
|
expect(result).toEqual(skill);
|
|
});
|
|
|
|
it('rejects invalid entities on write', async () => {
|
|
const invalid = { id: 'bad', type: 'skill', created: '2025-01-15', modified: '2025-01-15' } as unknown as Skill;
|
|
await expect(writeEntity(invalid, tempDir)).rejects.toThrow('Validation failed');
|
|
});
|
|
});
|
|
|
|
describe('readEntity errors', () => {
|
|
it('throws descriptive error when entity not found', async () => {
|
|
await expect(readEntity('skill', 'nonexistent', tempDir)).rejects.toThrow(
|
|
/Entity not found: skill "nonexistent"/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('listEntities', () => {
|
|
it('returns empty array for empty/missing directory', async () => {
|
|
const ids = await listEntities('skill', tempDir);
|
|
expect(ids).toEqual([]);
|
|
});
|
|
|
|
it('lists entity IDs from directory', async () => {
|
|
const skill1: Skill = {
|
|
id: 'python',
|
|
type: 'skill',
|
|
created: '2025-01-15',
|
|
modified: '2025-01-15',
|
|
name: 'Python',
|
|
category: 'programming-language',
|
|
level: 'expert',
|
|
};
|
|
const skill2: Skill = {
|
|
id: 'rust',
|
|
type: 'skill',
|
|
created: '2025-01-15',
|
|
modified: '2025-01-15',
|
|
name: 'Rust',
|
|
category: 'programming-language',
|
|
level: 'intermediate',
|
|
};
|
|
await writeEntity(skill1, tempDir);
|
|
await writeEntity(skill2, tempDir);
|
|
|
|
const ids = await listEntities('skill', tempDir);
|
|
expect(ids.sort()).toEqual(['python', 'rust']);
|
|
});
|
|
});
|
|
|
|
describe('deleteEntity', () => {
|
|
it('deletes an existing entity file', async () => {
|
|
const skill: Skill = {
|
|
id: 'go-lang',
|
|
type: 'skill',
|
|
created: '2025-01-15',
|
|
modified: '2025-01-15',
|
|
name: 'Go',
|
|
category: 'programming-language',
|
|
level: 'advanced',
|
|
};
|
|
await writeEntity(skill, tempDir);
|
|
expect(await entityExists('skill', 'go-lang', tempDir)).toBe(true);
|
|
|
|
await deleteEntity('skill', 'go-lang', tempDir);
|
|
expect(await entityExists('skill', 'go-lang', tempDir)).toBe(false);
|
|
});
|
|
|
|
it('throws descriptive error when entity not found', async () => {
|
|
await expect(deleteEntity('skill', 'nonexistent', tempDir)).rejects.toThrow(
|
|
/Entity not found: skill "nonexistent"/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('entityExists', () => {
|
|
it('returns false for non-existent entity', async () => {
|
|
expect(await entityExists('person', 'nobody', tempDir)).toBe(false);
|
|
});
|
|
|
|
it('returns true for existing entity', async () => {
|
|
const person: Person = {
|
|
id: 'andre-knie',
|
|
type: 'person',
|
|
created: '2025-01-15',
|
|
modified: '2025-09-01',
|
|
name: { first: 'Andre', last: 'Knie', display: 'Andre Knie' },
|
|
};
|
|
await writeEntity(person, tempDir);
|
|
expect(await entityExists('person', 'andre-knie', tempDir)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('multiple profiles coexist (Req 1.5)', () => {
|
|
it('supports multiple person profiles in the same KB', async () => {
|
|
const person1: Person = {
|
|
id: 'andre-knie',
|
|
type: 'person',
|
|
created: '2025-01-15',
|
|
modified: '2025-09-01',
|
|
name: { first: 'Andre', last: 'Knie', display: 'Andre Knie' },
|
|
};
|
|
const person2: Person = {
|
|
id: 'claudia-froldi',
|
|
type: 'person',
|
|
created: '2025-01-15',
|
|
modified: '2025-09-01',
|
|
name: { first: 'Claudia', last: 'Froldi', display: 'Claudia Froldi' },
|
|
};
|
|
|
|
await writeEntity(person1, tempDir);
|
|
await writeEntity(person2, tempDir);
|
|
|
|
const ids = await listEntities('person', tempDir);
|
|
expect(ids.sort()).toEqual(['andre-knie', 'claudia-froldi']);
|
|
|
|
const read1 = await readEntity('person', 'andre-knie', tempDir);
|
|
const read2 = await readEntity('person', 'claudia-froldi', tempDir);
|
|
expect(read1.id).toBe('andre-knie');
|
|
expect(read2.id).toBe('claudia-froldi');
|
|
});
|
|
});
|
|
});
|