Skip to content
Draft
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
85 changes: 85 additions & 0 deletions .github/scripts/run-api-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
#
# Runs the DevCard Postman collection against a freshly-booted backend.
#
# Invoked by .github/workflows/api-tests.yml. Keeping the orchestration here
# (rather than inline in the YAML) matches the repo convention of housing
# workflow logic under .github/scripts/.
#
# Expects to be run from the repository root with these env vars set:
# DATABASE_URL, REDIS_URL, JWT_SECRET, ENCRYPTION_KEY, NODE_ENV, PORT
#
# Steps: apply migrations + seed -> start server -> wait for /health ->
# run Newman -> always stop the server on exit.

set -euo pipefail

PORT="${PORT:-3000}"
BASE_URL="http://localhost:${PORT}"
HEALTH_RETRIES="${HEALTH_RETRIES:-30}"
HEALTH_INTERVAL="${HEALTH_INTERVAL:-2}"
COLLECTION="postman/DevCard.postman_collection.json"

SERVER_PID=""

# Always stop the server, however this script exits.
cleanup() {
if [[ -n "${SERVER_PID}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then
echo "Stopping API server (pid ${SERVER_PID})"
kill "${SERVER_PID}" 2>/dev/null || true
fi
}
trap cleanup EXIT

# Prisma and tsx resolve paths relative to the working directory, so run
# everything from the backend package rather than the repo root.
cd apps/backend

# CI runs against an ephemeral database with no migration history, and the
# committed migrations can lag schema.prisma. `db push` syncs the database
# straight from the schema — the right tool for a throwaway test database.
echo "::group::Sync database schema and seed"
npx prisma db push --skip-generate
npm run db:seed
echo "::endgroup::"

# src/env.ts loads a repo-root .env via dotenv and throws if the file is
# absent. CI supplies config through real env vars, so materialise a .env
# from them (dotenv does not override values already set in the environment).
echo "::group::Write .env for dotenv"
cat > ../../.env <<EOF
DATABASE_URL=${DATABASE_URL:-}
REDIS_URL=${REDIS_URL:-}
JWT_SECRET=${JWT_SECRET:-}
ENCRYPTION_KEY=${ENCRYPTION_KEY:-}
NODE_ENV=${NODE_ENV:-development}
PORT=${PORT:-3000}
PUBLIC_APP_URL=${PUBLIC_APP_URL:-}
EOF
echo "::endgroup::"

echo "::group::Start API server"
npx tsx src/server.ts &
SERVER_PID=$!
echo "Server started (pid ${SERVER_PID})"
echo "::endgroup::"

echo "::group::Wait for server health"
for ((i = 1; i <= HEALTH_RETRIES; i++)); do
if curl -sf "${BASE_URL}/health" > /dev/null; then
echo "Server is up after ${i} attempt(s)"
break
fi
if [[ "${i}" -eq "${HEALTH_RETRIES}" ]]; then
echo "Server did not become healthy within $((HEALTH_RETRIES * HEALTH_INTERVAL))s"
exit 1
fi
sleep "${HEALTH_INTERVAL}"
done
echo "::endgroup::"

echo "::group::Run Newman"
npx --yes newman run "${COLLECTION}" \
--env-var "baseUrl=${BASE_URL}" \
--reporters cli
echo "::endgroup::"
63 changes: 63 additions & 0 deletions .github/workflows/api-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: API Tests (Newman)

# Runs the DevCard Postman collection against a freshly-booted backend.
on:
pull_request:
paths:
- 'apps/backend/**'
- 'packages/shared/**'
- '.github/workflows/api-tests.yml'
workflow_dispatch:

jobs:
newman:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: devcard
POSTGRES_PASSWORD: devcard
POSTGRES_DB: devcard
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U devcard"
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
DATABASE_URL: postgresql://devcard:devcard@localhost:5432/devcard
REDIS_URL: redis://localhost:6379
JWT_SECRET: ci-test-secret-not-for-production-ci-test-secret-not-for-production
ENCRYPTION_KEY: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
NODE_ENV: development
PORT: '3000'
PUBLIC_APP_URL: http://localhost:5173

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install shared dependencies
run: npm --prefix packages/shared install

- name: Install backend dependencies
run: npm --prefix apps/backend install

- name: Run API tests
run: bash ./.github/scripts/run-api-tests.sh
7 changes: 7 additions & 0 deletions apps/backend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export default tseslint.config(
'coverage/**',
'prisma/migrations/**',
'**/*.d.ts',
'postman/**',
// Source is TypeScript only; JS-family files are config/scripts that the
// type-checked ruleset cannot parse (no tsconfig project). Skip them so
// CI linting of changed files does not crash on e.g. push.mjs or this config.
'**/*.mjs',
'**/*.cjs',
'**/*.js',
],
},

Expand Down
Loading
Loading