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>
50 lines
2.3 KiB
TypeScript
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?');
|
|
});
|
|
});
|