Testing the apps

Every app under apps/ ships with a test suite that runs via pnpm test and is enforced by the pre-push hook. This runbook explains the layers so you know where to add tests, and how to debug failures.

What you get for free

A single pnpm -r test from the repo root runs every workspace’s tests in parallel:

WorkspaceStackRunnerWhat’s tested
apps/apiNestJS / Fastifyjest (test:unit) + jest e2e (test:e2e)HealthController unit + real HTTP integration via supertest
apps/ai-workerPython / FastAPIpytest with TestClient/healthz shape, 501 stubs for tier1/tier2, 404 handling
apps/edge-appPython / FastAPIpytest with TestClient/healthz shape, 404 handling
apps/edge-supervisorGo / net/httpgo testhandleHealth + envDefault + loggingMiddleware
apps/dashboardReact + Vitevitest + Testing LibraryApp renders, brand + tagline, footer with version
apps/fd-viewReact + Vitevitest + Testing LibrarySame shape as dashboard
apps/edge-dashboardReact + Vitevitest + Testing LibrarySame shape as dashboard
packages/contractsTS + Python + Govitest + pytest + go testRound-trip + re-serialization stability per language

The pre-push hook runs pnpm -r test, so a broken test blocks every push. To bypass (rare, last resort), git push --no-verify.

Three layers of testing

1. Unit / component tests (fast — in the default pnpm test)

What runs on every push.

  • TypeScript apps use jest (apps/api) or vitest (apps/dashboard, fd-view, edge-dashboard).
  • Python apps use pytest with fastapi.testclient.TestClient — in-process HTTP, no real socket.
  • Go apps use the standard testing package with httptest.NewRecorder.

Where they live:

apps/api/src/health/health.controller.spec.ts   ← unit (NestJS controller-only)
apps/api/test/health.e2e-spec.ts                ← in-process HTTP via supertest
apps/ai-worker/tests/test_main.py               ← pytest + TestClient
apps/edge-supervisor/cmd/supervisor/main_test.go ← go test
apps/dashboard/src/App.test.tsx                  ← vitest + RTL

2. Docker container tests (slow — opt-in via test:docker)

Per app, a test:docker script wraps a shared runner at scripts/test-app-docker.mjs. The runner:

  1. docker build -f apps/<app>/Dockerfile -t <app>:test-<timestamp> .
  2. docker run -d on a random localhost port
  3. Wait up to 60s for /healthz to respond 200
  4. Validate the response JSON has ok: true and the expected service field
  5. Always tear down (docker rm -f) — even on failure

Run for one app:

pnpm -F @wiqaia/api test:docker

Run for all apps (~5–10 minutes total, depending on Docker cache state):

pnpm -r test:docker

Not in the default pnpm test because each one takes ~30–60s and the pre-push hook would become unbearable. These run in CI when Cloud Build is set up (see Gitea issue “Set up Cloud Build CI infrastructure”).

If a Docker test fails, the runner dumps the container’s logs before tearing down — that’s usually enough to see what went wrong.

3. Cross-language wire-contract tests (in packages/contracts)

These prove that a message encoded in one language decodes identically in the other two. See Working with contracts for details.

Adding a new test

TypeScript / NestJS (apps/api)

Unit test (controller-only, fast):

// apps/api/src/foo/foo.spec.ts
import { Test } from '@nestjs/testing';
import { FooController } from './foo.controller';

describe('FooController', () => {
  it('does the thing', async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [FooController],
    }).compile();
    const controller = moduleRef.get(FooController);
    expect(controller.bar()).toBe('baz');
  });
});

HTTP integration (real Fastify, slower):

// apps/api/test/foo.e2e-spec.ts
import { Test } from '@nestjs/testing';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import request from 'supertest';
import { AppModule } from '../src/app.module';
// boot via Test.createTestingModule, then await app.init().
// Use request(app.getHttpServer()) for assertions.

Vitest / React SPAs

// apps/dashboard/src/Foo.test.tsx
import { describe, expect, it } from 'vitest';
import { render, screen } from '@testing-library/react';
import Foo from './Foo';

describe('Foo', () => {
  it('renders', () => {
    render(<Foo />);
    expect(screen.getByText('expected text')).toBeInTheDocument();
  });
});

Python (apps/ai-worker, apps/edge-app)

# apps/<app>/tests/test_foo.py
from fastapi.testclient import TestClient
from wiqaia_<app>.main import app

client = TestClient(app)

def test_foo_endpoint():
    res = client.get('/foo')
    assert res.status_code == 200
    assert res.json()['ok'] is True

Go (apps/edge-supervisor)

// apps/edge-supervisor/cmd/supervisor/foo_test.go
func TestFoo(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/foo", nil)
    rec := httptest.NewRecorder()
    handleFoo(rec, req)
    if rec.Code != http.StatusOK { t.Fatal(rec.Code) }
}

Common debugging

Vitest can’t find __APP_VERSION__. Define it in vitest.config.ts:

define: { __APP_VERSION__: JSON.stringify('test-build') }

pytest can’t import the app’s package. Ensure the test file pre-pends the app’s src/ to sys.path:

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'src'))

Go tests reference a helper that doesn’t compile. Test-only helpers should live in a _test.go file so they’re not in the production binary.

Docker integration test fails with container not ready after 60s. The runner dumps logs on failure. Common causes: wrong --container-port flag, the app binds to 127.0.0.1 inside the container instead of 0.0.0.0, or the /healthz route isn’t actually wired.

WiqAIa+ Docs · Rizoma