#Requires -Version 7.0 <# .SYNOPSIS Orchestrates the FOCAS driver integration-test loop: bring up the focas-mock Docker container, run the managed wire-client integration tests, tear down Docker. .DESCRIPTION The FOCAS integration fixture now needs just two things running together: 1. A single focas-mock container listening on :8193 (one service, no per-series compose profile ceremony — the mock's native FOCAS Ethernet responder handles every call the managed driver issues). 2. The integration-test assembly built. The managed `WireFocasClient` dials the mock directly; there is no shim DLL, no P/Invoke, no test-bin DLL copy step. This script handles both and cleans up on exit. Designed to run unattended on a build agent or on a developer box. Exit code matches the test suite (0 = all pass or skip-clean, non-zero when any integration test failed). .PARAMETER Profile focas-mock profile name to seed at startup (e.g. `ThirtyOne_i`, `Sixteen_i`, `fwlib30i64`). Defaults to `ThirtyOne_i`. The fixture resolves aliases via `FocasCncSeries`, and tests that need per-series state can call `fixture.LoadProfileAsync` directly at test start to override the default. .PARAMETER SkipDocker Skip docker up/down. Use when the mock is already running from another shell. .PARAMETER Configuration Build configuration — Debug or Release. Default: Debug. .PARAMETER KeepDocker Don't tear down the docker stack on exit. Useful for iterating on tests. #> param( [string]$Profile = "ThirtyOne_i", [switch]$SkipDocker, [ValidateSet("Debug", "Release")] [string]$Configuration = "Debug", [switch]$KeepDocker ) Set-StrictMode -Version 3.0 $ErrorActionPreference = "Stop" $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path $integTests = Join-Path $repoRoot "tests\ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests" $dockerYml = Join-Path $integTests "Docker\docker-compose.yml" function Write-Step { param([string]$Msg) Write-Host ""; Write-Host "=== $Msg ===" -ForegroundColor Cyan } function Write-Info { param([string]$Msg) Write-Host "[INFO] $Msg" -ForegroundColor Gray } function Write-Fail { param([string]$Msg) Write-Host "[FAIL] $Msg" -ForegroundColor Red } $cleanupScripts = @() trap { Write-Host "" Write-Fail "run-focas.ps1 crashed: $_" foreach ($c in $cleanupScripts) { try { & $c } catch { Write-Host "cleanup failed: $_" -ForegroundColor DarkYellow } } exit 2 } Write-Step "Build FOCAS IntegrationTests ($Configuration)" dotnet build (Join-Path $integTests "ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj") ` --configuration $Configuration --nologo --verbosity minimal if ($LASTEXITCODE -ne 0) { throw "dotnet build failed (exit $LASTEXITCODE)" } if (-not $SkipDocker) { Write-Step "docker compose up" if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { Write-Fail "docker CLI not on PATH. Install Docker Desktop or pass -SkipDocker + run the mock externally." exit 4 } docker compose -f $dockerYml up -d --build --wait 2>&1 | Write-Host if ($LASTEXITCODE -ne 0) { throw "docker compose up failed (exit $LASTEXITCODE)" } if (-not $KeepDocker) { $cleanupScripts += { Write-Step "docker compose down" docker compose -f $dockerYml down --remove-orphans 2>&1 | Write-Host } } Write-Info "probing localhost:8193..." $tcp = [System.Net.Sockets.TcpClient]::new() try { $ok = $tcp.ConnectAsync("127.0.0.1", 8193).Wait([TimeSpan]::FromSeconds(5)) if (-not $ok -or -not $tcp.Connected) { throw "TCP probe to localhost:8193 failed after docker compose --wait succeeded" } } finally { $tcp.Dispose() } Write-Info "mock is accepting connections" } else { Write-Step "Docker (skipped)" } Write-Step "dotnet test (wire-backend integration)" $env:OTOPCUA_FOCAS_SIM_PROFILE = $Profile dotnet test (Join-Path $integTests "ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests.csproj") ` --configuration $Configuration --no-build --nologo --verbosity minimal $testExit = $LASTEXITCODE foreach ($c in $cleanupScripts) { & $c } if ($testExit -ne 0) { Write-Fail "integration tests failed with exit $testExit" exit $testExit } Write-Host "" Write-Host "run-focas.ps1 completed successfully." -ForegroundColor Green exit 0