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]); });