const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const dbPath = path.join(os.tmpdir(), `bill-tracker-category-reorder-test-${process.pid}.sqlite`); process.env.DB_PATH = dbPath; const { getDb, closeDb } = require('../db/database'); function createUser(db, suffix) { return db.prepare(` INSERT INTO users (username, password_hash, role, active, email, created_at, updated_at) VALUES (?, 'x', 'user', 1, ?, datetime('now'), datetime('now')) `).run(`category-reorder-${suffix}`, `category-reorder-${suffix}@local`).lastInsertRowid; } function createCategory(db, userId, name) { return db.prepare(` INSERT INTO categories (user_id, name) VALUES (?, ?) `).run(userId, name).lastInsertRowid; } function callCategoriesRoute(routePath, method, { userId, params = {}, body = {} }) { const categoriesRouter = require('../routes/categories'); const layer = categoriesRouter.stack.find(item => item.route?.path === routePath && item.route.methods[method]); assert.ok(layer, `route ${method.toUpperCase()} ${routePath} should exist`); const handler = layer.route.stack[0].handle; return new Promise((resolve, reject) => { const req = { body, params, user: { id: userId, role: 'user' }, }; const res = { statusCode: 200, status(code) { this.statusCode = code; return this; }, json(data) { resolve({ status: this.statusCode, data }); }, }; try { handler(req, res); } catch (err) { reject(err); } }); } test.after(() => { closeDb(); for (const suffix of ['', '-wal', '-shm']) { fs.rmSync(`${dbPath}${suffix}`, { force: true }); } }); test('category reorder endpoint persists category order for the current user', async () => { const db = getDb(); const userId = createUser(db, 'owner'); const food = createCategory(db, userId, 'Food'); const loans = createCategory(db, userId, 'Loans'); const utilities = createCategory(db, userId, 'Utilities'); const response = await callCategoriesRoute('/reorder', 'put', { userId, body: { [utilities]: 0, [food]: 1, [loans]: 2, }, }); assert.equal(response.status, 200); assert.equal(response.data.success, true); const ordered = await callCategoriesRoute('/', 'get', { userId }); assert.deepEqual( ordered.data.filter(cat => [food, loans, utilities].includes(cat.id)).map(cat => cat.id), [utilities, food, loans], ); }); test('category reorder rejects categories outside the current user scope', async () => { const db = getDb(); const ownerId = createUser(db, 'scoped-owner'); const otherId = createUser(db, 'scoped-other'); const ownerCategory = createCategory(db, ownerId, 'Owner Category'); const otherCategory = createCategory(db, otherId, 'Other Category'); const response = await callCategoriesRoute('/reorder', 'put', { userId: ownerId, body: { [ownerCategory]: 0, [otherCategory]: 1, }, }); assert.equal(response.status, 404); });