@accounter/server
Version:
Accounter GraphQL server
154 lines (130 loc) • 4.85 kB
text/typescript
/**
* Demo Data Validation Script
*
* Validates the integrity of seeded demo data with comprehensive ledger validation.
*
* Validation Checks:
* - Admin business entity exists
* - Charge count matches expected
* - Ledger records for each use-case:
* - Per-record internal balance (FR1)
* - Aggregate balance (FR2)
* - Entity-level balance (FR3)
* - No orphaned amounts (FR4)
* - Positive amounts only (FR5)
* - Foreign currency consistency (FR6)
* - Valid dates (FR7)
* - Record count matches (FR8)
*
* Exit codes:
* - 0: All validations passed
* - 1: Validation errors found or database connection failed
*/
import { config } from 'dotenv';
import pg from 'pg';
import { getAllUseCases } from './use-cases/index.js';
import { validateLedgerRecords } from './validators/ledger-validators.js';
import type { LedgerRecord, ValidationContext } from './validators/types.js';
config({
path: [`.env`, `../../.env`],
});
const DEFAULT_CURRENCY = 'ILS';
const BALANCE_TOLERANCE = 0.005;
/**
* Main validation function - connects to DB and runs all checks
*/
async function validateDemoData() {
const client = new pg.Client({
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
host: process.env.POSTGRES_HOST,
port: parseInt(process.env.POSTGRES_PORT || '5432'),
database: process.env.POSTGRES_DB,
ssl: process.env.POSTGRES_SSL === '1',
});
const errors: string[] = [];
try {
await client.connect();
// 1. Admin business exists
const adminCheck = await client.query(
`SELECT id FROM accounter_schema.financial_entities WHERE type = 'business' AND name = 'Admin Business'`,
);
if (adminCheck.rows.length === 0) {
errors.push('Admin business entity missing');
}
// 2. Use-case charge count reconciliation
const useCases = getAllUseCases();
const expectedChargeCount = useCases.reduce((sum, uc) => sum + uc.fixtures.charges.length, 0);
const actualChargeCount = await client.query(`SELECT COUNT(*) FROM accounter_schema.charges`);
if (parseInt(actualChargeCount.rows[0].count) !== expectedChargeCount) {
errors.push(
`Charge count mismatch: expected ${expectedChargeCount}, got ${actualChargeCount.rows[0].count}`,
);
}
// 3. Comprehensive ledger validation for all use-cases with expectations (FR9)
const useCasesWithExpectations = useCases.filter(uc => uc.expectations);
console.log(
`\nValidating ledger records for ${useCasesWithExpectations.length} use-case(s)...`,
);
let ledgerRecordsExist = false;
for (const useCase of useCasesWithExpectations) {
// Get all charges for this use-case
const chargeIds = useCase.fixtures.charges.map(c => c.id);
// Fetch all ledger records for these charges
const ledgerRecords = await client.query<LedgerRecord>(
`SELECT * FROM accounter_schema.ledger_records WHERE charge_id = ANY($1) ORDER BY created_at`,
[chargeIds],
);
if (ledgerRecords.rows.length === 0) {
// Ledger generation is separate from seeding - this is expected
console.log(` ⚠ ${useCase.id}: no ledger records (ledger generation not run)`);
continue;
}
ledgerRecordsExist = true;
// Create validation context
const context: ValidationContext = {
useCaseId: useCase.id,
defaultCurrency: DEFAULT_CURRENCY,
tolerance: BALANCE_TOLERANCE,
};
// Run comprehensive validation
const validationErrors = validateLedgerRecords(
ledgerRecords.rows,
useCase.expectations!.ledgerRecordCount,
context,
);
errors.push(...validationErrors);
// Log progress
if (validationErrors.length === 0) {
console.log(` ✓ ${useCase.id} (${ledgerRecords.rows.length} records)`);
} else {
console.log(` ✗ ${useCase.id} (${validationErrors.length} error(s))`);
}
}
if (!ledgerRecordsExist) {
console.log(
' ℹ️ Ledger records not found. Run ledger generation to create and validate ledger entries.',
);
}
// 4. VAT row present (percentage stored as decimal 0.17 for 17%)
const vatCheck = await client.query(
`SELECT 1 FROM accounter_schema.vat_value WHERE percentage = 0.17`,
);
if (vatCheck.rows.length === 0) {
errors.push('VAT default (17%) missing');
}
// Report errors or success
if (errors.length > 0) {
console.error('❌ Validation failed:');
for (const err of errors) console.error(` - ${err}`);
process.exit(1);
}
console.log('✅ Demo data validation passed');
} catch (error) {
console.error('❌ Validation error:', error);
process.exit(1);
} finally {
await client.end();
}
}
validateDemoData();