Update category files
CI/CD Pipeline / test (push) Failing after 1m46s
CI/CD Pipeline / deploy-internal (push) Has been skipped

This commit is contained in:
chris committed 2026-08-28 20:19:11 -04:00
1 parent 86b656ee5c
commit fea861f042
2 files changed
+83 -51

No files matched your search

+35 -31
View File
@@ -1,40 +1,44 @@
import { describe, it } from 'node:test'; import { describe, it } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { CategoryRepository } from './category.repository'; import { CategoryRepository } from './category.repository.ts';
import { Pool } from 'pg';
describe('CategoryRepository - ltree SQL Generator', () => { // Lightweight mock generator for the pg Pool
const repository = new CategoryRepository(); function createMockDb(mockRows: any[]) {
const tracker = { lastQuery: { text: '', values: [] as any[] } };
const mockPool = {
query: async (text: string, values: any[]) => {
tracker.lastQuery = { text, values };
return { rows: mockRows };
}
} as unknown as Pool;
describe('generateDescendantsQuery()', () => { return { mockPool, tracker };
it('should generate valid SQL using the ltree descenant operator (<@)', () => { }
const parentPath = 'Top.Science';
const result = repository.generateDescendantsQuery(parentPath);
// veriy the generated SQL syntax as correct ltree describe('CategoryRepository - ltree Queries', () => {
assert.match(result.sql, /<@/); it('should format findAllDescendants query using the ltree descendant operator (<@)', async () => {
assert.match(result.sql, /::ltree/); const mockData = [{ id: 2, name: 'Science', path: 'Top.Science' }];
assert.deepEqual(result.values, ['Top.Science']); const { mockPool, tracker } = createMockDb(mockData);
}); const repository = new CategoryRepository(mockPool);
const result = await repository.findAllDescendants('Top.Science');
// Verify data output matches mock rows
assert.deepEqual(result, mockData);
// Verify generated SQL uses the right operator and bindings
assert.match(tracker.lastQuery.text, /<@/);
assert.deepEqual(tracker.lastQuery.values, ['Top.Science']);
}); });
describe('generateWildcardQuery()', () => { it('should format findDirectChildren query with level matching requirements', async () => {
it('should generate valid SQL utilizing an lquery matching operator (~)', () => { const { mockPool, tracker } = createMockDb([]);
const result = repository.generateWildcardQuery('Top.*.Astronomy'); const repository = new CategoryRepository(mockPool);
assert.match(result.sql, /~/); await repository.findDirectChildren('Top');
assert.match(result.sql, /::lquery/);
assert.equal(result.values[0], 'Top.*.Astronomy'); assert.match(tracker.lastQuery.text, /nlevel\(path\)/);
}); assert.deepEqual(tracker.lastQuery.values, ['Top.*', 'Top']);
}); });
});
describe('isValidLtreePath() validation utility', () => {
it('should pass for correctly formatted dot-separated ltree structures', () => {
assert.equal(repository.isValidLtreePath('Top.Hobbies.Amateurs_Astronomy'), true);
});
it('should fail if special characters or spaces are introduced into the path', () => {
assert.equal(repository.isValidLtreePath('Top.Hobbies.Bad Path!'), false);
assert.equal(repository.isValidLtreePath('Top..InvalidDoubleDot'), false);
});
});
});
+48 -20
View File
@@ -1,30 +1,58 @@
// structural interface to the test table import { Pool } from 'pg';
export interface CatetoryNode {
export interface CategoryNode {
id: number; id: number;
name: string; name: string;
path: string; // ltree field path: string;
} }
export class CategoryRepository { export class CategoryRepository {
// simulate generating a SQL query targeting child paths // Inject the database connection pool
generateDescendantsQuery(parentPath: string): { sql: string; values: string[] } { constructor(private db: Pool) { }
return {
sql: 'SELECT id, name, path FROM categories WHERE path <@ $1::ltree', /**
values: [parentPath] * Finds all descendants of a given path (including the path itself).
}; * Uses the ltree operator <@ (is-descendant-of).
*/
async findAllDescendants(parentPath: string): Promise<CategoryNode[]> {
const query = `
SELECT id, name, path
FROM categories
WHERE path <@ $1::ltree
ORDER BY path ASC;
`;
const result = await this.db.query(query, [parentPath]);
return result.rows;
} }
generateWildcardQuery(pattern: string): { sql: string, values: string[] } { /**
// Simulate genrating a SQL query using ltree wildcards * Finds only the direct children of a given path.
return { * Uses lquery pattern matching (parent.*) and matches the exact next depth level.
sql: 'SELECT id, name, path FROM categories WHERE path ~ $1::lquery', */
values: [pattern] async findDirectChildren(parentPath: string): Promise<CategoryNode[]> {
}; const query = `
SELECT id, name, path
FROM categories
WHERE path ~ $1::lquery
AND nlevel(path) = nlevel($2::ltree) + 1;
`;
const values = [`${parentPath}.*`, parentPath];
const result = await this.db.query(query, values);
return result.rows;
} }
// helper utility to validate a string as an ltree value /**
isValidLtreePath(path: string): boolean { * Finds all ancestors of a path (useful for breadcrumbs).
const ltreeRegex = /^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/; * Uses the ltree operator @> (is-ancestor-of).
return ltreeRegex.test(path); */
async findAncestors(childPath: string): Promise<CategoryNode[]> {
const query = `
SELECT id, name, path
FROM categories
WHERE path @> $1::ltree
ORDER BY nlevel(path) ASC;
`;
const result = await this.db.query(query, [childPath]);
return result.rows;
} }
} }