Files

105 lines
3.8 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const {
buildSearchFilter,
buildSortOrderClause,
fetchPagedRows,
findTopLevelOrderByIndex,
normalizePageNumber,
normalizeSortDirection,
parseJsonSafe,
readFormArray
} = require('../src/data/utils');
test('parseJsonSafe returns null for invalid JSON and preserves objects', () => {
assert.equal(parseJsonSafe(''), null);
assert.equal(parseJsonSafe('not-json'), null);
assert.deepEqual(parseJsonSafe('{"value":true}'), { value: true });
const value = { nested: ['a'] };
assert.equal(parseJsonSafe(value), value);
});
test('readFormArray always returns an array shape', () => {
assert.deepEqual(readFormArray({}, 'roles'), []);
assert.deepEqual(readFormArray({ roles: 'admin' }, 'roles'), ['admin']);
assert.deepEqual(readFormArray({ roles: ['admin', 'editor'] }, 'roles'), ['admin', 'editor']);
});
test('normalize helpers clamp pagination and sort direction', () => {
assert.equal(normalizePageNumber('0'), 1);
assert.equal(normalizePageNumber('3.9'), 3);
assert.equal(normalizePageNumber('abc'), 1);
assert.equal(normalizeSortDirection('DESC'), 'desc');
assert.equal(normalizeSortDirection('anything else'), 'asc');
});
test('buildSortOrderClause applies multiple columns and ignores unknown keys', () => {
assert.deepEqual(buildSortOrderClause({ name: 'title' }, 'name', 'desc'), {
clause: ' ORDER BY title DESC',
sortKey: 'name',
sortDirection: 'desc'
});
assert.deepEqual(buildSortOrderClause({ name: ['title', 'id'] }, 'name', 'asc'), {
clause: ' ORDER BY title ASC, id ASC',
sortKey: 'name',
sortDirection: 'asc'
});
assert.deepEqual(buildSortOrderClause({ name: 'title' }, 'missing', 'desc'), {
clause: '',
sortKey: 'missing',
sortDirection: 'desc'
});
});
test('buildSearchFilter generates escaped LIKE clauses', () => {
assert.deepEqual(buildSearchFilter(['name', 'description'], '100%_ready'), {
clause: " WHERE (LOWER(COALESCE(CAST(name AS CHAR), '')) LIKE ? ESCAPE '\\\\' OR LOWER(COALESCE(CAST(description AS CHAR), '')) LIKE ? ESCAPE '\\\\')",
params: ['%100\\%\\_ready%', '%100\\%\\_ready%']
});
assert.deepEqual(buildSearchFilter([], 'anything'), { clause: '', params: [] });
});
test('findTopLevelOrderByIndex skips nested subqueries', () => {
const sql = 'SELECT * FROM (SELECT * FROM items ORDER BY created_at DESC) AS nested ORDER BY id ASC';
assert.equal(findTopLevelOrderByIndex(sql), sql.lastIndexOf('ORDER BY id ASC'));
});
test('fetchPagedRows combines search, ordering, and pagination parameters', async () => {
const queries = [];
const pool = {
async query(sql, params) {
queries.push({ sql, params });
if (sql.startsWith('SELECT COUNT(*)')) {
return [[{ count: 3 }]];
}
return [[{ id: 1 }]];
}
};
const result = await fetchPagedRows(pool, {
selectSql: 'SELECT id, name FROM items WHERE active = 1 ORDER BY name ASC',
countSql: 'SELECT COUNT(*) AS count FROM items WHERE active = 1',
params: ['active'],
searchColumns: ['name'],
searchTerm: 'alpha',
sortColumns: { name: 'name' },
sortKey: 'name',
sortDirection: 'desc',
pageSize: 2,
page: 2
});
assert.deepEqual(result, {
rows: [{ id: 1 }],
totalItems: 3,
totalPages: 2,
currentPage: 2,
pageSize: 2
});
assert.equal(queries.length, 2);
assert.equal(queries[0].sql, "SELECT COUNT(*) AS count FROM (SELECT id, name FROM items WHERE active = 1 AND (LOWER(COALESCE(CAST(name AS CHAR), '')) LIKE ? ESCAPE '\\\\')) AS filtered_rows");
assert.deepEqual(queries[0].params, ['active', '%alpha%']);
assert.equal(queries[1].sql, "SELECT id, name FROM items WHERE active = 1 AND (LOWER(COALESCE(CAST(name AS CHAR), '')) LIKE ? ESCAPE '\\\\') ORDER BY name DESC LIMIT ? OFFSET ?");
assert.deepEqual(queries[1].params, ['active', '%alpha%', 2, 2]);
});