# Calculation logic unit-tested with edge cases

- **Pattern:** `ab-001456` (`finserv-form-validation.error-edge-cases.calculation-testing`)
- **Severity:** info
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001456
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001456)

## Why it matters

Financial calculation functions — rounding, interest accrual, fee application — are high-stakes and frequently modified. Without unit tests that cover zero amounts, min/max boundary values, and rounding-boundary inputs like $10.005, a refactor that silently changes behavior at those boundaries will ship undetected. ISO 25010 maintainability requires that critical business logic be verifiable without running the full application. Missing edge-case coverage is the most common root cause of financial bugs found only in production, often after incorrect charges have been issued.

## Severity rationale

Info because absent tests don't cause immediate failures but increase the probability of undetected regressions in financial calculation logic during future changes.

## Remediation

Create `src/lib/finance.test.ts` (or equivalent) and include at minimum three edge-case categories: zero inputs, boundary amounts, and rounding midpoints.

```ts
// src/lib/finance.test.ts
import { calculateInterest, roundInterestCents } from './finance';

describe('calculateInterest', () => {
  it('returns 0 for zero principal', () =>
    expect(calculateInterest(0, 0.05)).toBe(0));

  it('handles maximum allowed amount', () =>
    expect(calculateInterest(99999, 0.05)).toBeGreaterThan(0));

  it('applies banker rounding at 0.5 cent boundary', () =>
    expect(roundInterestCents(100.5)).toBe(100)); // rounds to even

  it('produces integer output', () =>
    expect(Number.isInteger(roundInterestCents(10.335))).toBe(true));
});
```

Run tests in CI via `npm test` and gate merges on the test step passing.

## Detection

- **ID:** `calculation-testing`
- **Severity:** `info`
- **What to look for:** Count all financial calculation functions (interest, fees, tax, rounding, conversions). For each, check whether a corresponding test file exists in `__tests__/`, `test/`, or alongside as `.test.ts`/`.spec.ts`. Count the test cases and classify each as happy-path or edge-case (zero amounts, min/max limits, rounding boundaries like $0.005, negative inputs). Report: "X of Y financial functions have tests; Z edge-case scenarios covered."
- **Pass criteria:** At least 100% of financial calculation functions have unit tests. Tests include at least 3 edge-case categories: zero amounts, min/max boundary values, and rounding boundaries (e.g., $10.005). No more than 0 financial functions lack test coverage entirely.
- **Fail criteria:** Any financial calculation function has no test file, or tests cover only happy-path scenarios with fewer than 3 edge-case categories.
- **Skip (N/A) when:** The project has no financial calculations (0 financial calculation functions found), or calculations are trivial (single multiplication with no rounding).
- **Detail on fail:** Example: `"No tests found for interest calculations. Only happy-path scenarios tested, no edge cases like $0.005 rounding."`
- **Cross-reference:** The no-float-accumulation check verifies integer arithmetic at runtime; this check verifies those patterns are tested to prevent regressions.
- **Remediation:** Write comprehensive tests in `src/lib/finance.test.ts`:

  **In `src/lib/finance.test.ts`:**
  ```ts
  import { calculateInterest, bankersRound } from './finance';

  describe('calculateInterest', () => {
    it('should handle zero principal', () => {
      expect(calculateInterest(0, 0.05)).toBe(0);
    });

    it('should round correctly at 0.005', () => {
      expect(bankersRound(10000.5)).toBe(10001);
    });

    it('should handle maximum amounts', () => {
      const max = 999999; // $9,999.99
      expect(calculateInterest(max, 0.05)).toBeLessThanOrEqual(max);
    });

    it('should not accumulate floating-point errors', () => {
      let balance = 100000;
      for (let i = 0; i < 100; i++) {
        balance = Math.round(balance * 1.001);
      }
      expect(balance % 1).toBe(0);
    });
  });
  ```

---

## External references

- iso-25010 maintainability

Taxons: code-quality

HTML version: https://auditbuffet.com/patterns/ab-001456
