From 45ca4de0adb5e9ed72a3e891d13ea7e920569a37 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 28 Aug 2026 21:10:27 -0400 Subject: [PATCH] Add db seed and integration, update scripts in package.json --- api/package.json | 2 +- api/src/category.integration.test.ts | 65 ++++++++++++++++++++++++++++ api/src/run-seed.ts | 19 ++++++++ api/src/test-seed.ts | 27 ++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 api/src/category.integration.test.ts create mode 100644 api/src/run-seed.ts create mode 100644 api/src/test-seed.ts diff --git a/api/package.json b/api/package.json index d8e382a..0e517c8 100644 --- a/api/package.json +++ b/api/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "test": "tsx run-tests.ts", + "test": "node --import tsx --test \"src/**/*.test.ts\"", "db:migrate": "dotenv dbmate up" }, "keywords": [], diff --git a/api/src/category.integration.test.ts b/api/src/category.integration.test.ts new file mode 100644 index 0000000..f29c76d --- /dev/null +++ b/api/src/category.integration.test.ts @@ -0,0 +1,65 @@ +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { Pool } from 'pg'; +import { CategoryRepository } from './category.repository.ts'; +import { seedCategoryTable, clearCategoryTable } from './test-seed.ts'; +import { join } from 'node:path'; +import process from 'node:process'; + +// load the local .env file +try { + process.loadEnvFile(join(process.cwd(), '.env')); +} catch (e) { + console.warn('⚠️ Warning: No local .env file found. Falling back to default system environment tokens.'); +} + +const pool = new Pool({ + connectionString: process.env.DATABASE_URL || process.env.INTERNAL_PROD_DB_URL +}); + +describe('CategoryRepository - Live Database Integration Tests', () => { + const repository = new CategoryRepository(pool); + + // Setup: Seed the database before executing integration items + before(async () => { + await seedCategoryTable(pool); + }); + + // Teardown: Clean out the database after tests wrap up + after(async () => { + await clearCategoryTable(pool); + await pool.end(); + }); + + it('should fetch all deep descendants using the <@ ltree operator', async () => { + const results = await repository.findAllDescendants('Top.Science'); + + // Should return Science, Astronomy, and Astrophysics (3 nodes) + assert.equal(results.length, 3); + assert.equal(results[0].name, 'Science'); + assert.equal(results[1].name, 'Astronomy'); + assert.equal(results[2].name, 'Astrophysics'); + }); + + it('should isolate only immediate structural child links via findDirectChildren()', async () => { + const results = await repository.findDirectChildren('Top'); + + // Top has two immediate children: Science and Hobbies + assert.equal(results.length, 2); + + const names = results.map(r => r.name); + assert.ok(names.includes('Science')); + assert.ok(names.includes('Hobbies')); + assert.ok(!names.includes('Astronomy')); // Deeper descendant, should be skipped + }); + + it('should resolve breadcrumb history using the @> ltree operator', async () => { + const results = await repository.findAncestors('Top.Science.Astronomy'); + + // Should trace: Root (Top) -> Science -> Astronomy + assert.equal(results.length, 3); + assert.equal(results[0].path, 'Top'); + assert.equal(results[1].path, 'Top.Science'); + assert.equal(results[2].path, 'Top.Science.Astronomy'); + }); +}); diff --git a/api/src/run-seed.ts b/api/src/run-seed.ts new file mode 100644 index 0000000..9254590 --- /dev/null +++ b/api/src/run-seed.ts @@ -0,0 +1,19 @@ +import { Pool } from 'pg'; +import { seedCategoryTable } from './test-seed.ts'; +import 'dotenv/config'; // Requires npm install dotenv if you want to parse local .env assets + +const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + +async function run() { + console.log('🌱 Seeding target database connection schema...'); + try { + await seedCategoryTable(pool); + console.log('✅ Database hierarchy seed operations completed successfully.'); + } catch (error) { + console.error('❌ Database seed operations failed:', error); + } finally { + await pool.end(); + } +} + +run(); diff --git a/api/src/test-seed.ts b/api/src/test-seed.ts new file mode 100644 index 0000000..e42df0b --- /dev/null +++ b/api/src/test-seed.ts @@ -0,0 +1,27 @@ +import { Pool } from 'pg'; + +export async function seedCategoryTable(pool: Pool): Promise { + // 1. Wipe old records to ensure clean execution states + await pool.query('TRUNCATE TABLE categories RESTART IDENTITY CASCADE;'); + + // 2. Insert standard ltree seed records + const seedQueries = [ + { name: 'Root', path: 'Top' }, + { name: 'Science', path: 'Top.Science' }, + { name: 'Astronomy', path: 'Top.Science.Astronomy' }, + { name: 'Astrophysics', path: 'Top.Science.Astronomy.Astrophysics' }, + { name: 'Hobbies', path: 'Top.Hobbies' }, + { name: 'Gardening', path: 'Top.Hobbies.Gardening' }, + ]; + + for (const record of seedQueries) { + await pool.query( + 'INSERT INTO categories (name, path) VALUES ($1, $2::ltree);', + [record.name, record.path] + ); + } +} + +export async function clearCategoryTable(pool: Pool): Promise { + await pool.query('TRUNCATE TABLE categories RESTART IDENTITY CASCADE;'); +}