import-importers-golden.test.mjs
332 lines 13.4 KB
Raw
1 /**
2 * Golden import tests: synthetic fixtures → vault notes with expected SPEC-aligned frontmatter.
3 * Skips notion (live API), audio/video (Whisper + OPENAI_API_KEY).
4 */
5 import { describe, it, before, after } from 'node:test';
6 import assert from 'node:assert';
7 import path from 'path';
8 import fs from 'fs';
9 import { fileURLToPath } from 'url';
10 import { runImport } from '../lib/import.mjs';
11 import { readNote } from '../lib/vault.mjs';
12
13 const __dirname = path.dirname(fileURLToPath(import.meta.url));
14 const fixturesRoot = path.join(__dirname, 'fixtures', 'import');
15 const fixtureMarkdown = path.join(__dirname, 'fixtures', 'markdown-import', 'simple.md');
16 const fixturePdf = path.join(__dirname, 'fixtures', 'pdf-import', 'hello.pdf');
17 const fixtureDocx = path.join(__dirname, 'fixtures', 'docx-import', 'hello.docx');
18 const fixtureGenericCsv = path.join(__dirname, 'fixtures', 'generic-csv-import', 'sample.csv');
19 const fixtureJsonRows = path.join(__dirname, 'fixtures', 'json-rows-import', 'sample.json');
20 const fixtureVcf = path.join(__dirname, 'fixtures', 'vcf-import', 'sample.vcf');
21 const testVault = path.join(__dirname, 'fixtures', 'tmp-import-golden-vault');
22
23 function assertIsoDate(value) {
24 assert(typeof value === 'string' && value.length >= 10, `expected date-like string, got ${value}`);
25 }
26
27 describe('import golden fixtures', () => {
28 before(() => {
29 if (fs.existsSync(testVault)) fs.rmSync(testVault, { recursive: true });
30 fs.mkdirSync(testVault, { recursive: true });
31 });
32
33 after(() => {
34 if (fs.existsSync(testVault)) {
35 try {
36 fs.rmSync(testVault, { recursive: true });
37 } catch (_) {}
38 }
39 });
40
41 it('markdown', async () => {
42 const result = await runImport('markdown', fixtureMarkdown, {
43 vaultPath: testVault,
44 outputDir: 'inbox/golden-md',
45 dryRun: false,
46 });
47 assert.strictEqual(result.count, 1);
48 const note = readNote(testVault, result.imported[0].path);
49 assert.strictEqual(note.frontmatter.source, 'markdown');
50 assertIsoDate(String(note.frontmatter.date || ''));
51 });
52
53 it('pdf', async () => {
54 const result = await runImport('pdf', fixturePdf, {
55 vaultPath: testVault,
56 outputDir: 'inbox/golden-pdf',
57 dryRun: false,
58 });
59 assert.strictEqual(result.count, 1);
60 const note = readNote(testVault, result.imported[0].path);
61 assert.strictEqual(note.frontmatter.source, 'pdf-import');
62 assert.strictEqual(note.frontmatter.pdf_file, 'hello.pdf');
63 assert.equal(Number(note.frontmatter.pdf_pages), 1);
64 assert.ok(String(note.frontmatter.source_id || '').length >= 16);
65 assertIsoDate(String(note.frontmatter.date || ''));
66 assert(note.body.includes('Knowtation PDF fixture'));
67 });
68
69 it('docx', async () => {
70 const result = await runImport('docx', fixtureDocx, {
71 vaultPath: testVault,
72 outputDir: 'inbox/golden-docx',
73 dryRun: false,
74 });
75 assert.strictEqual(result.count, 1);
76 const note = readNote(testVault, result.imported[0].path);
77 assert.strictEqual(note.frontmatter.source, 'docx-import');
78 assert.strictEqual(note.frontmatter.docx_file, 'hello.docx');
79 assert.ok(String(note.frontmatter.source_id || '').length >= 16);
80 assertIsoDate(String(note.frontmatter.date || ''));
81 assert(note.body.includes('Knowtation DOCX fixture'));
82 assert(note.body.includes('Second line for golden test'));
83 });
84
85 it('markdown empty file', async () => {
86 const input = path.join(fixturesRoot, 'empty.md');
87 const result = await runImport('markdown', input, {
88 vaultPath: testVault,
89 outputDir: 'inbox/golden-empty-md',
90 dryRun: false,
91 });
92 assert.strictEqual(result.count, 1);
93 const note = readNote(testVault, result.imported[0].path);
94 assert.strictEqual(note.frontmatter.source, 'markdown');
95 assert.strictEqual(String(note.body || '').trim(), '');
96 });
97
98 it('chatgpt-export', async () => {
99 const input = path.join(fixturesRoot, 'chatgpt-export');
100 const result = await runImport('chatgpt-export', input, {
101 vaultPath: testVault,
102 outputDir: 'inbox/golden-chatgpt',
103 dryRun: false,
104 });
105 assert.strictEqual(result.count, 1);
106 const note = readNote(testVault, result.imported[0].path);
107 assert.strictEqual(note.frontmatter.source, 'chatgpt');
108 assert.strictEqual(note.frontmatter.title, 'Fixture conversation');
109 assert.ok(String(note.frontmatter.source_id || '').startsWith('chatgpt_'));
110 assertIsoDate(String(note.frontmatter.date || ''));
111 assert(note.body.includes('Fixture user line'));
112 });
113
114 it('claude-export (JSON)', async () => {
115 const input = path.join(fixturesRoot, 'claude-export', 'sample.json');
116 const result = await runImport('claude-export', input, {
117 vaultPath: testVault,
118 outputDir: 'inbox/golden-claude',
119 dryRun: false,
120 });
121 assert.strictEqual(result.count, 1);
122 const note = readNote(testVault, result.imported[0].path);
123 assert.strictEqual(note.frontmatter.source, 'claude');
124 assert.strictEqual(note.frontmatter.title, 'Fixture Claude note');
125 assert.strictEqual(note.frontmatter.source_id, 'claude-fix-1');
126 assert.strictEqual(note.frontmatter.date, '2024-01-15');
127 assert(note.body.includes('Fixture body'));
128 });
129
130 it('mem0-export', async () => {
131 const input = path.join(fixturesRoot, 'mem0-export', 'sample.json');
132 const result = await runImport('mem0-export', input, {
133 vaultPath: testVault,
134 outputDir: 'inbox/golden-mem0',
135 dryRun: false,
136 });
137 assert.strictEqual(result.count, 1);
138 const note = readNote(testVault, result.imported[0].path);
139 assert.strictEqual(note.frontmatter.source, 'mem0');
140 assert.strictEqual(note.frontmatter.source_id, 'mem0-fix-1');
141 assert(note.body.includes('Synthetic Mem0'));
142 });
143
144 it('mif', async () => {
145 const input = path.join(fixturesRoot, 'mif', 'sample.memory.md');
146 const result = await runImport('mif', input, {
147 vaultPath: testVault,
148 outputDir: 'inbox/golden-mif',
149 dryRun: false,
150 });
151 assert.strictEqual(result.count, 1);
152 const note = readNote(testVault, result.imported[0].path);
153 assert.strictEqual(note.frontmatter.source, 'mif');
154 assert.strictEqual(note.frontmatter.source_id, 'mif-fix-1');
155 assert(note.body.includes('MIF import golden'));
156 });
157
158 it('jira-export', async () => {
159 const input = path.join(fixturesRoot, 'jira-export', 'issues.csv');
160 const result = await runImport('jira-export', input, {
161 vaultPath: testVault,
162 outputDir: 'inbox/golden-jira',
163 dryRun: false,
164 });
165 assert.strictEqual(result.count, 1);
166 const note = readNote(testVault, result.imported[0].path);
167 assert.strictEqual(note.frontmatter.source, 'jira');
168 assert.strictEqual(note.frontmatter.source_id, 'PROJ-1');
169 assert.strictEqual(note.frontmatter.title, 'Fixture Jira issue');
170 assert.ok(note.body.includes('## All CSV fields (JSON)'));
171 assert.ok(note.body.includes('"Issue key": "PROJ-1"'));
172 assertIsoDate(String(note.frontmatter.date || ''));
173 });
174
175 it('linear-export', async () => {
176 const input = path.join(fixturesRoot, 'linear-export', 'issues.csv');
177 const result = await runImport('linear-export', input, {
178 vaultPath: testVault,
179 outputDir: 'inbox/golden-linear',
180 dryRun: false,
181 });
182 assert.strictEqual(result.count, 1);
183 const note = readNote(testVault, result.imported[0].path);
184 assert.strictEqual(note.frontmatter.source, 'linear');
185 assert.strictEqual(note.frontmatter.source_id, 'LIN-1');
186 assertIsoDate(String(note.frontmatter.date || ''));
187 assert(note.body.includes('## All CSV fields (JSON)'));
188 assert(note.body.includes('"id": "LIN-1"'));
189 assert(note.body.includes('Fixture Linear'));
190 });
191
192 it('notebooklm (JSON)', async () => {
193 const input = path.join(fixturesRoot, 'notebooklm', 'sample.json');
194 const result = await runImport('notebooklm', input, {
195 vaultPath: testVault,
196 outputDir: 'inbox/golden-nblm',
197 dryRun: false,
198 });
199 assert.strictEqual(result.count, 1);
200 const note = readNote(testVault, result.imported[0].path);
201 assert.strictEqual(note.frontmatter.source, 'notebooklm');
202 assert.strictEqual(note.frontmatter.source_id, 'nb-fix-1');
203 assert(note.body.includes('NotebookLM'));
204 });
205
206 it('gdrive (folder of md)', async () => {
207 const input = path.join(fixturesRoot, 'gdrive');
208 const result = await runImport('gdrive', input, {
209 vaultPath: testVault,
210 outputDir: 'inbox/golden-gdrive',
211 dryRun: false,
212 });
213 assert.strictEqual(result.count, 1);
214 const note = readNote(testVault, result.imported[0].path);
215 assert.strictEqual(note.frontmatter.source, 'gdrive');
216 assert.strictEqual(note.frontmatter.source_id, 'doc1');
217 assert(note.body.includes('Synthetic Google Drive'));
218 });
219
220 it('generic-csv', async () => {
221 const result = await runImport('generic-csv', fixtureGenericCsv, {
222 vaultPath: testVault,
223 outputDir: 'inbox/golden-generic-csv',
224 dryRun: false,
225 });
226 assert.strictEqual(result.count, 2);
227 const a = readNote(testVault, result.imported[0].path);
228 const b = readNote(testVault, result.imported[1].path);
229 assert.strictEqual(a.frontmatter.source, 'csv-import');
230 assert.strictEqual(a.frontmatter.csv_file, 'sample.csv');
231 assert.strictEqual(a.frontmatter.title, 'sample.csv · Alice');
232 assert.equal(Number(a.frontmatter.row_index), 1);
233 {
234 const h = a.frontmatter.import_column_headers;
235 const cols = typeof h === 'string' ? JSON.parse(h) : h;
236 assert.deepStrictEqual(cols, ['name', 'amount', 'note']);
237 }
238 assert(a.body.startsWith('# sample.csv · Alice'));
239 assert(a.body.includes('## Full row (JSON)'));
240 assert(a.body.includes('"name": "Alice"'));
241 assert(a.body.includes('Alice') && a.body.includes('10'));
242 assert.equal(Number(b.frontmatter.row_index), 2);
243 assert.strictEqual(b.frontmatter.title, 'sample.csv · Bob');
244 assert(b.body.startsWith('# sample.csv · Bob'));
245 assert(b.body.includes('Bob'));
246 });
247
248 it('json-rows', async () => {
249 const result = await runImport('json-rows', fixtureJsonRows, {
250 vaultPath: testVault,
251 outputDir: 'inbox/golden-json-rows',
252 dryRun: false,
253 });
254 assert.strictEqual(result.count, 2);
255 const n0 = readNote(testVault, result.imported[0].path);
256 const n1 = readNote(testVault, result.imported[1].path);
257 assert.strictEqual(n0.frontmatter.source, 'json-import');
258 assert.strictEqual(n0.frontmatter.json_file, 'sample.json');
259 assert.equal(Number(n0.frontmatter.item_index), 0);
260 assert.strictEqual(n0.frontmatter.source_id, 'row-a');
261 assert(n0.body.includes('"name": "First"'));
262 assert.equal(Number(n1.frontmatter.item_index), 1);
263 assert(n1.body.includes('Second'));
264 });
265
266 it('excel-xlsx', async () => {
267 const ExcelJS = (await import('exceljs')).default;
268 const xlsxPath = path.join(testVault, 'golden-temp.xlsx');
269 const wb = new ExcelJS.Workbook();
270 const ws = wb.addWorksheet('First');
271 ws.getRow(1).values = [null, 'name', 'n'];
272 ws.getRow(2).values = [null, 'Alpha', 10];
273 ws.getRow(3).values = [null, 'Beta', 20];
274 const buffer = await wb.xlsx.writeBuffer();
275 fs.writeFileSync(xlsxPath, buffer);
276 const result = await runImport('excel-xlsx', xlsxPath, {
277 vaultPath: testVault,
278 outputDir: 'inbox/golden-excel',
279 dryRun: false,
280 });
281 assert.strictEqual(result.count, 2);
282 const a = readNote(testVault, result.imported[0].path);
283 const b = readNote(testVault, result.imported[1].path);
284 assert.strictEqual(a.frontmatter.source, 'xlsx-import');
285 assert.strictEqual(a.frontmatter.title, 'golden-temp.xlsx · Alpha');
286 assert.equal(Number(a.frontmatter.row_index), 1);
287 assert(a.body.startsWith('# golden-temp.xlsx · Alpha'));
288 assert(a.body.includes('## Full row (JSON)'));
289 assert(a.body.includes('"name": "Alpha"'));
290 assert(a.body.includes('Alpha') && a.body.includes('10'));
291 assert.strictEqual(b.frontmatter.title, 'golden-temp.xlsx · Beta');
292 assert(b.body.startsWith('# golden-temp.xlsx · Beta'));
293 assert(b.body.includes('Beta'));
294 });
295
296 it('vcf', async () => {
297 const result = await runImport('vcf', fixtureVcf, {
298 vaultPath: testVault,
299 outputDir: 'inbox/golden-vcf',
300 dryRun: false,
301 });
302 assert.strictEqual(result.count, 2);
303 const n0 = readNote(testVault, result.imported[0].path);
304 const n1 = readNote(testVault, result.imported[1].path);
305 assert.strictEqual(n0.frontmatter.source, 'vcf-import');
306 assert(n0.path.includes('contacts/') && n0.path.includes('vcf'));
307 assert(n0.body.includes('Alice') && n0.body.includes('[email protected]'));
308 assert(n1.body.includes('Bob') && n1.body.includes('[email protected]'));
309 });
310
311 it('google-sheets: requires service account in environment', async () => {
312 const a = process.env.GOOGLE_APPLICATION_CREDENTIALS;
313 const j = process.env.GOOGLE_SERVICE_ACCOUNT_JSON;
314 try {
315 delete process.env.GOOGLE_APPLICATION_CREDENTIALS;
316 delete process.env.GOOGLE_SERVICE_ACCOUNT_JSON;
317 await assert.rejects(
318 runImport('google-sheets', '1dummySpreadsheetId', {
319 vaultPath: testVault,
320 outputDir: 'inbox/golden-sheets',
321 dryRun: true,
322 }),
323 /google-sheets import: set GOOGLE_SERVICE_ACCOUNT_JSON/,
324 );
325 } finally {
326 if (a === undefined) delete process.env.GOOGLE_APPLICATION_CREDENTIALS;
327 else process.env.GOOGLE_APPLICATION_CREDENTIALS = a;
328 if (j === undefined) delete process.env.GOOGLE_SERVICE_ACCOUNT_JSON;
329 else process.env.GOOGLE_SERVICE_ACCOUNT_JSON = j;
330 }
331 });
332 });
File History 1 commit