Skip to content

Testing

VETA has nine test suites. Each addresses a distinct failure class: unit suites cover logic primitives, integration covers service boundaries, E2E covers UI composition, smoke covers deployment plumbing, the synthetic probe covers public reachability, and performance covers throughput regressions. The taxonomy below is organised by failure class rather than by tooling.

| Suite | What it catches | Where it runs | Speed | | --- | --- | --- | --- | | Backend unit | Pure-logic bugs in algos, OMS, FIX, schemas | Pre-commit, CI | <30s | | Frontend unit | Component + Redux + hook regressions | Pre-commit, CI | ~20s | | Integration (Testcontainers) | Service-boundary contract breaks | CI | ~5min | | Playwright E2E | UI flows that span the whole stack | CI | ~3min | | Electron E2E | Desktop-app-specific behaviour (IPC, contextBridge) | CI | ~1min | | Smoke | Service reachability through production-equivalent ingress | CI testcontainers gate | ~90s | | Visual anomalies | DOM overflows + a11y violations (non-gating) | CI (informational) | ~1min | | Synthetic probe | Live deployment is reachable from the internet | Every 60s from edge server | continuous | | Performance | Throughput regressions, latency p99 drift | On demand | varies | | k6 load testing | Sustained throughput under realistic order flow scenarios | On demand | varies | | Continuous load generator | Always-on synthetic trade flow on the production server | Production only | continuous |

The pre-commit hook (.husky/pre-commit) runs lint + type-check + unit tests + integration in seven steps. Integration auto-skips when services aren't running locally so the hook stays fast for tight edit loops. Smoke runs in CI under the testcontainers gate rather than at pre-commit.

graph TD
UNIT["Unit<br/>(Deno, Vitest)<br/>pure functions, no I/O"]:::fast
INT["Integration<br/>(.tc.test.ts)<br/>real Postgres and Redpanda<br/>per test, via Testcontainers"]:::mid
SMOKE["Smoke<br/>(.tc.test.ts)<br/>HTTP health and pipeline checks<br/>against an ephemeral stack"]:::mid
PW["Playwright E2E<br/>real browser, real frontend,<br/>mocked or real backend"]:::mid
EL["Electron E2E<br/>Playwright launched against<br/>the packaged Electron binary"]:::mid
PROBE["Synthetic probe<br/>internet → edge server → server Traefik<br/>the path real users take"]:::slow
UNIT --> INT --> SMOKE --> PW --> EL --> PROBE
classDef fast fill:#dcfce7,stroke:#16a34a,color:#000
classDef mid fill:#fef3c7,stroke:#d97706,color:#000
classDef slow fill:#fecaca,stroke:#dc2626,color:#000

Lower entries depend on more of the system being real. The position of a failing suite localises the fault: a probe failure with green unit tests indicates a deployment or ingress regression; a unit failure with a green probe indicates a regression not yet shipped.

Where each suite runs across the dev lifecycle

Section titled “Where each suite runs across the dev lifecycle”
graph LR
subgraph LOCAL["Local pre-commit hook"]
L1["lint + type-check"]
L2["unit (backend + frontend)"]
L3["(smoke runs in CI only)"]
L4["integration<br/>(auto-skip if stack offline)"]
end
subgraph PR["Pull request CI"]
P1["lint + type-check"]
P2["unit"]
P3["integration<br/>(Testcontainers)"]
P4["Playwright E2E"]
P5["Electron E2E"]
P6["visual-anomalies<br/>(non-gating)"]
P7["screenshots"]
end
subgraph MAIN["On merge to main"]
M1["all PR checks"]
M2["coverage badges<br/>auto-committed"]
M3["container images<br/>published to GHCR"]
end
subgraph DEPLOY["Homelab deploy (post-push)"]
D1["systemd auto-pull<br/>(every 5 min)"]
D2["docker compose up -d"]
D3["docker compose healthchecks<br/>(gates rollout)"]
D4["frontend deploy-gate smoke<br/>(post-deploy probe)"]
end
subgraph PROD["Live production"]
R1["synthetic probe<br/>(every 60s from edge server)"]
end
LOCAL --> PR --> MAIN --> DEPLOY --> PROD
classDef local fill:#dcfce7,stroke:#16a34a,color:#000
classDef ci fill:#fef3c7,stroke:#d97706,color:#000
classDef deploy fill:#fecaca,stroke:#dc2626,color:#000
classDef prod fill:#e0e7ff,stroke:#4f46e5,color:#000
class L1,L2,L3,L4 local
class P1,P2,P3,P4,P5,P6,P7,M1,M2,M3 ci
class D1,D2,D3,D4 deploy
class R1 prod

Each suite has a defined trigger. The pre-commit hook is the first line; integration auto-skips locally to keep the hook fast, but runs in CI without the auto-skip. Smoke runs in CI behind the testcontainers gate rather than at pre-commit. The post-deploy probe is the final gate before users see new code: a probe failure pages the operator.

Coverage is generated on every push to main and surfaced as badges in the README:

  • Frontend: V8 provider via Vitest. Reports text-summary, lcov, and json-summary. Thresholds: 80% statements, branches, functions, lines, enforced by vitest.config.ts.
  • Backend: Deno's native --coverage flag. Exported as lcov via deno coverage --lcov > coverage.lcov.
  • Badge: docs/badges/coverage.json is auto-committed by the CI job that runs coverage.

To run coverage locally:

Terminal window
# backend
deno task test:coverage
# frontend
cd frontend && npm run test:coverage
graph LR
LT["lint-and-test"] --> INT["integration<br/>(Testcontainers)"]
LT --> DB["docker-base"] --> DS["docker-services"]
FE["frontend"] --> PW["playwright-ui"]
FE --> SS["screenshots"]
FE --> VA["visual-anomalies<br/>(non-gating)"]
FE --> EL["electron"]

Playwright, screenshots, Electron, and visual-anomalies run in parallel with the integration job rather than after it. The synthetic probe and load tests are not part of the merge pipeline; they run continuously and on demand respectively.

See CI / CD for the full GitHub Actions workflow.