Skip to content
Open
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
48 changes: 48 additions & 0 deletions .github/workflows/backend-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: backend-integration

on:
push:
branches: [main, master]
paths:
- "track-service/**"
- "acars-service/**"
- "nginx/**"
- "frontend/**"
- "Dockerfile"
- "entrypoint.sh"
- "docker-healthcheck.sh"
- "tests/integration/**"
- ".github/workflows/backend-integration.yml"
pull_request:
paths:
- "track-service/**"
- "acars-service/**"
- "nginx/**"
- "frontend/**"
- "Dockerfile"
- "entrypoint.sh"
- "docker-healthcheck.sh"
- "tests/integration/**"
- ".github/workflows/backend-integration.yml"

permissions:
contents: read

concurrency:
group: backend-integration-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
compose-smoke:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

- name: Show Docker versions
run: |
docker version
docker compose version

- name: Run full-stack integration suite
run: tests/integration/run.sh
23 changes: 23 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ npm run test -- --watch # watch mode

CI runs `npm run typecheck && npm run test && npm run build` on every PR.

## Backend and deployment integration tests

`integration/` boots a deterministic public test stack with Docker Compose:

- a moving readsb-compatible `aircraft.json` fixture;
- a TCP ACARS fixture;
- TimescaleDB, track-service, and acars-service;
- the production viewer/nginx image in both live-only and full multi-feed modes.

The verifier exercises track ingestion and history, heatmap aggregation, ACARS
ingestion, schema creation, compression and retention policies, nginx routing,
feature flags, and multi-feed proxy generation. It then restarts both backend
services and repeats the suite to catch non-idempotent database startup.

Run it locally anywhere Docker Compose v2 is available:

```sh
tests/integration/run.sh
```

No receiver, radio hardware, credentials, or external service is required.
GitHub Actions runs the same command on public `ubuntu-latest` runners.

## Browser smoke test

`tests/smoke-test.html` is a legacy browser-based test page from the early
Expand Down
186 changes: 186 additions & 0 deletions tests/integration/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
name: adsb3d-integration

services:
timescaledb:
image: timescale/timescaledb:2.17.2-pg16
environment:
POSTGRES_DB: adsb_tracks
POSTGRES_USER: adsb
POSTGRES_PASSWORD: ci-only-password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U adsb -d adsb_tracks"]
interval: 2s
timeout: 3s
retries: 30

fixture-feeder:
image: python:3.11-alpine
command: ["python", "/fixtures/server.py", "http"]
volumes:
- ./fixtures:/fixtures:ro
healthcheck:
test:
- CMD
- python
- -c
- >-
import urllib.request;
urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=2)
interval: 2s
timeout: 3s
retries: 20

fixture-acars:
image: python:3.11-alpine
command: ["python", "/fixtures/server.py", "acars"]
volumes:
- ./fixtures:/fixtures:ro
healthcheck:
test:
- CMD
- python
- -c
- >-
import socket;
socket.create_connection(('127.0.0.1', 15550), timeout=2).close()
interval: 2s
timeout: 3s
retries: 20

track-service:
build:
context: ../../track-service
environment:
FEEDER_URL: http://fixture-feeder:8080
COLLECTION_INTERVAL: "1"
RETENTION_DAYS: "14"
DB_HOST: timescaledb
DB_PORT: "5432"
DB_NAME: adsb_tracks
DB_USER: adsb
DB_PASSWORD: ci-only-password
depends_on:
timescaledb:
condition: service_healthy
fixture-feeder:
condition: service_healthy
healthcheck:
test:
- CMD
- python
- -c
- >-
import urllib.request;
urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)
interval: 2s
timeout: 3s
retries: 30
start_period: 5s

acars-service:
build:
context: ../../acars-service
environment:
ACARS_HOST: fixture-acars
ACARS_PORT: "15550"
STATION_ID: CI_STATION
DB_HOST: timescaledb
DB_PORT: "5432"
DB_NAME: adsb_tracks
DB_USER: adsb
DB_PASSWORD: ci-only-password
depends_on:
track-service:
condition: service_healthy
fixture-acars:
condition: service_healthy
healthcheck:
test:
- CMD
- python
- -c
- >-
import urllib.request;
urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)
interval: 2s
timeout: 3s
retries: 30
start_period: 5s

viewer:
image: adsb3d-integration-viewer:local
build:
context: ../..
environment:
LATITUDE: "51.5000"
LONGITUDE: "-0.1200"
ALTITUDE: "100"
LOCATION_NAME: CI Local
FEEDER_URL: http://fixture-feeder:8080
TRACK_API_HOST: track-service:8000
ACARS_API_HOST: acars-service:8000
ENABLE_HISTORICAL: "true"
ENABLE_ACARS: "true"
ENABLE_VOICE: "false"
FEED1_NAME: CI Local
FEED1_LAT: "51.5000"
FEED1_LON: "-0.1200"
FEED1_ALT: "100"
FEED1_ACARS: "true"
FEED2_NAME: CI Remote
FEED2_URL: fixture-feeder:8080
FEED2_LAT: "51.6000"
FEED2_LON: "-0.2200"
FEED2_ALT: "200"
depends_on:
track-service:
condition: service_healthy
acars-service:
condition: service_healthy
healthcheck:
test: ["CMD", "/docker-healthcheck.sh"]
interval: 2s
timeout: 5s
retries: 30
start_period: 5s

viewer-live-only:
image: adsb3d-integration-viewer:local
environment:
LATITUDE: "40.0000"
LONGITUDE: "-75.0000"
ALTITUDE: "50"
LOCATION_NAME: CI Live Only
FEEDER_URL: http://fixture-feeder:8080
ENABLE_HISTORICAL: "false"
ENABLE_ACARS: "false"
ENABLE_VOICE: "false"
depends_on:
fixture-feeder:
condition: service_healthy
healthcheck:
test: ["CMD", "/docker-healthcheck.sh"]
interval: 2s
timeout: 5s
retries: 30
start_period: 5s

integration-tests:
build:
context: ../../track-service
command: ["python", "/tests/verify.py"]
environment:
DB_HOST: timescaledb
DB_PORT: "5432"
DB_NAME: adsb_tracks
DB_USER: adsb
DB_PASSWORD: ci-only-password
VIEWER_URL: http://viewer
LIVE_VIEWER_URL: http://viewer-live-only
volumes:
- .:/tests:ro
depends_on:
viewer:
condition: service_healthy
viewer-live-only:
condition: service_healthy
Loading