Skip to content
Merged

Dev #12

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion api/__tests__/accounts-coa-integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ABOUTME: Verifies operational account creation stays simple while enforcing valid chart-account behavior.
// ABOUTME: Protects location-scoped payment method links from crossing business and branch boundaries.
import { afterEach, describe, expect, it } from "vitest";
import { and, eq, inArray } from "drizzle-orm";
import { and, eq, inArray, isNull } from "drizzle-orm";

import { appRouter } from "../router";
import {
Expand Down Expand Up @@ -162,8 +162,25 @@ describe("operational accounts and payment-method linking", () => {
locationId: ctx.location.id,
businessId: ctx.business.id,
type: "cash",
});

expect(account.accountType).toBeNull();
expect(account.accountSubType).toBeNull();

const sysAccounts = await db
.select()
.from(accounts)
.where(and(
eq(accounts.businessId, ctx.business.id),
eq(accounts.systemKey, "asset:cash"),
isNull(accounts.deletedAt),
)).limit(1);

expect(sysAccounts.length).toBeGreaterThan(0);
expect(sysAccounts[0]).toMatchObject({
accountType: "asset",
accountSubType: "cash",
isSystemGenerated: true,
});
});

Expand Down
42 changes: 19 additions & 23 deletions api/__tests__/business-reset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,6 @@ async function seedResetContext(seed: string): Promise<SeededContext> {
nextExpenseNumber: 27,
} as any).returning());

// Create expense category
const category = await firstRow<typeof expenseCategories.$inferSelect>(db.insert(expenseCategories).values({
businessId: business.id,
locationId: location.id,
name: `Test Category ${seed}`,
color: "#C73E1D",
defaultAccountId: 0,
isActive: true,
} as any).returning());

// Create operational (user) account
const opAccount = await firstRow<typeof accounts.$inferSelect>(db.insert(accounts).values({
businessId: business.id,
Expand All @@ -141,10 +131,15 @@ async function seedResetContext(seed: string): Promise<SeededContext> {
isActive: true,
} as any).returning());

// Fix category defaultAccountId
await db.update(expenseCategories)
.set({ defaultAccountId: sysAccount.id })
.where(eq(expenseCategories.id, category.id));
// Create expense category (accounts must exist first due to FK constraint)
const category = await firstRow<typeof expenseCategories.$inferSelect>(db.insert(expenseCategories).values({
businessId: business.id,
locationId: location.id,
name: `Test Category ${seed}`,
color: "#C73E1D",
defaultAccountId: sysAccount.id,
isActive: true,
} as any).returning());

// Create supplier
const supplier = await firstRow<typeof suppliers.$inferSelect>(db.insert(suppliers).values({
Expand Down Expand Up @@ -255,10 +250,10 @@ async function seedResetContext(seed: string): Promise<SeededContext> {
totalPrice: "1000.00",
} as any);

// Create M-PESA transaction
// Create M-PESA transaction (txnId limited to varchar(20))
await db.insert(mpesaTransactions).values({
locationId: location.id,
txnId: `MPESA-${seed}`,
txnId: `MPESA-${seed}`.slice(0, 20),
txnDate: "2026-05-16",
txnType: "topup",
amount: "500.00",
Expand Down Expand Up @@ -414,8 +409,9 @@ async function cleanupResetContext(accountId: string) {
await db.delete(journalEntries).where(eq(journalEntries.businessId, business.id));
await db.delete(expenses).where(eq(expenses.businessId, business.id));
await db.delete(bills).where(eq(bills.businessId, business.id));
await db.delete(accounts).where(eq(accounts.businessId, business.id));
// Delete expense_categories FIRST (FK references accounts.id via defaultAccountId)
await db.delete(expenseCategories).where(eq(expenseCategories.businessId, business.id));
await db.delete(accounts).where(eq(accounts.businessId, business.id));
await db.delete(suppliers).where(eq(suppliers.businessId, business.id));
await db.delete(employees).where(sql`${employees.id} > 0`);
await db.delete(locations).where(eq(locations.businessId, business.id));
Expand Down Expand Up @@ -455,7 +451,7 @@ describe("resetBusinessTransactions", () => {

expect(result.success).toBe(true);
expect(result.preserved).toContain("audit_log");
expect(result.preserved).toContain("accounts (system)");
expect(result.preserved).toContain("accounts (all)");
expect(result.resetAt).toBeTruthy();

// Verify system account preserved and balance reset
Expand Down Expand Up @@ -641,11 +637,11 @@ describe("resetBusinessTransactions", () => {

expect(snapshot.businessId).toBe(ctx.business.id);
expect(snapshot.timestamp).toBeTruthy();
expect(snapshot.tableCounts.dailySales).toBeGreaterThan(0);
expect(snapshot.tableCounts.expenses).toBeGreaterThan(0);
expect(snapshot.tableCounts.bills).toBeGreaterThan(0);
expect(snapshot.tableCounts.mpesaTransactions).toBeGreaterThan(0);
expect(snapshot.tableCounts.journalEntries).toBeGreaterThan(0);
expect(Number(snapshot.tableCounts.dailySales)).toBeGreaterThan(0);
expect(Number(snapshot.tableCounts.expenses)).toBeGreaterThan(0);
expect(Number(snapshot.tableCounts.bills)).toBeGreaterThan(0);
expect(Number(snapshot.tableCounts.mpesaTransactions)).toBeGreaterThan(0);
expect(Number(snapshot.tableCounts.journalEntries)).toBeGreaterThan(0);
});

// ── Test 5: Return results structure ─────────────────────────────────────
Expand Down
15 changes: 15 additions & 0 deletions api/test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"../../db/migrations/0000_flawless_jack_murdock.sql",
);
if (!(await tableExists(testPool, "users"))) {
let sql = fs.readFileSync(baseSchemaPath, "utf8");

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/business-reset.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/business-logo-router-contract.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/business-documents-utils.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/business-documents-router-contract.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/auth.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/accounts.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/accounts-coa-integration.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/account-subscription-migration.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/account-subscription-enforcement.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }

Check failure on line 71 in api/test/setup.ts

View workflow job for this annotation

GitHub Actions / test

api/__tests__/account-subscription-context.test.ts

Error: ENOENT: no such file or directory, open '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' ❯ ensureTestDatabase api/test/setup.ts:71:20 ❯ api/test/setup.ts:129:3 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/home/runner/work/finaflow/finaflow/db/migrations/0000_flawless_jack_murdock.sql' }
// Strip the DOWN migration section (everything from the "Drops all tables" comment onward)
const downMarker = "-- Drops all tables and enums created by this migration";
const downIdx = sql.indexOf(downMarker);
Expand Down Expand Up @@ -101,6 +101,21 @@
// continue with the next statement for idempotent setup.
}
}

const migration2Path = path.resolve(
import.meta.dirname,
"../../db/migrations/0002_soft_flamingo.sql",
);
let migration2Sql = fs.readFileSync(migration2Path, "utf8").replaceAll("--> statement-breakpoint", "");
const migration2Statements = migration2Sql.split(";").filter((s) => s.trim());
for (const stmt of migration2Statements) {
try {
await testPool.query(stmt);
} catch {
// Individual DDL statements may already exist;
// continue with the next statement for idempotent setup.
}
}
} finally {
await testPool.end();
}
Expand Down
Binary file removed resources/1-Dashboard-uai-258x470.png
Binary file not shown.
Loading
Loading