149 lines
6.3 KiB
YAML
149 lines
6.3 KiB
YAML
name: Windows backend
|
|
|
|
on:
|
|
push:
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
test-windows:
|
|
name: Backend and frontend checks (Windows x64)
|
|
runs-on: windows-2025
|
|
timeout-minutes: 30
|
|
env:
|
|
DATABASE_URL: postgresql+asyncpg://postgres:root@127.0.0.1:5432/lct_test
|
|
DEV_AUTH_BYPASS: "true"
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Start the runner's PostgreSQL service
|
|
shell: pwsh
|
|
run: |
|
|
$service = Get-Service -Name 'postgresql-x64-17' -ErrorAction Stop
|
|
Set-Service -Name $service.Name -StartupType Manual
|
|
Start-Service -Name $service.Name
|
|
$pgIsReady = Join-Path $env:PGBIN 'pg_isready.exe'
|
|
$env:PGPASSWORD = 'root'
|
|
$ready = $false
|
|
for ($attempt = 0; $attempt -lt 30; $attempt++) {
|
|
& $pgIsReady -h 127.0.0.1 -p 5432 -U postgres
|
|
if ($LASTEXITCODE -eq 0) { $ready = $true; break }
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
if (-not $ready) { throw 'PostgreSQL did not become ready on 127.0.0.1:5432' }
|
|
& (Join-Path $env:PGBIN 'createdb.exe') -h 127.0.0.1 -p 5432 -U postgres lct_test
|
|
if ($LASTEXITCODE -ne 0) { throw 'Could not create clean lct_test database' }
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v7
|
|
with:
|
|
python-version: "3.11"
|
|
|
|
- name: Set up uv
|
|
uses: astral-sh/setup-uv@v10
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: backend/uv.lock
|
|
|
|
- name: Install locked backend dependencies
|
|
working-directory: backend
|
|
run: uv sync --locked --extra dev
|
|
|
|
- name: Apply migrations and seed the clean PostgreSQL database
|
|
working-directory: backend
|
|
run: |
|
|
uv run --locked --extra dev alembic upgrade head
|
|
if ($LASTEXITCODE -ne 0) { throw 'Alembic migrations failed' }
|
|
uv run --locked --extra dev python scripts/seed.py
|
|
if ($LASTEXITCODE -ne 0) { throw 'Scenario seed failed' }
|
|
|
|
- name: Prepare a least-privilege production startup probe account
|
|
shell: pwsh
|
|
run: |
|
|
$env:PGPASSWORD = 'root'
|
|
$psql = Join-Path $env:PGBIN 'psql.exe'
|
|
$password = 'Lct-Windows-CI-only-0123456789abcdef'
|
|
& $psql -h 127.0.0.1 -p 5432 -U postgres -d lct_test -v ON_ERROR_STOP=1 `
|
|
-c "CREATE ROLE lct_ci LOGIN PASSWORD '$password'"
|
|
if ($LASTEXITCODE -ne 0) { throw 'Could not create disposable startup-probe role' }
|
|
& $psql -h 127.0.0.1 -p 5432 -U postgres -d lct_test -v ON_ERROR_STOP=1 `
|
|
-c 'GRANT CONNECT ON DATABASE lct_test TO lct_ci; GRANT USAGE ON SCHEMA public TO lct_ci; GRANT SELECT ON TABLE users, scenarios, sessions, utterances TO lct_ci; GRANT UPDATE ON TABLE sessions TO lct_ci; GRANT INSERT ON TABLE audit_log TO lct_ci'
|
|
if ($LASTEXITCODE -ne 0) { throw 'Could not grant startup-probe database permissions' }
|
|
|
|
- name: Run backend tests with PostgreSQL integration enabled
|
|
working-directory: backend
|
|
run: uv run --locked --extra dev pytest -q
|
|
|
|
- name: Start production-configured app on Windows and probe health plus audited login
|
|
shell: pwsh
|
|
working-directory: backend
|
|
env:
|
|
APP_ENV: production
|
|
DATABASE_URL: postgresql+asyncpg://lct_ci:Lct-Windows-CI-only-0123456789abcdef@127.0.0.1:5432/lct_test
|
|
DEV_AUTH_BYPASS: "false"
|
|
DEMO_NO_DB: "false"
|
|
OFFLINE: "true"
|
|
LLM_PROVIDER: local
|
|
SECURE_COOKIES: "true"
|
|
SESSION_SECRET: windows-ci-smoke-only-session-secret-0123456789abcdef
|
|
PORT: "18088"
|
|
run: |
|
|
$server = Start-Process -FilePath 'uv' `
|
|
-ArgumentList @('run', '--locked', '--extra', 'dev', 'uvicorn', 'app.main:app', '--host', '127.0.0.1', '--port', $env:PORT, '--workers', '1') `
|
|
-WorkingDirectory (Get-Location).Path -PassThru
|
|
try {
|
|
$health = $null
|
|
for ($attempt = 0; $attempt -lt 60; $attempt++) {
|
|
if ($server.HasExited) { throw "Windows uvicorn exited with code $($server.ExitCode)" }
|
|
try {
|
|
$health = Invoke-RestMethod -Uri "http://127.0.0.1:$($env:PORT)/api/health" -TimeoutSec 2
|
|
break
|
|
} catch { Start-Sleep -Seconds 1 }
|
|
}
|
|
if ($null -eq $health -or $health.status -ne 'ok' -or $health.demo_no_db -ne $false -or $health.scenarios_loaded -lt 90) {
|
|
throw "Windows production startup health check failed: $($health | ConvertTo-Json -Compress)"
|
|
}
|
|
$responseFile = Join-Path $env:RUNNER_TEMP 'windows-login-smoke.json'
|
|
$httpCode = & curl.exe --silent --show-error --output $responseFile --write-out '%{http_code}' `
|
|
--header 'Content-Type: application/json' --data-raw '{"login":"windows-ci-unknown","password":"not-a-real-account"}' `
|
|
"http://127.0.0.1:$($env:PORT)/api/auth/login"
|
|
if ($LASTEXITCODE -ne 0 -or $httpCode -ne '401') { throw "Windows audited login probe returned HTTP $httpCode" }
|
|
$loginError = Get-Content -Raw $responseFile | ConvertFrom-Json
|
|
if ($loginError.detail -ne 'bad_credentials') { throw 'Windows login probe returned an unexpected public error' }
|
|
Write-Host "Windows production startup OK; scenarios=$($health.scenarios_loaded); unauthenticated login was safely rejected."
|
|
} finally {
|
|
if (-not $server.HasExited) {
|
|
& taskkill.exe /PID $server.Id /T /F | Out-Null
|
|
}
|
|
}
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: "22"
|
|
cache: npm
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Install locked frontend dependencies
|
|
working-directory: frontend
|
|
run: npm ci
|
|
|
|
- name: Check frontend types and KIO fields
|
|
working-directory: frontend
|
|
run: npm run typecheck
|
|
|
|
- name: Test reliable WebSocket outbox
|
|
working-directory: frontend
|
|
run: npm run test:ws-outbox
|
|
|
|
- name: Test DDS archive recipient filters and date sorting
|
|
working-directory: frontend
|
|
run: npm run test:dds-history
|
|
|
|
- name: Build frontend
|
|
working-directory: frontend
|
|
run: npm run build
|