Skip to main content

Deployment Verification Checklist

This document provides complete deployment verification procedures to ensure the fulla full-stack system runs correctly on Windows Docker Desktop or in a Linux production environment.


Quick Verification (5 Minutes)

1. Check All Container Statuses

# Windows
docker compose -f deploy/docker/docker-compose.yml ps

# Linux
docker compose -f deploy/docker/docker-compose.prod.yml --env-file .env.docker ps

Expected result: all containers show a status of Up or Up (healthy)

ContainerStatusPort Mapping
fulla-frontendUp8080:80
fulla-adminUp8081:80
fulla-backendUp (healthy)5555:5555
fulla-postgresUp (healthy)5433:5432
fulla-redisUp6380:6379
fulla-prometheusUp9090:9090

2. Health Check

# Backend health endpoint
curl http://localhost:5555/health

# Expected output
{"status":"healthy","timestamp":"2026-08-26T10:30:00Z"}

3. Database Connection Test

# Enter the postgres container
docker exec -it fulla-postgres psql -U fulla_user -d fulla_db -c "\dt"

# Expected output: list of OAuth2-related tables
# oauth2_clients, oauth2_codes, oauth2_access_tokens, oauth2_refresh_tokens,
# oauth2_scopes, users, roles, user_roles, organizations, audit_logs, etc. (21 tables in total after V026)

4. Frontend Access Test

Open the following in a browser:

Expected result: pages load normally with no 404 or 502 errors


Full Verification (30 Minutes)

Phase 1: Infrastructure Verification

1.1 PostgreSQL Verification

# Connection test
docker exec fulla-postgres pg_isready -U fulla_user

# Expected output: /var/run/postgresql:5432 - accepting connections

# Table structure check
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name;
"

# Expected table list (V002-V026 actual schema, all with the oauth2_ prefix):
# - oauth2_access_tokens, oauth2_refresh_tokens, oauth2_codes
# - oauth2_clients, oauth2_scopes, oauth2_client_scopes
# - oauth2_user_consents, oauth2_subject_mappings, oauth2_device_codes
# - users, roles, permissions, user_roles, role_permissions
# - organizations, audit_logs, webauthn_credentials, etc.

# Database version check
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "SELECT version();"

# Expected: PostgreSQL 17.x (deploy compose defaults to postgres:17-alpine;
# existing deployments explicitly pinned to 15 should show 15.x here, see docs/operate/postgresql-major-upgrade.md)

1.2 Redis Verification

# Enter the redis container
docker exec -it fulla-redis redis-cli -a redis_secret_pass ping

# Expected output: PONG

# Test read/write
docker exec fulla-redis redis-cli -a redis_secret_pass SET test_key "hello"
docker exec fulla-redis redis-cli -a redis_secret_pass GET test_key

# Expected output: "hello"

# Check memory usage
docker exec fulla-redis redis-cli -a redis_secret_pass INFO memory

# Expected: used_memory_human shows a reasonable amount of memory usage

1.3 Network Connectivity Verification

# Test database connectivity from the backend container
docker exec fulla-backend ping -c 3 fulla-postgres

# Expected: 3 packets transmitted, 3 received, 0% packet loss

# Test Redis connectivity from the backend container
docker exec fulla-backend ping -c 3 fulla-redis

# Expected: 3 packets transmitted, 3 received, 0% packet loss

# Check DNS resolution
docker exec fulla-backend nslookup fulla-postgres

# Expected: returns the container IP address of fulla-postgres (e.g., 172.x.x.x)

Phase 2: Database Initialization Verification

2.1 Check Seed Data

# Check the admin user (roles are linked through user_roles; the users table itself has no role column)
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "
SELECT u.username, u.email, r.name AS role, u.created_at
FROM users u
LEFT JOIN user_roles ur ON ur.user_id = u.id
LEFT JOIN roles r ON r.id = ur.role_id
WHERE u.username = 'admin';
"

# Expected output:
# username | email | role | created_at
# ----------+-------------------+-------+----------------------------
# admin | [email protected] | admin | 2026-xx-xx xx:xx:xx

# Check the default clients (the table name carries the oauth2_ prefix; the name column is name)
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "
SELECT client_id, name, client_type, token_endpoint_auth_method
FROM oauth2_clients
WHERE client_id IN ('admin-console', 'vue-client');
"

# Expected output: both admin-console and vue-client are PUBLIC (token_endpoint_auth_method = none)

# Check the default scopes (the scope name column is name)
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "
SELECT name, description
FROM oauth2_scopes
LIMIT 5;
"

# Expected output: standard scopes such as openid, profile, email, admin

2.2 Verify Database Migrations

# Check the migrations table (if present)
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "\d schema_migrations"

# Or check table structure integrity
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "
SELECT COUNT(*) AS table_count
FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE';
"

# Expected: table_count >= 15 (measured 21 public tables after V026)

Phase 3: Backend API Verification

3.1 Obtain an Admin Token

admin-console is a PUBLIC client (token_endpoint_auth_method=none, no client_secret, password grant not supported). Tokens must go through the two-step authorization code + PKCE flow (F-011: PKCE is mandatory for PUBLIC clients). The following is equivalent to the setup step in scripts/backend/test-admin-endpoints.sh:

# 1) Log in to obtain an authorization code (form-encoded; code_challenge = BASE64URL(SHA256(code_verifier)))
CODE_VERIFIER=$(head -c 32 /dev/urandom | basenc --base64url | tr -d '=' | tr -d '+/' | head -c 43)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | basenc --base64url | tr -d '=')

LOGIN_RESP=$(curl -s -X POST http://localhost:5555/oauth2/login \
-d "username=admin&password=admin" \
-d "client_id=admin-console&redirect_uri=http://localhost:5174/admin/callback" \
-d "scope=openid+profile+admin&state=verify-state" \
-d "code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&json=true")
CODE=$(echo "$LOGIN_RESP" | jq -r '.code')

# 2) Exchange the authorization code for tokens (form-encoded; a PUBLIC client sends only client_id and must not include any secret)
curl -s -X POST http://localhost:5555/oauth2/token \
-d "grant_type=authorization_code&code=$CODE" \
-d "redirect_uri=http://localhost:5174/admin/callback" \
-d "client_id=admin-console&code_verifier=$CODE_VERIFIER"

# Expected response (save the access_token):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JH7xN1yQ9X2...",
"scope": "openid profile admin"
}

# Set an environment variable (used by the tests below)
export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

On Windows, use the repository-provided scripts/backend/test-admin-endpoints.ps1 to run the same login + token flow.

3.2 Verify Token Introspection

# Introspect the token (RFC 7662, form-encoded)
curl -s -X POST http://localhost:5555/oauth2/introspect \
-d "token=$TOKEN" \
-d "token_type_hint=access_token" \
-d "client_id=admin-console"

# Expected response:
{
"active": true,
"client_id": "admin-console",
"username": "admin",
"scope": "openid profile admin",
"exp": 1719123456,
"iat": 1719119856,
"sub": "admin",
"iss": "http://localhost:5555"
}

# Test an invalid token
curl -s -X POST http://localhost:5555/oauth2/introspect \
-d "token=invalid_token" \
-d "token_type_hint=access_token" \
-d "client_id=admin-console"

# Expected response: {"active": false}

3.3 Refresh a Token

# Use the refresh_token to obtain a new access_token (form-encoded;
# a PUBLIC client sends only client_id — including a client_secret would actually be rejected by F-017)
curl -s -X POST http://localhost:5555/oauth2/token \
-d "grant_type=refresh_token" \
-d "refresh_token=tGzv3JH7xN1yQ9X2..." \
-d "client_id=admin-console"

# Expected response: returns a new access_token and refresh_token
{
"access_token": "new access token...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "new refresh token...",
"scope": "openid profile admin"
}

3.4 Revoke a Token

# Revoke the token (RFC 7009, form-encoded; the client authentication method must match
# the registered token_endpoint_auth_method — a PUBLIC client uses only client_id)
curl -s -X POST http://localhost:5555/oauth2/revoke \
-d "token=$TOKEN" \
-d "token_type_hint=access_token" \
-d "client_id=admin-console"

# Expected response: HTTP 200 OK (empty response body)

# Verify the token has been revoked
curl -s -X POST http://localhost:5555/oauth2/introspect \
-d "token=$TOKEN" \
-d "token_type_hint=access_token" \
-d "client_id=admin-console"

# Expected response: {"active": false}

Phase 4: Admin Console API Verification

4.1 User Management API

# Get the user list
curl -X GET http://localhost:5555/api/admin/users \
-H "Authorization: Bearer $TOKEN"

# Expected response: user list JSON
{
"users": [
{
"user_id": 1,
"username": "admin",
"email": "[email protected]",
"role": "admin",
"created_at": "2026-08-26T10:00:00Z",
"updated_at": "2026-08-26T10:00:00Z"
}
],
"total": 1,
"page": 1,
"per_page": 20
}

# Create a new user
curl -X POST http://localhost:5555/api/admin/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "[email protected]",
"password": "TestPassword123!",
"role": "user"
}'

# Expected response: HTTP 201 Created
{
"user_id": 2,
"username": "testuser",
"email": "[email protected]",
"role": "user",
"created_at": "2026-08-26T10:30:00Z"
}

# Get a single user's details
curl -X GET http://localhost:5555/api/admin/users/2 \
-H "Authorization: Bearer $TOKEN"

# Expected response: shows the details of testuser

4.2 Client Management API

# Get the client list
curl -X GET http://localhost:5555/api/admin/clients \
-H "Authorization: Bearer $TOKEN"

# Expected response: client list (client secrets are never echoed back; only hashes are stored in the database)
{
"clients": [
{
"client_id": "admin-console",
"name": "Admin Console",
"client_type": "PUBLIC",
"token_endpoint_auth_method": "none",
"redirect_uris": ["http://localhost:5174/admin/callback"],
"allowed_grant_types": ["authorization_code", "refresh_token"],
"scopes": ["openid", "profile", "admin"]
}
],
"total": 1
}

# Create a new client
curl -X POST http://localhost:5555/api/admin/clients \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_id": "test-client",
"name": "Test Client",
"client_type": "CONFIDENTIAL",
"client_secret": "test-secret",
"redirect_uris": ["http://localhost:8080/callback"],
"allowed_grant_types": ["authorization_code", "refresh_token"],
"scopes": ["openid", "profile", "email"]
}'

# Expected response: HTTP 201 Created (the response contains the newly created client's metadata; the secret is not echoed back)

4.3 Scope Management API

# Get all scopes
curl -X GET http://localhost:5555/api/admin/scopes \
-H "Authorization: Bearer $TOKEN"

# Expected response: scope list (the scope name field is name, consistent with the oauth2_scopes table)
{
"scopes": [
{"name": "openid", "description": "OpenID Connect"},
{"name": "profile", "description": "User profile"},
{"name": "email", "description": "User email"},
{"name": "admin", "description": "Administrative access"}
]
}

# Create a new scope
curl -X POST http://localhost:5555/api/admin/scopes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "read",
"description": "Read access to user resources"
}'

# Expected response: HTTP 201 Created

Phase 5: Frontend Feature Verification

5.1 User Frontend Verification

Test ItemStepsExpected Result
Visit the home pageOpen http://localhost:8080The login page is displayed
User registrationFill in the registration form (username, email, password)Registration succeeds and redirects to the login page
User loginLog in with the account just registeredLogin succeeds and redirects to the profile page
View profileClick the "Profile" menuUser information is displayed (username, email)
Change passwordEnter the old and new passwordsThe password change succeeds and the user must log in again
Log outClick the "Log out" buttonLogout succeeds and redirects to the login page

5.2 Admin Console Verification

Test ItemStepsExpected Result
Open the admin consoleOpen http://localhost:8081/adminThe admin console login page is displayed
Admin loginLog in with admin/adminLogin succeeds and the dashboard is displayed
App managementClick the "Apps" menuThe client list is displayed (contains at least admin-console)
Create an appClick "New App" and fill in the formThe app is created successfully and appears in the list
User managementClick the "Users" menuThe user list is displayed (contains at least admin and the newly registered user)
Token managementClick the "Token" menuThe list of active tokens is displayed

5.3 OAuth2 Authorization Code Flow Verification

# Step 1: Build the authorization URL (visit it in a browser)
# Note: the redirect_uri must exactly match the client's registered value
# (vue-client's seed registration uses http://127.0.0.1:8080/callback — using localhost will be rejected)
# http://localhost:5555/oauth2/authorize?
# response_type=code&
# client_id=vue-client&
# redirect_uri=http://127.0.0.1:8080/callback&
# scope=openid profile email&
# state=random_state_value

# Expected: redirect to the login page

# Step 2: User login
# Log in with a test account (e.g., testuser)

# Expected: the authorization consent page is displayed

# Step 3: User consent
# Click the "Authorize" button

# Expected: redirect to the redirect_uri carrying the authorization code
# http://127.0.0.1:8080/callback?code=xxx&state=random_state_value

# Step 4: Exchange the token (vue-client is a PUBLIC client → must include the PKCE code_verifier
# and must not include a client_secret)
curl -s -X POST http://localhost:5555/oauth2/token \
-d "grant_type=authorization_code" \
-d "code=<code obtained from the callback>" \
-d "redirect_uri=http://127.0.0.1:8080/callback" \
-d "client_id=vue-client" \
-d "code_verifier=<PKCE verifier used at login>"

# Expected response: returns an access_token and refresh_token
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "xxx",
"scope": "openid profile email"
}

Phase 5 Supplement: Email Service Verification

The email service has two modes, selected by the backend's getEmailService() based on the FULLA_SMTP_* environment variables.

5.4 Confirm the Email Service Mode

# Check the email service mode in the backend startup logs
docker logs fulla-backend 2>&1 | grep -i "Email service"

Expected output (one of the following):

  • Console mode (SMTP not configured): Email service: Console (set FULLA_SMTP_* env vars to enable SMTP)
  • SMTP mode (configured): Email service: SMTP (smtp.163.com:465)

5.5 Email Address Verification Message

Steps: log in to the user frontend → Profile page → click "Send Email Verification"

ModeVerification Method
Console modeThe message content is written to the backend logs; copy the verification link from there
SMTP modeA real message should arrive in the inbox

To view the verification link in Console mode:

docker logs fulla-backend --tail 50 2>&1 | grep -A 5 -iE "verify|email"
# Expected: a link containing "verify-email?token=xxx"

To verify message delivery in SMTP mode:

# After triggering the send, check the backend for SMTP errors
docker logs fulla-backend --tail 50 2>&1 | grep -iE "smtp|email|curl"
# Expected: no ERROR-level logs; a "Verify Your Email" message arrives in the inbox

5.6 Password Reset Message

Steps: frontend "Forgot Password" page → enter the email address → submit

  • Expected response (anti-enumeration): regardless of whether the email address exists, the same message is returned: If the email exists, a reset link has been sent
  • In Console mode, the reset link is likewise written to the backend logs

5.7 Enable Real SMTP (Optional; see the deployment guide for details)

For real message delivery, set FULLA_SMTP_HOST / FULLA_SMTP_USER / FULLA_SMTP_PASSWORD in .env.docker and restart the backend:

docker compose -f deploy/docker/docker-compose.yml --env-file .env.docker up -d fulla-backend
docker logs fulla-backend 2>&1 | grep -i "Email service"
# Expected: Email service: SMTP (...)

Note: email verification links use FULLA_FRONTEND_URL. In a local deployment this is http://localhost:8080; clicking the link from another machine will not work.


Phase 6: Security Verification

6.1 Error Response Verification

# Test an invalid client ID (token endpoint, form-encoded)
curl -s -X POST http://localhost:5555/oauth2/token \
-d "grant_type=authorization_code&code=x" \
-d "client_id=invalid-client&code_verifier=x"

# Expected response: HTTP 401 Unauthorized
{
"error": "invalid_client",
"error_description": "Client authentication failed"
}

# Test a wrong password (login endpoint — note: failed attempts trigger F-018 rate limiting, so do not exceed the threshold with repeated attempts)
curl -s -X POST http://localhost:5555/oauth2/login \
-d "username=admin&password=wrong-password" \
-d "client_id=admin-console&redirect_uri=http://localhost:5174/admin/callback" \
-d "scope=openid&state=t&code_challenge=x&code_challenge_method=S256"

# Expected response: HTTP 401 Unauthorized (error codes go through the ErrorCatalog, ensuring a unified anti-enumeration posture)
{
"error": "invalid_grant",
"error_description": "Invalid username or password"
}

# Test a missing required parameter
curl -s -X POST http://localhost:5555/oauth2/token \
-d "grant_type=authorization_code"

# Expected response: HTTP 400 Bad Request
{
"error": "invalid_request",
"error_description": "Missing required parameter: client_id"
}

6.2 Token Expiration Verification

# Wait for the token to expire (3600 seconds), or change the backend configuration to a shorter expiration time for testing

# Or use an already-revoked token
curl -X GET http://localhost:5555/api/admin/users \
-H "Authorization: Bearer revoked_token"

# Expected response: HTTP 401 Unauthorized
{
"error": "invalid_token",
"error_description": "The access token expired or has been revoked"
}

6.3 Scope Authorization Verification

# Request a resource beyond the granted scope (if scope-based access control is implemented)
curl -X GET http://localhost:5555/api/admin/users \
-H "Authorization: Bearer $token_with_limited_scope"

# Expected response: HTTP 403 Forbidden
{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token"
}

Phase 7: Performance and Monitoring Verification

7.1 Prometheus Metrics Verification

# Access the Prometheus UI
# Open in a browser: http://localhost:9090

# Example metric queries (the authoritative list is in docs/operate/observability.md):
# - oauth2_requests_total: total number of requests (by endpoint/status dimensions)
# - oauth2_latency_seconds: latency histogram for key steps
# - oauth2_active_tokens: current number of active tokens
# - oauth2_login_failures_total: number of login failures

# Expected: metrics are collected normally and data is present

7.2 Log Verification

# View backend logs
docker logs fulla-backend --tail 50

# Expected: no ERROR-level logs, normal INFO/DEBUG logs

# View nginx logs (Linux production environment)
docker logs oauth2-nginx --tail 50

# Expected: normal access logs, no 5xx errors

# Follow logs in real time
docker compose -f deploy/docker/docker-compose.yml logs -f fulla-backend

7.3 Database Performance Verification

# Check the number of database connections
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "
SELECT count(*) AS connections
FROM pg_stat_activity
WHERE datname = 'fulla_db';
"

# Expected: connections is a reasonable value (usually < 20)

# Check slow queries (if the pg_stat_statements extension is available)
docker exec fulla-postgres psql -U fulla_user -d fulla_db -c "
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 5;
"

# Expected: no significant slow queries (mean_time < 100ms)

Troubleshooting Checkpoints

Problem 1: Containers fail to start

Check steps:

# View container status
docker compose -f deploy/docker/docker-compose.yml ps

# View the failed container's logs
docker logs fulla-backend

# Check resource usage
docker stats

# Validate the configuration file
docker compose -f deploy/docker/docker-compose.yml config

Problem 2: Database connection failures

Check steps:

# Verify the postgres container health status
docker exec fulla-postgres pg_isready -U fulla_user

# Check network connectivity
docker exec fulla-backend ping fulla-postgres

# Verify environment variables
docker exec fulla-backend env | grep FULLA_DB

# View database logs
docker logs fulla-postgres

Problem 3: Frontend cannot reach the backend API

Check steps:

# Test the backend connection from the frontend container
docker exec fulla-frontend curl -s http://fulla-backend:5555/health

# Check the nginx configuration (production environment)
docker exec oauth2-nginx nginx -t

# View the backend CORS configuration
docker logs fulla-backend | grep -i cors

Problem 4: Token verification failures

Check steps:

# Verify that the JWT keys exist
docker exec fulla-backend ls -la /app/keys/

# Check the token signature
# Copy the access_token to https://jwt.io to decode and verify it

# View authentication errors in the backend logs
docker logs fulla-backend | grep -i "auth\|token"

Automated Verification Script

Full Verification Script (PowerShell)

Save as verify-deployment.ps1:

# fulla deployment verification script
param(
[string]$BackendUrl = "http://localhost:5555",
[string]$FrontendUrl = "http://localhost:8080",
[string]$AdminUrl = "http://localhost:8081",
[switch]$Verbose
}

function Test-ContainerStatus {
Write-Host "`n[1/8] Checking container status..." -ForegroundColor Cyan
$containers = docker compose -f deploy/docker/docker-compose.yml ps
if ($LASTEXITCODE -eq 0) {
Write-Host "[+] All containers are running normally" -ForegroundColor Green
return $true
} else {
Write-Host "[-] Container status abnormal" -ForegroundColor Red
return $false
}
}

function Test-BackendHealth {
Write-Host "`n[2/8] Checking backend health..." -ForegroundColor Cyan
try {
$response = Invoke-RestMethod -Uri "$BackendUrl/health" -Method Get
if ($response.status -eq "healthy") {
Write-Host "[+] Backend health check passed" -ForegroundColor Green
return $true
}
} catch {
Write-Host "[-] Backend health check failed: $_" -ForegroundColor Red
return $false
}
}

function Test-DatabaseConnection {
Write-Host "`n[3/8] Checking database connection..." -ForegroundColor Cyan
$result = docker exec fulla-postgres pg_isready -U fulla_user 2>&1
if ($LASTEXITCODE -eq 0 -and $result -match "accepting connections") {
Write-Host "[+] Database connection is normal" -ForegroundColor Green
return $true
} else {
Write-Host "[-] Database connection failed" -ForegroundColor Red
return $false
}
}

function Test-DatabaseTables {
Write-Host "`n[4/8] Checking database table structure..." -ForegroundColor Cyan
$result = docker exec fulla-postgres psql -U fulla_user -d fulla_db -t -c "
SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE';
" 2>&1

$tableCount = [int]$result.Trim()
if ($tableCount -ge 20) {
Write-Host "[+] Database table structure complete ($tableCount tables)" -ForegroundColor Green
return $true
} else {
Write-Host "[-] Database table structure incomplete (only $tableCount tables)" -ForegroundColor Red
return $false
}
}

function Test-SeedData {
Write-Host "`n[5/8] Checking seed data..." -ForegroundColor Cyan
$result = docker exec fulla-postgres psql -U fulla_user -d fulla_db -t -c "
SELECT COUNT(*) FROM users WHERE username = 'admin';
" 2>&1

$count = [int]$result.Trim()
if ($count -eq 1) {
Write-Host "[+] Admin account has been created" -ForegroundColor Green
return $true
} else {
Write-Host "[-] Admin account has not been created" -ForegroundColor Red
return $false
}
}

function Test-RedisConnection {
Write-Host "`n[6/8] Checking Redis connection..." -ForegroundColor Cyan
$result = docker exec fulla-redis redis-cli -a redis_secret_pass ping 2>&1
if ($result -match "PONG") {
Write-Host "[+] Redis connection is normal" -ForegroundColor Green
return $true
} else {
Write-Host "[-] Redis connection failed" -ForegroundColor Red
return $false
}
}

function Test-DiscoveryEndpoint {
Write-Host "`n[7/8] Testing the OIDC discovery endpoint..." -ForegroundColor Cyan
# For the full login flow (PKCE), see Phase 3 above; scripted deployment checks use the discovery
# endpoint to verify that the backend OAuth2 stack is ready without depending on specific credentials.
try {
$response = Invoke-RestMethod -Uri "$BackendUrl/.well-known/openid-configuration" -Method Get
if ($response.issuer -and $response.token_endpoint) {
Write-Host "[+] OIDC discovery endpoint is normal (issuer: $($response.issuer))" -ForegroundColor Green
return $true
}
} catch {
Write-Host "[-] OIDC discovery endpoint failed: $_" -ForegroundColor Red
return $false
}
}

function Test-FrontendAccess {
Write-Host "`n[8/8] Checking frontend access..." -ForegroundColor Cyan
try {
$response = Invoke-WebRequest -Uri $FrontendUrl -Method Get -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Host "[+] Frontend page is accessible" -ForegroundColor Green
return $true
}
} catch {
Write-Host "[-] Frontend page access failed: $_" -ForegroundColor Red
return $false
}
}

# Run all tests
$results = @()
$results += Test-ContainerStatus
$results += Test-BackendHealth
$results += Test-DatabaseConnection
$results += Test-DatabaseTables
$results += Test-SeedData
$results += Test-RedisConnection
$results += Test-DiscoveryEndpoint
$results += Test-FrontendAccess

# Summarize the results
$passed = ($results | Where-Object { $_ -eq $true }).Count
$total = $results.Count

Write-Host "`n" -NoNewline
Write-Host ("=" * 60) -ForegroundColor DarkGray
Write-Host "Verification results: $passed / $total passed" -ForegroundColor $(if ($passed -eq $total) { "Green" } else { "Yellow" })
Write-Host ("=" * 60) -ForegroundColor DarkGray

if ($passed -eq $total) {
Write-Host "`n[+] Deployment verification fully passed! The system is ready for use." -ForegroundColor Green
exit 0
} else {
Write-Host "`n[-] Deployment verification failed; please review the failed items above." -ForegroundColor Red
exit 1
}

Usage:

# Basic verification
.\verify-deployment.ps1

# Verbose mode
.\verify-deployment.ps1 -Verbose

# Custom endpoints
.\verify-deployment.ps1 -BackendUrl "https://your-domain.com" -FrontendUrl "https://your-domain.com"

Verification Report Template

After completing verification, fill in the following report template:

## fulla Deployment Verification Report

**Verification date**: YYYY-MM-DD
**Verification environment**: Windows Docker Desktop / Linux production server
**Verified by**: [Name]

### Verification Results Summary

| Phase | Status | Notes |
|------|------|------|
| Infrastructure verification | Passed | All containers running normally |
| Database initialization verification | Passed | 21 tables (V026), admin account created |
| Backend API verification | Passed | Token endpoint, introspection, and revocation working normally |
| Admin console API verification | Passed | User, client, and scope management working normally |
| Frontend feature verification | Passed | User login, registration, and profile features working normally |
| Security verification | Passed | Error handling and token verification working normally |
| Performance and monitoring verification | Partially passed | Prometheus normal; slow queries need optimization |

### Issues Found

1. **Issue description**: [specific issue]
- **Impact scope**: [affected functionality]
- **Resolution**: [how it was resolved]
- **Status**: [Open | Resolved]

### Optimization Suggestions

1. [Suggestion 1]
2. [Suggestion 2]

### Next Actions

- [ ] Deploy to production
- [ ] Configure Let's Encrypt certificates
- [ ] Set up monitoring alerts
- [ ] Run performance load tests

### Sign-off

Verified by: __________ Date: __________
Reviewed by: __________ Date: __________

Summary

This verification checklist covers all core functionality of the fulla system:

  • Infrastructure: Docker containers, networking, storage volumes
  • Data layer: PostgreSQL database, Redis cache
  • Business layer: OAuth2 core flows, admin console APIs
  • Presentation layer: Vue.js user frontend, admin console
  • Security: authentication, authorization, token management
  • Observability: logs, metrics, health checks

Pass criteria:

  • All containers have a status of Up
  • Backend health check passes
  • Database table structure is complete (21 public tables in total after V026)
  • The admin account is usable
  • OAuth2 core flows (authorization, tokens, introspection, revocation) work correctly
  • Frontend pages are accessible and basic operations complete successfully

Once this checklist has been fully verified, the system is ready for production use.