Skip to content

Commit 2166069

Browse files
committed
test: add file-import tests — parseSkillFile, parseNoteDir/TaskDir/SkillDir (12 tests)
Cover event-sourced directory parsing, skill file parsing with steps, edge cases (missing files, no created event, confidence clamping).
1 parent 863cbf0 commit 2166069

1 file changed

Lines changed: 179 additions & 0 deletions

File tree

src/tests/file-import-gaps.test.ts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import * as os from 'os';
4+
import { parseSkillFile, parseNoteDir, parseTaskDir, parseSkillDir } from '@/lib/file-import';
5+
6+
function tmpDir(): string {
7+
return fs.mkdtempSync(path.join(os.tmpdir(), 'gm-import-'));
8+
}
9+
10+
// ---------------------------------------------------------------------------
11+
// parseSkillFile
12+
// ---------------------------------------------------------------------------
13+
14+
describe('parseSkillFile', () => {
15+
it('parses a skill markdown file', () => {
16+
const dir = tmpDir();
17+
const file = path.join(dir, 'deploy.md');
18+
fs.writeFileSync(file, `---
19+
tags: [ops, deploy]
20+
triggers: [deploy, release]
21+
source: user
22+
confidence: 0.9
23+
---
24+
25+
# Deploy to Production
26+
27+
Setup the deployment pipeline
28+
29+
## Steps
30+
31+
1. Build the Docker image
32+
2. Push to registry
33+
3. Deploy to Kubernetes
34+
`);
35+
36+
const result = parseSkillFile(file);
37+
expect(result).not.toBeNull();
38+
expect(result!.title).toBe('Deploy to Production');
39+
expect(result!.description).toBe('Setup the deployment pipeline');
40+
expect(result!.steps).toEqual(['Build the Docker image', 'Push to registry', 'Deploy to Kubernetes']);
41+
expect(result!.tags).toEqual(['ops', 'deploy']);
42+
expect(result!.triggers).toEqual(['deploy', 'release']);
43+
expect(result!.source).toBe('user');
44+
expect(result!.confidence).toBe(0.9);
45+
});
46+
47+
it('returns null for non-existent file', () => {
48+
expect(parseSkillFile('/nonexistent/skill.md')).toBeNull();
49+
});
50+
51+
it('handles skill without steps section', () => {
52+
const dir = tmpDir();
53+
const file = path.join(dir, 'simple.md');
54+
fs.writeFileSync(file, `---
55+
tags: [test]
56+
---
57+
58+
# Simple Skill
59+
60+
Just a description, no steps.
61+
`);
62+
63+
const result = parseSkillFile(file);
64+
expect(result).not.toBeNull();
65+
expect(result!.title).toBe('Simple Skill');
66+
expect(result!.steps).toEqual([]);
67+
expect(result!.description).toBe('Just a description, no steps.');
68+
});
69+
70+
it('clamps confidence to 0-1', () => {
71+
const dir = tmpDir();
72+
const file = path.join(dir, 'high.md');
73+
fs.writeFileSync(file, `---\nconfidence: 5\n---\n# High\nDesc\n`);
74+
const result = parseSkillFile(file);
75+
expect(result!.confidence).toBe(1);
76+
});
77+
});
78+
79+
// ---------------------------------------------------------------------------
80+
// parseNoteDir (event-sourced)
81+
// ---------------------------------------------------------------------------
82+
83+
describe('parseNoteDir', () => {
84+
it('parses note from events.jsonl + content.md', () => {
85+
const dir = tmpDir();
86+
const noteDir = path.join(dir, 'my-note');
87+
fs.mkdirSync(noteDir, { recursive: true });
88+
89+
fs.writeFileSync(path.join(noteDir, 'events.jsonl'),
90+
'{"ts":"2026-01-01T00:00:00Z","op":"created","id":"my-note","title":"My Note","tags":["test"],"createdAt":1000}\n');
91+
fs.writeFileSync(path.join(noteDir, 'content.md'), 'Note content here');
92+
93+
const result = parseNoteDir(noteDir);
94+
expect(result).not.toBeNull();
95+
expect(result!.id).toBe('my-note');
96+
expect(result!.title).toBe('My Note');
97+
expect(result!.content).toBe('Note content here');
98+
expect(result!.tags).toEqual(['test']);
99+
});
100+
101+
it('returns null for dir without events.jsonl', () => {
102+
const dir = tmpDir();
103+
expect(parseNoteDir(dir)).toBeNull();
104+
});
105+
106+
it('returns null for events without created event', () => {
107+
const dir = tmpDir();
108+
const noteDir = path.join(dir, 'bad');
109+
fs.mkdirSync(noteDir, { recursive: true });
110+
fs.writeFileSync(path.join(noteDir, 'events.jsonl'),
111+
'{"ts":"2026-01-01T00:00:00Z","op":"update","title":"x"}\n');
112+
expect(parseNoteDir(noteDir)).toBeNull();
113+
});
114+
115+
it('handles missing content.md', () => {
116+
const dir = tmpDir();
117+
const noteDir = path.join(dir, 'no-content');
118+
fs.mkdirSync(noteDir, { recursive: true });
119+
fs.writeFileSync(path.join(noteDir, 'events.jsonl'),
120+
'{"ts":"2026-01-01T00:00:00Z","op":"created","id":"nc","title":"No Content","tags":[],"createdAt":1000}\n');
121+
122+
const result = parseNoteDir(noteDir);
123+
expect(result).not.toBeNull();
124+
expect(result!.content).toBe('');
125+
});
126+
});
127+
128+
// ---------------------------------------------------------------------------
129+
// parseTaskDir (event-sourced)
130+
// ---------------------------------------------------------------------------
131+
132+
describe('parseTaskDir', () => {
133+
it('parses task from events.jsonl + description.md', () => {
134+
const dir = tmpDir();
135+
const taskDir = path.join(dir, 'my-task');
136+
fs.mkdirSync(taskDir, { recursive: true });
137+
138+
fs.writeFileSync(path.join(taskDir, 'events.jsonl'),
139+
'{"ts":"2026-01-01T00:00:00Z","op":"created","id":"my-task","title":"Fix Bug","status":"todo","priority":"high","tags":["bug"],"dueDate":null,"estimate":null,"completedAt":null,"createdAt":2000}\n');
140+
fs.writeFileSync(path.join(taskDir, 'description.md'), 'Fix the login bug');
141+
142+
const result = parseTaskDir(taskDir);
143+
expect(result).not.toBeNull();
144+
expect(result!.id).toBe('my-task');
145+
expect(result!.title).toBe('Fix Bug');
146+
expect(result!.status).toBe('todo');
147+
expect(result!.description).toBe('Fix the login bug');
148+
});
149+
150+
it('returns null without events.jsonl', () => {
151+
expect(parseTaskDir(tmpDir())).toBeNull();
152+
});
153+
});
154+
155+
// ---------------------------------------------------------------------------
156+
// parseSkillDir (event-sourced)
157+
// ---------------------------------------------------------------------------
158+
159+
describe('parseSkillDir', () => {
160+
it('parses skill from events.jsonl + description.md', () => {
161+
const dir = tmpDir();
162+
const skillDir = path.join(dir, 'deploy');
163+
fs.mkdirSync(skillDir, { recursive: true });
164+
165+
fs.writeFileSync(path.join(skillDir, 'events.jsonl'),
166+
'{"ts":"2026-01-01T00:00:00Z","op":"created","id":"deploy","title":"Deploy","tags":["ops"],"steps":["build","push"],"triggers":["deploy"],"inputHints":[],"filePatterns":[],"source":"user","confidence":1,"usageCount":0,"lastUsedAt":null,"createdAt":3000}\n');
167+
fs.writeFileSync(path.join(skillDir, 'description.md'), 'How to deploy');
168+
169+
const result = parseSkillDir(skillDir);
170+
expect(result).not.toBeNull();
171+
expect(result!.id).toBe('deploy');
172+
expect(result!.title).toBe('Deploy');
173+
expect(result!.steps).toEqual(['build', 'push']);
174+
});
175+
176+
it('returns null without events.jsonl', () => {
177+
expect(parseSkillDir(tmpDir())).toBeNull();
178+
});
179+
});

0 commit comments

Comments
 (0)