Files
Spendlight/server/test/ideas.test.ts
Chris Olson c3cf0f3eb8 Milestones 0 and 1: foundation and money in (20261007.10)
Foundation: single arm64 container (Hono + SQLite via Drizzle, React + Vite),
local sign-in with long-lived sessions and passkeys, roles (read-only, full
access, admin), nightly and pre-upgrade backups, light and dark themes,
date-based version numbers, and the ideas lightbulb behind IDEAS_ENABLED.

Money in: accounts with balances, CSV import with bank detection, a column
mapping pop-up for unknown layouts, duplicate checks, transfer pairing and
undo; one-tap review with learned rules; activity with search, splits, tags,
notes and cash entries. Statement files are blocked from commits by a
pre-commit hook; tests use synthetic fixtures only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:52:07 -04:00

50 lines
2.3 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest';
import { Client, makeApp, setUpAdmin, type TestApp } from './helpers';
let t: TestApp;
afterEach(() => t.cleanup());
describe('ideas box', () => {
it('does not exist when IDEAS_ENABLED is off', async () => {
t = await makeApp({ IDEAS_ENABLED: 'false' });
const admin = await setUpAdmin(t);
expect((await admin.get('/api/v1/config')).json.ideasEnabled).toBe(false);
expect((await admin.post('/api/v1/ideas', { title: 'Hello' })).status).toBe(404);
expect((await admin.get('/api/v1/ideas')).status).toBe(404);
});
it('takes ideas from anyone signed in and lets an admin triage and export them', async () => {
t = await makeApp({ IDEAS_ENABLED: 'true' });
const admin = await setUpAdmin(t);
const added = await admin.post('/api/v1/people', { displayName: 'Sam', username: 'sam', access: 'read', password: 'sams-password' });
expect(added.status).toBe(200);
const sam = new Client(t);
await sam.post('/api/v1/auth/login', { username: 'sam', password: 'sams-password' });
expect((await new Client(t).post('/api/v1/ideas', { title: 'Anonymous' })).status).toBe(401);
expect((await sam.post('/api/v1/ideas', { title: ' ' })).status).toBe(400);
const idea = await sam.post('/api/v1/ideas', {
title: 'Show a coffee total',
description: 'How much did we spend on coffee this year?',
path: '/insights',
});
expect(idea.status).toBe(200);
expect(idea.json).toMatchObject({ status: 'new', author: 'Sam', appVersion: '20261007.01', path: '/insights' });
expect((await sam.get('/api/v1/ideas')).status).toBe(403);
const list = (await admin.get('/api/v1/ideas')).json;
expect(list).toHaveLength(1);
const planned = await admin.patch(`/api/v1/ideas/${idea.json.id}`, { status: 'planned', adminNote: 'Milestone 2' });
expect(planned.json).toMatchObject({ status: 'planned', adminNote: 'Milestone 2' });
expect((await admin.get('/api/v1/ideas?status=new')).json).toHaveLength(0);
const md = await admin.get('/api/v1/ideas/export.md');
expect(md.headers.get('content-type')).toContain('text/markdown');
expect(md.text).toContain(`## #${idea.json.id}: Show a coffee total`);
expect(md.text).toContain('- Status: planned');
expect(md.text).toContain('How much did we spend on coffee this year?');
});
});