Release v2.20.2
This commit is contained in:
1 parent
220c3f401e
commit
61d553f74b
14 files changed
+395
-94
No files matched your search
@@ -4,6 +4,26 @@ const assert = require('node:assert/strict');
|
||||
require('../src/common');
|
||||
|
||||
const registerAuditLogRoutes = require('../src/web/routes/settings/audit-log');
|
||||
const { buildDiffSegments } = require('../src/web/public/js/settings/audit-log');
|
||||
|
||||
test('audit diff highlights only the differing middle span', () => {
|
||||
assert.deepEqual(buildDiffSegments('prefix old suffix', 'prefix new suffix'), {
|
||||
before: [
|
||||
{ text: 'prefix ', changed: false },
|
||||
{ text: 'old', changed: true },
|
||||
{ text: ' suffix', changed: false }
|
||||
],
|
||||
after: [
|
||||
{ text: 'prefix ', changed: false },
|
||||
{ text: 'new', changed: true },
|
||||
{ text: ' suffix', changed: false }
|
||||
]
|
||||
});
|
||||
assert.deepEqual(buildDiffSegments('', 'added'), {
|
||||
before: [],
|
||||
after: [{ text: 'added', changed: true }]
|
||||
});
|
||||
});
|
||||
|
||||
test('audit log uses its own read permission and shared pagination partial', async () => {
|
||||
const handlers = {};
|
||||
@@ -19,6 +39,16 @@ test('audit log uses its own read permission and shared pagination partial', asy
|
||||
async query(sql, params) {
|
||||
const query = String(sql);
|
||||
queries.push([query, params || []]);
|
||||
if (query.includes('SELECT e.details_json FROM o_audit_events e WHERE e.id = ?')) {
|
||||
return [[{
|
||||
details_json: JSON.stringify({
|
||||
changes: {
|
||||
'content.heading.value': { from: 'Old heading', to: 'New heading' },
|
||||
'content.qr.qr_svg': { from: '[Generated asset: 13 bytes; SHA-256 old]', to: '[Generated asset: 13 bytes; SHA-256 new]' }
|
||||
}
|
||||
})
|
||||
}]];
|
||||
}
|
||||
if (query.includes('SELECT COUNT(*) AS count')) {
|
||||
return [[{ count: 2 }]];
|
||||
}
|
||||
@@ -34,42 +64,8 @@ test('audit log uses its own read permission and shared pagination partial', asy
|
||||
event_type: 'login.success',
|
||||
target_label: 'newer',
|
||||
user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36',
|
||||
details_json: JSON.stringify({
|
||||
changes: {
|
||||
content: {
|
||||
from: { title: 'Same', body: { color: 'red', keep: 'same' } },
|
||||
to: { title: 'Same', body: { color: 'blue', keep: 'same' } }
|
||||
},
|
||||
permissions: {
|
||||
from: ['dashboard.read', 'screens.read'],
|
||||
to: ['screens.read', 'playlists.read']
|
||||
},
|
||||
backgroundColor: {
|
||||
from: null,
|
||||
to: '#111111'
|
||||
},
|
||||
logoPath: {
|
||||
from: '',
|
||||
to: '/media/logo.png'
|
||||
},
|
||||
'content.heading.value': {
|
||||
from: 'Old heading',
|
||||
to: 'New heading'
|
||||
},
|
||||
'content.added.value': {
|
||||
from: null,
|
||||
to: 'Added text'
|
||||
},
|
||||
'content.removed.value': {
|
||||
from: 'Removed text',
|
||||
to: null
|
||||
},
|
||||
'content.qr.qr_svg': {
|
||||
from: null,
|
||||
to: '[Generated asset: 13 bytes; SHA-256 fingerprint]'
|
||||
}
|
||||
}
|
||||
})
|
||||
details_json: '{}',
|
||||
has_changes: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@@ -77,7 +73,8 @@ test('audit log uses its own read permission and shared pagination partial', asy
|
||||
category: 'authentication',
|
||||
event_type: 'login.failed',
|
||||
target_label: 'older',
|
||||
details_json: null
|
||||
details_json: null,
|
||||
has_changes: 0
|
||||
}
|
||||
]];
|
||||
}
|
||||
@@ -114,31 +111,32 @@ test('audit log uses its own read permission and shared pagination partial', asy
|
||||
assert.match(response.body, /login\.success/);
|
||||
assert.match(response.body, />Chrome 150\.0\.0\.0 on Windows</);
|
||||
assert.doesNotMatch(response.body, /Mozilla\/5\.0 \(Windows NT 10\.0; Win64; x64\)/);
|
||||
assert.match(response.body, /content\.body\.color/);
|
||||
assert.match(response.body, />red</);
|
||||
assert.match(response.body, />blue</);
|
||||
assert.doesNotMatch(response.body, /content\.body\.keep/);
|
||||
assert.match(response.body, /permissions removed/);
|
||||
assert.match(response.body, /permissions added/);
|
||||
assert.match(response.body, /dashboard\.read/);
|
||||
assert.match(response.body, /playlists\.read/);
|
||||
assert.match(response.body, /backgroundColor added/);
|
||||
assert.match(response.body, /logoPath added/);
|
||||
assert.match(response.body, /content\.heading\.value/);
|
||||
assert.match(response.body, />New heading</);
|
||||
assert.match(response.body, /content\.added\.value added/);
|
||||
assert.match(response.body, /content\.removed\.value removed/);
|
||||
assert.match(response.body, /Generated asset: 13 bytes; SHA-256 fingerprint/);
|
||||
assert.doesNotMatch(response.body, /content\.heading\.value|Old heading|New heading/);
|
||||
assert.match(response.body, /data-audit-detail-url="\/settings\/audit-log\/1\/details"/);
|
||||
assert.match(response.body, /id="audit-change-detail-modal"/);
|
||||
assert.match(response.body, /\/assets\/js\/settings\/audit-log\.js/);
|
||||
assert.ok(response.body.indexOf('login.success') < response.body.indexOf('login.failed'));
|
||||
assert.match(response.body, />Export</);
|
||||
assert.ok(handlers['/settings/audit-log/export']);
|
||||
const pageQuery = queries.find(([sql]) => sql.includes('LIMIT ? OFFSET ?'));
|
||||
assert.ok(pageQuery);
|
||||
assert.match(pageQuery[0], /ORDER BY e\.occurred_at DESC, e\.id DESC LIMIT \? OFFSET \?/);
|
||||
assert.match(pageQuery[0], /JSON_CONTAINS_PATH\(e\.details_json, 'one', '\$\.changes'\) AS has_changes/);
|
||||
assert.deepEqual(pageQuery[1].slice(-2), [25, 0]);
|
||||
assert.ok(queries.some(([sql]) => sql.includes('SELECT COUNT(*) AS count')));
|
||||
assert.ok(queries.some(([sql]) => sql.includes('SELECT DISTINCT e.event_type') && /WHERE e\.event_type IS NOT NULL/.test(sql)));
|
||||
|
||||
let popupPayload;
|
||||
await handlers['/settings/audit-log/:id/details'][1]({ params: { id: '1' } }, {
|
||||
json(value) { popupPayload = value; }
|
||||
}, function (error) {
|
||||
throw error;
|
||||
});
|
||||
assert.deepEqual(popupPayload.changeRows.map(row => [row.path, row.from, row.to]), [
|
||||
['content.heading.value', 'Old heading', 'New heading'],
|
||||
['content.qr.qr_svg', '[Generated asset: 13 bytes; SHA-256 old]', '[Generated asset: 13 bytes; SHA-256 new]']
|
||||
]);
|
||||
|
||||
const exportResponse = {
|
||||
headers: {},
|
||||
setHeader(name, value) { this.headers[name] = value; },
|
||||
@@ -153,6 +151,12 @@ test('audit log uses its own read permission and shared pagination partial', asy
|
||||
const exportQuery = queries.filter(([sql]) => sql.includes('FROM o_audit_events') && !sql.includes('SELECT COUNT(*)') && !sql.includes('SELECT DISTINCT')).at(-1);
|
||||
assert.ok(exportQuery);
|
||||
assert.doesNotMatch(exportQuery[0], /LIMIT \?/);
|
||||
assert.match(exportQuery[0], /e\.details_json/);
|
||||
assert.doesNotMatch(exportQuery[0], /JSON_REMOVE/);
|
||||
const listQuery = queries.find(([sql]) => sql.includes('LIMIT ? OFFSET ?'));
|
||||
assert.ok(listQuery);
|
||||
assert.match(listQuery[0], /JSON_CONTAINS_PATH\(e\.details_json, 'one', '\$\.changes'\) AS has_changes/);
|
||||
assert.match(listQuery[0], /JSON_REMOVE\(e\.details_json, '\$\.changes'\) AS details_json/);
|
||||
});
|
||||
|
||||
test('audit log applies SQL filters, escaped search, and selected sort before pagination', async () => {
|
||||
|
||||
Reference in new issue
Block a user