130 lines
4.4 KiB
JavaScript
130 lines
4.4 KiB
JavaScript
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-groups-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-groups-${suffix}`, `category-groups-${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 groups can be created, listed, renamed, and deleted', async () => {
|
|
const db = getDb();
|
|
const userId = createUser(db, 'crud');
|
|
|
|
const created = await callCategoriesRoute('/groups', 'post', { userId, body: { name: 'Bills' } });
|
|
assert.equal(created.status, 201);
|
|
assert.equal(created.data.name, 'Bills');
|
|
const groupId = created.data.id;
|
|
|
|
const listed = await callCategoriesRoute('/groups', 'get', { userId });
|
|
assert.equal(listed.status, 200);
|
|
assert.deepEqual(listed.data.map(g => g.name), ['Bills']);
|
|
|
|
const renamed = await callCategoriesRoute('/groups/:id', 'put', { userId, params: { id: groupId }, body: { name: 'Fixed Costs' } });
|
|
assert.equal(renamed.status, 200);
|
|
assert.equal(renamed.data.name, 'Fixed Costs');
|
|
|
|
const deleted = await callCategoriesRoute('/groups/:id', 'delete', { userId, params: { id: groupId } });
|
|
assert.equal(deleted.status, 200);
|
|
assert.equal(deleted.data.success, true);
|
|
|
|
const listedAfter = await callCategoriesRoute('/groups', 'get', { userId });
|
|
assert.deepEqual(listedAfter.data, []);
|
|
});
|
|
|
|
test('creating a duplicate group name for the same user is rejected', async () => {
|
|
const db = getDb();
|
|
const userId = createUser(db, 'dup');
|
|
|
|
await callCategoriesRoute('/groups', 'post', { userId, body: { name: 'Everyday' } });
|
|
const dup = await callCategoriesRoute('/groups', 'post', { userId, body: { name: 'Everyday' } });
|
|
|
|
assert.equal(dup.status, 409);
|
|
});
|
|
|
|
test('assigning a category to a group requires the group to belong to the user', async () => {
|
|
const db = getDb();
|
|
const ownerId = createUser(db, 'group-owner');
|
|
const otherId = createUser(db, 'group-other');
|
|
|
|
const category = createCategory(db, ownerId, 'Groceries');
|
|
const otherGroup = await callCategoriesRoute('/groups', 'post', { userId: otherId, body: { name: 'Not Mine' } });
|
|
|
|
const result = await callCategoriesRoute('/:id', 'put', {
|
|
userId: ownerId,
|
|
params: { id: category },
|
|
body: { name: 'Groceries', group_id: otherGroup.data.id },
|
|
});
|
|
|
|
assert.equal(result.status, 404);
|
|
});
|
|
|
|
test('assigning a category to an owned group succeeds and is reflected on read', async () => {
|
|
const db = getDb();
|
|
const userId = createUser(db, 'group-assign');
|
|
|
|
const category = createCategory(db, userId, 'Dining');
|
|
const group = await callCategoriesRoute('/groups', 'post', { userId, body: { name: 'Everyday' } });
|
|
|
|
const updated = await callCategoriesRoute('/:id', 'put', {
|
|
userId,
|
|
params: { id: category },
|
|
body: { name: 'Dining', group_id: group.data.id },
|
|
});
|
|
|
|
assert.equal(updated.status, 200);
|
|
assert.equal(updated.data.group_id, group.data.id);
|
|
});
|