import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, rmSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { Repository } from './repository.js'; describe('Repository dashboard widgets', () => { let tmpDir: string; let repo: Repository; beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), 'dashboard-repo-test-')); repo = new Repository(join(tmpDir, 'test.db')); }); afterEach(() => { rmSync(tmpDir, { recursive: true, force: true }); }); it('creates and lists widgets scoped to user_id', async () => { const a = await repo.createDashboardWidget({ userId: 'u1', slug: 'memo', title: 'Memo', content: 'hello' }); await repo.createDashboardWidget({ userId: 'u2', slug: 'memo', title: 'Memo2', content: 'other-user' }); const widgets = await repo.listDashboardWidgets('u1'); expect(widgets).toHaveLength(1); expect(widgets[0]!.id).toBe(a.id); expect(widgets[0]!.slug).toBe('memo'); expect(widgets[0]!.title).toBe('Memo'); expect(widgets[0]!.markdownContent).toBe('hello'); }); it('rejects duplicate slug for same user', async () => { await repo.createDashboardWidget({ userId: 'u1', slug: 'memo', title: 'a', content: '' }); await expect( repo.createDashboardWidget({ userId: 'u1', slug: 'memo', title: 'b', content: '' }) ).rejects.toThrow(); }); it('updates content and bumps updated_at', async () => { const w = await repo.createDashboardWidget({ userId: 'u1', slug: 's', title: 't', content: 'old' }); const originalUpdatedAt = w.updatedAt; await new Promise(r => setTimeout(r, 1100)); // datetime('now') has 1s precision const updated = await repo.updateDashboardWidget(w.id, 'u1', { content: 'new' }); expect(updated.markdownContent).toBe('new'); expect(updated.updatedAt).not.toBe(originalUpdatedAt); }); it('updateDashboardWidget rejects updates from other users', async () => { const w = await repo.createDashboardWidget({ userId: 'u1', slug: 's', title: 't', content: 'orig' }); await expect( repo.updateDashboardWidget(w.id, 'u2', { content: 'hack' }) ).rejects.toThrow(/not found/i); const list = await repo.listDashboardWidgets('u1'); expect(list[0]!.markdownContent).toBe('orig'); }); it('upserts by (user_id, slug): returns existing if slug already exists', async () => { const created = await repo.upsertDashboardWidgetBySlug({ userId: 'u1', slug: 'news', title: 'News', content: 'a' }); const second = await repo.upsertDashboardWidgetBySlug({ userId: 'u1', slug: 'news', content: 'b' }); expect(second.id).toBe(created.id); expect(second.markdownContent).toBe('b'); }); it('deletes widget by id', async () => { const w = await repo.createDashboardWidget({ userId: 'u1', slug: 's', title: 't', content: '' }); await repo.deleteDashboardWidget(w.id, 'u1'); const list = await repo.listDashboardWidgets('u1'); expect(list).toHaveLength(0); }); it('reorders widgets by id list', async () => { const a = await repo.createDashboardWidget({ userId: 'u1', slug: 'a', title: 'A', content: '' }); const b = await repo.createDashboardWidget({ userId: 'u1', slug: 'b', title: 'B', content: '' }); const c = await repo.createDashboardWidget({ userId: 'u1', slug: 'c', title: 'C', content: '' }); await repo.reorderDashboardWidgets('u1', [c.id, a.id, b.id]); const list = await repo.listDashboardWidgets('u1'); expect(list.map(w => w.slug)).toEqual(['c', 'a', 'b']); }); it('reorder rejects ids from other users (silent skip + leave order intact)', async () => { const u1a = await repo.createDashboardWidget({ userId: 'u1', slug: 'a', title: 'A', content: '' }); const u2x = await repo.createDashboardWidget({ userId: 'u2', slug: 'x', title: 'X', content: '' }); await repo.reorderDashboardWidgets('u1', [u2x.id, u1a.id]); const list = await repo.listDashboardWidgets('u1'); expect(list.map(w => w.id)).toEqual([u1a.id]); // u2x should not appear }); });