import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import Database from 'better-sqlite3'; import { mkdtemp, writeFile, mkdir, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { runMigrations } from '@main/db/migrations/index.js'; import { NoteRepository } from '@main/repository/NoteRepository.js'; import { ImportService } from '@main/services/ImportService.js'; import { MediaStore } from '@main/services/MediaStore.js'; describe('ImportService.applySyncFromDir', () => { let db: Database.Database; let repo: NoteRepository; let svc: ImportService; let workDir: string; beforeEach(async () => { db = new Database(':memory:'); db.pragma('foreign_keys = ON'); runMigrations(db); repo = new NoteRepository(db); workDir = await mkdtemp(join(tmpdir(), 'inkling-sync-')); const mediaStore = new MediaStore(workDir); svc = new ImportService(repo, mediaStore); }); afterEach(async () => { db.close(); await rm(workDir, { recursive: true, force: true }); }); it('inserts new notes and reports changedCount', async () => { const notesDir = join(workDir, 'notes'); await mkdir(notesDir, { recursive: true }); await writeFile( join(notesDir, 'a.md'), `---\nid: 00000000-0000-0000-0000-000000000001\ncreated_at: 2026-05-09T00:00:00Z\nupdated_at: 2026-05-10T00:00:00Z\ntitle: title\ntitle_source: ai\nsummary: summary\nsummary_source: ai\nstatus: active\ninkling_export_version: 1\n---\n\n# title\n\n> summary\n\nbody\n` ); const r = await svc.applySyncFromDir(workDir); expect(r.changedCount).toBe(1); const note = repo.findById('00000000-0000-0000-0000-000000000001'); expect(note?.rawText).toBe('body'); }); it('skips unchanged notes (no changedCount increment)', async () => { const created = repo.create({ rawText: 'body' }); db.prepare(`UPDATE notes SET updated_at=? WHERE id=?`).run('2026-05-15T00:00:00Z', created.id); const notesDir = join(workDir, 'notes'); await mkdir(notesDir, { recursive: true }); await writeFile( join(notesDir, 'a.md'), `---\nid: ${created.id}\ncreated_at: 2026-05-09T00:00:00Z\nupdated_at: 2026-05-10T00:00:00Z\ntitle: t\ntitle_source: ai\nsummary: s\nsummary_source: ai\nstatus: active\ninkling_export_version: 1\n---\n\n# t\n\n> s\n\nbody\n` ); const r = await svc.applySyncFromDir(workDir); expect(r.changedCount).toBe(0); }); it('returns changedCount=0 for an empty notes directory', async () => { const notesDir = join(workDir, 'notes'); await mkdir(notesDir, { recursive: true }); const r = await svc.applySyncFromDir(workDir); expect(r.changedCount).toBe(0); }); it('updates a note when source updatedAt is newer', async () => { const created = repo.create({ rawText: 'old body' }); db.prepare(`UPDATE notes SET updated_at=? WHERE id=?`).run('2026-05-01T00:00:00Z', created.id); const notesDir = join(workDir, 'notes'); await mkdir(notesDir, { recursive: true }); await writeFile( join(notesDir, 'a.md'), `---\nid: ${created.id}\ncreated_at: 2026-05-09T00:00:00Z\nupdated_at: 2026-05-10T00:00:00Z\ntitle: t\ntitle_source: ai\nsummary: s\nsummary_source: ai\nstatus: active\ninkling_export_version: 1\n---\n\n# t\n\n> s\n\nnew body\n` ); const r = await svc.applySyncFromDir(workDir); expect(r.changedCount).toBe(1); const note = repo.findById(created.id); expect(note?.rawText).toBe('new body'); }); it('preserves status field from frontmatter', async () => { const notesDir = join(workDir, 'notes'); await mkdir(notesDir, { recursive: true }); await writeFile( join(notesDir, 'a.md'), `---\nid: 00000000-0000-0000-0000-000000000002\ncreated_at: 2026-05-09T00:00:00Z\nupdated_at: 2026-05-10T00:00:00Z\ntitle: t\ntitle_source: ai\nsummary: s\nsummary_source: ai\nstatus: archived\nstatus_changed_at: 2026-05-08T00:00:00Z\nmove_reason: done\ninkling_export_version: 1\n---\n\n# t\n\n> s\n\nbody\n` ); await svc.applySyncFromDir(workDir); const note = repo.findById('00000000-0000-0000-0000-000000000002'); expect(note?.status).toBe('archived'); expect(note?.statusChangedAt).toBe('2026-05-08T00:00:00Z'); expect(note?.moveReason).toBe('done'); }); it('preserves dueDate from frontmatter', async () => { const notesDir = join(workDir, 'notes'); await mkdir(notesDir, { recursive: true }); await writeFile( join(notesDir, 'a.md'), `---\nid: 00000000-0000-0000-0000-000000000003\ncreated_at: 2026-05-09T00:00:00Z\nupdated_at: 2026-05-10T00:00:00Z\ntitle: t\ntitle_source: ai\nsummary: s\nsummary_source: ai\nstatus: active\ndue_date: 2026-06-01\ndue_date_source: user\ninkling_export_version: 1\n---\n\n# t\n\n> s\n\nbody\n` ); await svc.applySyncFromDir(workDir); const note = repo.findById('00000000-0000-0000-0000-000000000003'); expect(note?.dueDate).toBe('2026-06-01'); expect(note?.dueDateEditedByUser).toBe(true); }); });