Skip to main content

Test Case Template

Test Case Template

Project: Bilko Version: 0.1 Date: 2026-02-23 Author: Ops Architect Status: Draft Reviewers: Tech Lead, Alem Bašić

Document History

Version Date Author Changes
0.1 2026-02-23 Ops Architect Initial draft

How to Use This Template

Copy the relevant section below for each test case. Financial and accounting test cases require explicit precision validation (NUMERIC(19,4)) and country-specific VAT rates.


Unit Test Case Template

// File: apps/api/src/utils/__tests__/<module>.test.ts

import { describe, it, expect } from 'vitest';
import { <functionName> } from '../<module>';

/**
 * TC-ID: UNIT-XXX
 * Module: <module name>
 * Priority: P0 / P1 / P2
 * Author: <developer name>
 * Date: YYYY-MM-DD
 */
describe('<functionName>', () => {
  // STANDARD CASE
  it('<description of what is tested>', () => {
    // Arrange
    const input = <input value>;

    // Act
    const result = <functionName>(input);

    // Assert
    expect(result).toBe(<expected value>);
  });

  // EDGE CASE
  it('handles <edge case description>', () => {
    // Arrange
    const input = <edge case input>;

    // Act + Assert
    expect(() => <functionName>(input)).toThrow('<error message>');
    // OR
    expect(<functionName>(input)).toBeNull();
  });
});

Integration Test Case Template

// File: apps/api/src/routes/__tests__/<route>.test.ts

import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import { app } from '../../app';
import { prisma } from '../../lib/prisma';
import { createTestOrg, createTestUser, loginTestUser } from '../helpers';

/**
 * TC-ID: INT-XXX
 * Endpoint: <METHOD> <path>
 * Priority: P0 / P1 / P2
 * Author: <developer name>
 * Date: YYYY-MM-DD
 */
describe('<METHOD> <path>', () => {
  let authToken: string;
  let organizationId: string;

  beforeEach(async () => {
    // Fresh organization + user per test
    const org = await createTestOrg();
    organizationId = org.id;
    authToken = await loginTestUser(org.id);
  });

  it('<success scenario description>', async () => {
    // Arrange
    const payload = { /* request body */ };

    // Act
    const res = await request(app)
      .post('/api/v1/<path>')
      .set('Authorization', `Bearer ${authToken}`)
      .send(payload);

    // Assert
    expect(res.status).toBe(201);
    expect(res.body.<field>).toBe(<expected>);
  });

  it('returns 401 without auth', async () => {
    const res = await request(app).post('/api/v1/<path>').send({});
    expect(res.status).toBe(401);
  });

  it('returns 403 for cross-org access', async () => {
    // Create resource in a different org
    const otherOrg = await createTestOrg();
    const otherResource = await prisma.<model>.create({
      data: { organizationId: otherOrg.id, /* ... */ }
    });

    const res = await request(app)
      .get(`/api/v1/<path>/${otherResource.id}`)
      .set('Authorization', `Bearer ${authToken}`);

    expect(res.status).toBe(404); // 404, not 403 (no enumeration)
  });
});

Accounting-Specific Test Cases (Bilko)

Double-Entry Balance Verification

// TC-ID: ACCT-001
// Priority: P0
// Description: Every financial transaction must have balanced debit and credit entries

it('validates double-entry balance — debits must equal credits', () => {
  const entry = {
    debitAccountId: 'acc_receivables',
    creditAccountId: 'acc_revenue',
    amount: new Decimal('50000.0000'),  // NUMERIC(19,4)
    currencyCode: 'RSD',
  };

  expect(() => validateDoubleEntry(entry)).not.toThrow();

  const debitTotal = new Decimal('1000.0000');
  const creditTotal = new Decimal('999.9999');  // Unbalanced
  expect(() => validateDoubleEntry({ ...entry, creditAmount: creditTotal }))
    .toThrow('Double-entry imbalance: debit 1000.0000 ≠ credit 999.9999');
});

VAT Calculation Accuracy by Country

// TC-ID: ACCT-002
// Priority: P0
// Description: VAT must be calculated per country-specific rules with NUMERIC precision

describe('VAT calculation accuracy', () => {
  it('Serbia RS — standard rate 20% on 1000.0000 RSD', () => {
    const result = calculateVAT(new Decimal('1000.0000'), 'RS', 'standard');
    expect(result.vatAmount.toString()).toBe('200.0000');
    expect(result.total.toString()).toBe('1200.0000');
    expect(result.rate).toBe(20);
  });

  it('Bosnia BiH — standard rate 17% on 100.0000 BAM', () => {
    const result = calculateVAT(new Decimal('100.0000'), 'BA', 'standard');
    expect(result.vatAmount.toString()).toBe('17.0000');
    expect(result.total.toString()).toBe('117.0000');
    expect(result.rate).toBe(17);
  });

  it('Croatia HR — standard rate 25% on 100.0000 EUR', () => {
    const result = calculateVAT(new Decimal('100.0000'), 'HR', 'standard');
    expect(result.vatAmount.toString()).toBe('25.0000');
    expect(result.total.toString()).toBe('125.0000');
    expect(result.rate).toBe(25);
  });

  it('exports zero-rated — all countries', () => {
    for (const country of ['RS', 'BA', 'HR']) {
      const result = calculateVAT(new Decimal('5000.0000'), country, 'zero');
      expect(result.vatAmount.toString()).toBe('0.0000');
      expect(result.rate).toBe(0);
    }
  });

  it('NUMERIC precision — no floating point drift', () => {
    // 0.1 + 0.2 ≠ 0.3 in IEEE 754 — must use Decimal
    const result = calculateVAT(new Decimal('0.1000'), 'RS', 'standard');
    expect(result.vatAmount.toString()).toBe('0.0200');  // Not 0.020000000000000004
  });
});

Currency Conversion with Rate Locking

// TC-ID: ACCT-003
// Priority: P0
// Description: Exchange rate must be locked at transaction date, not current rate

it('locks exchange rate at transaction date — not current rate', async () => {
  const transactionDate = new Date('2026-01-15');
  const historicalRate = new Decimal('117.5000');  // EUR/RSD on that date

  // Seed historical rate
  await prisma.exchangeRate.create({
    data: {
      fromCurrency: 'EUR',
      toCurrency: 'RSD',
      rate: historicalRate,
      effectiveDate: transactionDate,
    },
  });

  const result = await lockExchangeRate('EUR', 'RSD', transactionDate);
  expect(result.toString()).toBe('117.5000');

  // Current rate is different — should not matter
  await prisma.exchangeRate.create({
    data: {
      fromCurrency: 'EUR',
      toCurrency: 'RSD',
      rate: new Decimal('118.2000'),
      effectiveDate: new Date(),
    },
  });

  const lockedResult = await lockExchangeRate('EUR', 'RSD', transactionDate);
  expect(lockedResult.toString()).toBe('117.5000');  // Same historical rate
});

Invoice Total Calculation with Mixed VAT Rates

// TC-ID: ACCT-004
// Priority: P0
// Description: Multi-item invoices with different VAT rates must calculate correctly

it('invoice with mixed VAT rates — NUMERIC precision throughout', () => {
  const items = [
    { description: 'Consulting', quantity: 10, unitPrice: new Decimal('5000.0000'), taxRate: 20 },
    { description: 'Books', quantity: 1, unitPrice: new Decimal('2500.0000'), taxRate: 0 },  // zero-rated
  ];

  const totals = calculateInvoiceTotals(items);

  expect(totals.subtotal.toString()).toBe('52500.0000');
  expect(totals.taxAmount.toString()).toBe('10000.0000');  // Only first item taxed
  expect(totals.totalAmount.toString()).toBe('62500.0000');
  expect(totals.items[0].taxAmount.toString()).toBe('10000.0000');
  expect(totals.items[1].taxAmount.toString()).toBe('0.0000');
});

E2E Test Case Template

// File: apps/e2e/tests/<flow>.spec.ts

import { test, expect } from '@playwright/test';

/**
 * TC-ID: E2E-XXX
 * Flow: <flow name>
 * Priority: P0 / P1
 * Author: <developer name>
 * Date: YYYY-MM-DD
 */
test.describe('<Flow Name>', () => {
  test.beforeEach(async ({ page }) => {
    // Login with test user
    await page.goto('/login');
    await page.fill('input[name="email"]', '[email protected]');
    await page.fill('input[name="password"]', 'demo123');
    await page.click('button[type="submit"]');
    await expect(page).toHaveURL('/dashboard');
  });

  test('<scenario description>', async ({ page }) => {
    // Navigate
    await page.goto('/<route>');

    // Interact
    await page.click('<selector>');
    await page.fill('<selector>', '<value>');

    // Assert
    await expect(page.locator('<selector>')).toContainText('<expected>');
    await expect(page).toHaveURL(/<url-pattern>/);
  });
});

Test Case Register

Track all test cases in TEST-INVENTORY.md. Each test case needs:

Field Description
ID UNIT-XXX, INT-XXX, ACCT-XXX, E2E-XXX
Title Brief description
Priority P0 / P1 / P2
Type Unit / Integration / E2E
File Relative path to test file
Status Not implemented / Pass / Fail / Skipped
Financial logic Yes / No (if Yes, requires NUMERIC precision assertion)


Approval

Role Name Date Signature
Author Ops Architect 2026-02-23
Reviewer Tech Lead
Approver Alem Bašić