lct-hack/.github/workflows/windows-backend.yml
2026-09-26 17:13:45 +00:00

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