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