77 lines
4 KiB
JavaScript
77 lines
4 KiB
JavaScript
|
|
// RBAC coverage for asset comments: the guard resolves the project via the
|
||
|
|
// asset, requiring view to read and edit to write. Also verifies the author id
|
||
|
|
// is recorded from req.user (regression for the old req.session.userId bug).
|
||
|
|
// Skips without TEST_DATABASE_URL.
|
||
|
|
import { test } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import express from 'express';
|
||
|
|
import { isTestDbConfigured, setupTestDb } from '../helpers/setup-db.js';
|
||
|
|
|
||
|
|
const SKIP = !isTestDbConfigured() && 'TEST_DATABASE_URL not set';
|
||
|
|
|
||
|
|
async function appWithComments(user) {
|
||
|
|
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
|
||
|
|
process.env.AUTH_ENABLED = 'true';
|
||
|
|
const { default: commentsRouter } = await import('../../src/routes/comments.js?v=' + Date.now());
|
||
|
|
const app = express();
|
||
|
|
app.use(express.json());
|
||
|
|
app.use((req, _res, next) => { req.user = user; next(); });
|
||
|
|
// Mirror index.js mount so :assetId flows through (mergeParams in the router).
|
||
|
|
app.use('/api/v1/assets/:assetId/comments', commentsRouter);
|
||
|
|
return new Promise(r => {
|
||
|
|
const srv = app.listen(0, '127.0.0.1', () =>
|
||
|
|
r({ baseUrl: 'http://127.0.0.1:' + srv.address().port, close: () => new Promise(rs => srv.close(rs)) }));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async function seed(pool) {
|
||
|
|
const proj = async (n) => (await pool.query(`INSERT INTO projects (name, s3_prefix) VALUES ($1,$1) RETURNING id`, [n])).rows[0].id;
|
||
|
|
const alpha = await proj('Alpha');
|
||
|
|
const beta = await proj('Beta');
|
||
|
|
const asset = async (pid, name) => (await pool.query(
|
||
|
|
`INSERT INTO assets (project_id, filename, display_name, media_type, status) VALUES ($1,$2,$2,'video','ready') RETURNING id`, [pid, name])).rows[0].id;
|
||
|
|
const assetA = await asset(alpha, 'a1');
|
||
|
|
const assetB = await asset(beta, 'b1');
|
||
|
|
const viewer = { id: (await pool.query(`INSERT INTO users (username, password_hash, role) VALUES ('vu','x','viewer') RETURNING id`)).rows[0].id, role: 'viewer' };
|
||
|
|
const editor = { id: (await pool.query(`INSERT INTO users (username, password_hash, role) VALUES ('ed','x','editor') RETURNING id`)).rows[0].id, role: 'editor' };
|
||
|
|
await pool.query(`INSERT INTO project_access (project_id, subject_type, subject_id, level) VALUES ($1,'user',$2,'view')`, [alpha, viewer.id]);
|
||
|
|
await pool.query(`INSERT INTO project_access (project_id, subject_type, subject_id, level) VALUES ($1,'user',$2,'edit')`, [alpha, editor.id]);
|
||
|
|
return { assetA, assetB, viewer, editor };
|
||
|
|
}
|
||
|
|
|
||
|
|
test('comments require view to read and block ungranted assets', { skip: SKIP }, async () => {
|
||
|
|
const pool = await setupTestDb();
|
||
|
|
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
|
||
|
|
try {
|
||
|
|
const { assetA, assetB, viewer } = await seed(pool);
|
||
|
|
const s = await appWithComments(viewer);
|
||
|
|
assert.equal((await fetch(s.baseUrl + '/api/v1/assets/' + assetA + '/comments')).status, 200);
|
||
|
|
assert.equal((await fetch(s.baseUrl + '/api/v1/assets/' + assetB + '/comments')).status, 403);
|
||
|
|
await s.close();
|
||
|
|
} finally { await pool.end(); }
|
||
|
|
});
|
||
|
|
|
||
|
|
test('posting a comment requires edit and records the author id from req.user', { skip: SKIP }, async () => {
|
||
|
|
const pool = await setupTestDb();
|
||
|
|
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
|
||
|
|
try {
|
||
|
|
const { assetA, viewer, editor } = await seed(pool);
|
||
|
|
|
||
|
|
// viewer (view-only) cannot post.
|
||
|
|
let s = await appWithComments(viewer);
|
||
|
|
let r = await fetch(s.baseUrl + '/api/v1/assets/' + assetA + '/comments', {
|
||
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ body: 'hi' }) });
|
||
|
|
assert.equal(r.status, 403);
|
||
|
|
await s.close();
|
||
|
|
|
||
|
|
// editor can post, and the author id is the editor (not null).
|
||
|
|
let e = await appWithComments(editor);
|
||
|
|
r = await fetch(e.baseUrl + '/api/v1/assets/' + assetA + '/comments', {
|
||
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ body: 'looks good' }) });
|
||
|
|
assert.equal(r.status, 201);
|
||
|
|
const created = await r.json();
|
||
|
|
assert.equal(created.user_id, editor.id, 'comment author must be req.user.id');
|
||
|
|
await e.close();
|
||
|
|
} finally { await pool.end(); }
|
||
|
|
});
|