Ci cd/test runner (#268)

This commit is contained in:
Aaron Po
2026-08-31 14:16:33 -04:00
committed by GitHub
parent 8b429be857
commit cd0f33f603
17 changed files with 337 additions and 80 deletions

View File

@@ -11,6 +11,7 @@ The project uses Docker Compose to orchestrate multiple services:
- Database migrations runner (DbUp)
- Database seeder
- .NET API
- React Router website (SSR frontend)
- Test runners
See the [deployment diagram](diagrams-out/deployment.svg) for visual
@@ -39,6 +40,7 @@ sqlserver # SQL Server 2022 (port 1434)
database.migrations # DbUp migrations
database.seed # Seed initial data
api.core # Web API (ports 8080, 8081)
frontend # React Router website (port 3000)
mailpit # Local dev SMTP server + UI (port 8025)
seaweedfs # S3-compatible object storage (ports 8333, 8888, 9333, 23646)
```
@@ -51,6 +53,7 @@ docker compose --env-file web/.env.dev -f web/docker-compose.dev.yaml up -d
**Access**:
- Website: http://localhost:3000
- API Swagger: http://localhost:8080/swagger
- Health Check: http://localhost:8080/health
- Mailpit UI: http://localhost:8025
@@ -90,6 +93,7 @@ database.migrations # Fresh schema
database.seed # Test data
api.specs # Reqnroll BDD tests
unit.tests # All Features.*.Tests unit test projects
frontend.tests # Storybook Vitest + Playwright suites (own Playwright image)
```
**Run Tests**:
@@ -119,13 +123,16 @@ docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
- Health checks enabled
- Restart policies (unless-stopped)
- Security hardening
- Only `frontend` publishes a port to the host; `sqlserver` and `api.core`
are reachable only from other containers on `prodnet`
**Services**:
```yaml
sqlserver # Production SQL Server
sqlserver # Production SQL Server (internal only)
database.migrations # Schema updates only
api.core # Production API
api.core # Production API (internal only, no published ports)
frontend # Production website (port 3000, the only public binding)
```
**Deploy Production**:

View File

@@ -20,7 +20,9 @@ Direct environment variable access via `Environment.GetEnvironmentVariable()`.
### Frontend (`web/frontend`)
The active website reads runtime values from the server environment for its auth
and API integration.
and API integration. In Docker, the `frontend` service is wired into the same
`web/.env.dev` / `.env.test` / `.env.prod` files as the backend; running it
locally with `npm run dev` still reads from the shell environment.
### Docker
@@ -34,8 +36,9 @@ Environment-specific `.env` files loaded via `env_file:` in docker-compose.yaml:
`web/generate-env.sh` copies `.env.example` to a target file and replaces the
secret-bearing values (`DB_PASSWORD`, `ACCESS_TOKEN_SECRET`,
`REFRESH_TOKEN_SECRET`, `CONFIRMATION_TOKEN_SECRET`, `SMTP_USERNAME`,
`SMTP_PASSWORD`, `SEAWEEDFS_ACCESS_KEY_ID`, `SEAWEEDFS_SECRET_ACCESS_KEY`)
`REFRESH_TOKEN_SECRET`, `CONFIRMATION_TOKEN_SECRET`, `SESSION_SECRET`,
`SMTP_USERNAME`, `SMTP_PASSWORD`, `SEAWEEDFS_ACCESS_KEY_ID`,
`SEAWEEDFS_SECRET_ACCESS_KEY`)
with freshly randomized ones via `openssl`. Non-secret values are left
untouched. The output file is written with `600` permissions.
@@ -173,19 +176,26 @@ The active website does not use the old Next.js/Prisma environment model. Its
core runtime variables are:
```bash
API_BASE_URL=http://localhost:8080 # Base URL for the .NET API
API_BASE_URL=http://api.core:8080 # Base URL for the .NET API (container DNS name in Docker)
SESSION_SECRET=<generated-secret> # Cookie session signing secret
NODE_ENV=development # Standard Node runtime mode
PORT=3000 # Port the built server listens on
```
The `frontend` Docker service hardcodes `API_BASE_URL` to `http://api.core:8080`
(the backend's in-network service name) and `PORT` to `3000`; only
`SESSION_SECRET` is sourced from the env file, and `NODE_ENV` is set per
compose file (`development` in dev, `production` in prod).
### Frontend variable details
#### `API_BASE_URL`
- **Required**: Yes for local development
- **Default in code**: `http://localhost:8080`
- **Used by**: `web/frontend/app/lib/auth.server.ts`
- **Purpose**: Routes website auth actions to the .NET API
- **Used by**: `web/frontend/app/features/auth/auth.server.ts`,
`web/frontend/app/features/breweries/breweries.server.ts`
- **Purpose**: Routes website auth and API calls to the .NET API
#### `SESSION_SECRET`
@@ -200,6 +210,12 @@ NODE_ENV=development # Standard Node runtime mode
- **Typical values**: `development`, `production`, `test`
- **Purpose**: Controls secure cookie behavior and runtime mode
#### `PORT`
- **Required**: No
- **Default**: `3000`
- **Purpose**: Port the built server (`build/server/index.js`) binds to
### SMTP configuration (backend)
Read by `Infrastructure.Email/SmtpEmailProvider.cs` for sending confirmation and
@@ -312,9 +328,10 @@ reference only and is not run as part of the active stack.
| `SEAWEEDFS_ACCESS_KEY_ID` | ✓ | | ✓ | Yes | S3 access key |
| `SEAWEEDFS_SECRET_ACCESS_KEY` | ✓ | | ✓ | Yes | S3 secret key |
| `SEAWEEDFS_BUCKET` | ✓ | | ✓ | Yes | S3 bucket name |
| `API_BASE_URL` | | ✓ | | Yes | Website-to-API base URL |
| `SESSION_SECRET` | | ✓ | | Yes | Website session signing |
| `NODE_ENV` | | ✓ | | No | Runtime mode |
| `API_BASE_URL` | | ✓ | ✓ | Yes | Website-to-API base URL |
| `SESSION_SECRET` | | ✓ | ✓ | Yes | Website session signing |
| `NODE_ENV` | | ✓ | ✓ | No | Runtime mode |
| `PORT` | | ✓ | ✓ | No | Frontend server port |
| `CLEAR_DATABASE` | ✓ | | ✓ | No | Dev/test reset flag |
| `ASPNETCORE_ENVIRONMENT` | ✓ | | ✓ | Yes | ASP.NET environment |
| `ASPNETCORE_URLS` | ✓ | | ✓ | Yes | API binding address |
@@ -366,6 +383,9 @@ REFRESH_TOKEN_SECRET=<generated-with-openssl>
CONFIRMATION_TOKEN_SECRET=<generated-with-openssl>
WEBSITE_BASE_URL=http://localhost:3000
# Frontend session signing
SESSION_SECRET=<generated-with-openssl>
# SMTP (Mailpit in dev)
SMTP_HOST=mailpit
SMTP_PORT=1025

View File

@@ -33,7 +33,8 @@ This command:
1. Starts a fresh SQL Server instance
2. Runs database migrations
3. Seeds test data
4. Executes all test suites in parallel
4. Executes all test suites in parallel, including the frontend's Storybook
Vitest and Playwright suites (`frontend.tests`, which needs no database)
5. Exports results to `./test-results/`
6. Exits when tests complete
@@ -49,6 +50,8 @@ cat test-results/Features.Users.Tests.trx
cat test-results/Features.Breweries.Tests.trx
cat test-results/Features.Emails.Tests.trx
cat test-results/Features.PhotoUpload.Tests.trx
cat test-results/frontend/vitest-storybook.xml
cat test-results/frontend/playwright-storybook.xml
```
### Clean up
@@ -327,8 +330,10 @@ Scenario: User login with valid credentials
## Continuous integration
Tests run automatically in CI/CD pipelines using the test Docker Compose
configuration:
The `.github/workflows/tests.yml` GitHub Actions workflow runs on every push
and pull request to `main`. It runs the same test Docker Compose
configuration used locally, so backend and frontend tests execute in the same
containerized environment in CI as they do on a developer machine:
```bash
# CI/CD command
@@ -337,13 +342,22 @@ docker compose --env-file web/.env.test -f web/docker-compose.test.yaml up --abo
docker compose --env-file web/.env.test -f web/docker-compose.test.yaml down -v
```
Because `--abort-on-container-exit` stops the stack as soon as any one-shot
test container exits (success or failure), the workflow separately inspects
the exit code of each test container (`test-env-api-specs`,
`test-env-unit-tests`, `test-env-frontend-tests`) after `up` returns and fails
the job if any of them is non-zero. `./test-results/` is uploaded as a build
artifact regardless of outcome.
Exit codes:
- `0` - All tests passed
- Non-zero - Test failures occurred
Frontend UI checks should also be included in CI for the active website
workspace:
The frontend's Storybook Vitest and Playwright suites run inside the
`frontend.tests` container (see [Docker Guide](docker.md)) as part of that
same compose run, so no separate frontend CI step is needed. To run them
locally without Docker:
```bash
cd web/frontend