param( [string]$AdminUsername = 'e2e_admin', [string]$AdminPassword = 'E2EAdmin@123456', [string]$AdminEmail = 'e2e_admin@example.com', [int]$BrowserPort = 0 ) $ErrorActionPreference = 'Stop' $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path $frontendRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $tempCacheRoot = Join-Path $env:TEMP 'ums-e2e-cache' $goCacheDir = Join-Path $tempCacheRoot 'go-build' $goModCacheDir = Join-Path $tempCacheRoot 'gomod' $goPathDir = Join-Path $tempCacheRoot 'gopath' $serverExePath = Join-Path $env:TEMP ("ums-server-e2e-" + [guid]::NewGuid().ToString('N') + '.exe') New-Item -ItemType Directory -Force $goCacheDir, $goModCacheDir, $goPathDir | Out-Null function Test-UrlReady { param( [Parameter(Mandatory = $true)][string]$Url ) try { $response = Invoke-WebRequest $Url -UseBasicParsing -TimeoutSec 2 return $response.StatusCode -ge 200 -and $response.StatusCode -lt 500 } catch { return $false } } function Wait-UrlReady { param( [Parameter(Mandatory = $true)][string]$Url, [Parameter(Mandatory = $true)][string]$Label, [int]$RetryCount = 120, [int]$DelayMs = 500 ) for ($i = 0; $i -lt $RetryCount; $i++) { if (Test-UrlReady -Url $Url) { return } Start-Sleep -Milliseconds $DelayMs } throw "$Label did not become ready: $Url" } function Start-ManagedProcess { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][string]$FilePath, [string[]]$ArgumentList = @(), [Parameter(Mandatory = $true)][string]$WorkingDirectory ) $stdoutPath = Join-Path $env:TEMP "$Name-stdout.log" $stderrPath = Join-Path $env:TEMP "$Name-stderr.log" Remove-Item $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue if ($ArgumentList -and $ArgumentList.Count -gt 0) { $process = Start-Process ` -FilePath $FilePath ` -ArgumentList $ArgumentList ` -WorkingDirectory $WorkingDirectory ` -PassThru ` -WindowStyle Hidden ` -RedirectStandardOutput $stdoutPath ` -RedirectStandardError $stderrPath } else { $process = Start-Process ` -FilePath $FilePath ` -WorkingDirectory $WorkingDirectory ` -PassThru ` -WindowStyle Hidden ` -RedirectStandardOutput $stdoutPath ` -RedirectStandardError $stderrPath } return [pscustomobject]@{ Name = $Name Process = $process StdOut = $stdoutPath StdErr = $stderrPath } } function Stop-ManagedProcess { param( [Parameter(Mandatory = $false)]$Handle ) if (-not $Handle) { return } if ($Handle.Process -and -not $Handle.Process.HasExited) { try { taskkill /PID $Handle.Process.Id /T /F *> $null } catch { Stop-Process -Id $Handle.Process.Id -Force -ErrorAction SilentlyContinue } } } function Show-ManagedProcessLogs { param( [Parameter(Mandatory = $false)]$Handle ) if (-not $Handle) { return } if (Test-Path $Handle.StdOut) { Get-Content $Handle.StdOut -ErrorAction SilentlyContinue } if (Test-Path $Handle.StdErr) { Get-Content $Handle.StdErr -ErrorAction SilentlyContinue } } function Remove-ManagedProcessLogs { param( [Parameter(Mandatory = $false)]$Handle ) if (-not $Handle) { return } Remove-Item $Handle.StdOut, $Handle.StdErr -Force -ErrorAction SilentlyContinue } $backendHandle = $null $frontendHandle = $null $startedBackend = $false $startedFrontend = $false $adminInitialized = $false try { Push-Location $projectRoot try { $env:GOCACHE = $goCacheDir $env:GOMODCACHE = $goModCacheDir $env:GOPATH = $goPathDir go build -o $serverExePath .\cmd\server\main.go if ($LASTEXITCODE -ne 0) { throw 'server build failed' } } finally { Pop-Location Remove-Item Env:GOCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOMODCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOPATH -ErrorAction SilentlyContinue } $backendWasRunning = Test-UrlReady -Url 'http://127.0.0.1:8080/health' Push-Location $projectRoot try { $env:GOCACHE = $goCacheDir $env:GOMODCACHE = $goModCacheDir $env:GOPATH = $goPathDir $env:UMS_ADMIN_USERNAME = $AdminUsername $env:UMS_ADMIN_PASSWORD = $AdminPassword $env:UMS_ADMIN_EMAIL = $AdminEmail $env:UMS_ADMIN_RESET_PASSWORD = 'true' $previousErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' $initOutput = go run .\tools\init_admin.go 2>&1 | Out-String $initExitCode = $LASTEXITCODE $ErrorActionPreference = $previousErrorActionPreference if ($initExitCode -eq 0) { $adminInitialized = $true } else { $verifyOutput = go run .\tools\verify_admin.go 2>&1 | Out-String if ($LASTEXITCODE -eq 0 -and $verifyOutput -match 'password valid: True|password valid: true') { Write-Host 'init_admin fallback: existing admin credentials verified' $adminInitialized = $true } else { Write-Host $initOutput } } } finally { Pop-Location Remove-Item Env:GOCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOMODCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOPATH -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_USERNAME -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_PASSWORD -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_EMAIL -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_RESET_PASSWORD -ErrorAction SilentlyContinue } if (-not $adminInitialized -and -not $backendWasRunning) { $backendHandle = Start-ManagedProcess ` -Name 'ums-backend-bootstrap' ` -FilePath $serverExePath ` -WorkingDirectory $projectRoot $startedBackend = $true try { Wait-UrlReady -Url 'http://127.0.0.1:8080/health' -Label 'backend bootstrap' } catch { Show-ManagedProcessLogs $backendHandle throw } Stop-ManagedProcess $backendHandle Remove-ManagedProcessLogs $backendHandle $backendHandle = $null Start-Sleep -Seconds 1 Push-Location $projectRoot try { $env:GOCACHE = $goCacheDir $env:GOMODCACHE = $goModCacheDir $env:GOPATH = $goPathDir $env:UMS_ADMIN_USERNAME = $AdminUsername $env:UMS_ADMIN_PASSWORD = $AdminPassword $env:UMS_ADMIN_EMAIL = $AdminEmail $env:UMS_ADMIN_RESET_PASSWORD = 'true' $previousErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' $initOutput = go run .\tools\init_admin.go 2>&1 | Out-String $initExitCode = $LASTEXITCODE $ErrorActionPreference = $previousErrorActionPreference if ($initExitCode -eq 0) { $adminInitialized = $true } else { $verifyOutput = go run .\tools\verify_admin.go 2>&1 | Out-String if ($LASTEXITCODE -eq 0 -and $verifyOutput -match 'password valid: True|password valid: true') { Write-Host 'init_admin fallback: existing admin credentials verified' $adminInitialized = $true } else { Write-Host $initOutput } } } finally { Pop-Location Remove-Item Env:GOCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOMODCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOPATH -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_USERNAME -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_PASSWORD -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_EMAIL -ErrorAction SilentlyContinue Remove-Item Env:UMS_ADMIN_RESET_PASSWORD -ErrorAction SilentlyContinue } } if (-not $adminInitialized) { throw 'init_admin failed' } if (-not $backendWasRunning) { $backendHandle = Start-ManagedProcess ` -Name 'ums-backend' ` -FilePath $serverExePath ` -ArgumentList @() ` -WorkingDirectory $projectRoot try { Wait-UrlReady -Url 'http://127.0.0.1:8080/health' -Label 'backend' } catch { Show-ManagedProcessLogs $backendHandle throw } } if (-not (Test-UrlReady -Url 'http://127.0.0.1:3000')) { $frontendHandle = Start-ManagedProcess ` -Name 'ums-frontend' ` -FilePath 'npm.cmd' ` -ArgumentList @('run', 'dev', '--', '--host', '127.0.0.1', '--port', '3000') ` -WorkingDirectory $frontendRoot $startedFrontend = $true try { Wait-UrlReady -Url 'http://127.0.0.1:3000' -Label 'frontend' } catch { Show-ManagedProcessLogs $frontendHandle throw } } $env:E2E_LOGIN_USERNAME = $AdminUsername $env:E2E_LOGIN_PASSWORD = $AdminPassword Push-Location $frontendRoot try { & (Join-Path $PSScriptRoot 'run-cdp-smoke.ps1') -Port $BrowserPort } finally { Pop-Location Remove-Item Env:E2E_LOGIN_USERNAME -ErrorAction SilentlyContinue Remove-Item Env:E2E_LOGIN_PASSWORD -ErrorAction SilentlyContinue } } finally { if ($startedFrontend) { Stop-ManagedProcess $frontendHandle Remove-ManagedProcessLogs $frontendHandle } if ($startedBackend) { Stop-ManagedProcess $backendHandle Remove-ManagedProcessLogs $backendHandle } Remove-Item $serverExePath -Force -ErrorAction SilentlyContinue }