Files
ARCADIA-Studio/harness/fixtures/run-timed-bundle-oracle.ps1
T

100 lines
7.3 KiB
PowerShell

param(
[Parameter(Mandatory=$true)][string]$ServerExe,
[Parameter(Mandatory=$true)][string]$PristineFile,
[Parameter(Mandatory=$true)][string]$SavedEntry
)
$ErrorActionPreference='Stop'
$taskRoot=(Resolve-Path (Join-Path $PSScriptRoot '../..')).Path
$exe=(Resolve-Path -LiteralPath $ServerExe).Path
$pristine=(Resolve-Path -LiteralPath $PristineFile).Path
$saved=(Resolve-Path -LiteralPath $SavedEntry).Path
$include=(Resolve-Path -LiteralPath (Join-Path (Split-Path -Parent $saved) 'sa/model.arcadia')).Path
$runRoot=Join-Path $taskRoot ('.local/timed-bundle-oracle-'+(Get-Date -Format 'yyyyMMdd-HHmmss'))
$storePath=Join-Path $runRoot 'store'
New-Item -ItemType Directory -Path $runRoot,$storePath | Out-Null
$listener=[Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,0)
$listener.Start(); $probePort=$listener.LocalEndpoint.Port; $listener.Stop()
$base="http://127.0.0.1:$probePort"
$testToken='timed-bundle-oracle-20260906-synthetic-only'
$tokenPath=Join-Path $runRoot 'synthetic-token.txt'
[IO.File]::WriteAllText($tokenPath,$testToken)
$script:ownedServer=$null
$script:launch=0
function Start-Oracle {
$script:launch++
$script:ownedServer=Start-Process -FilePath $exe -ArgumentList @('--bind',"127.0.0.1:$probePort",'--data-dir',('"'+$storePath+'"'),'--token-file',('"'+$tokenPath+'"')) -WindowStyle Hidden -PassThru -RedirectStandardOutput (Join-Path $runRoot "server-$script:launch.stdout.log") -RedirectStandardError (Join-Path $runRoot "server-$script:launch.stderr.log")
for($n=0;$n -lt 40;$n++) {
if($script:ownedServer.HasExited){throw 'Oracle server exited'}
try{$r=Invoke-WebRequest "$base/health/ready" -TimeoutSec 1;if($r.StatusCode -eq 200){return}}catch{}
Start-Sleep -Milliseconds 250
}
throw 'Oracle readiness expired'
}
function Stop-Oracle {
if($null -ne $script:ownedServer -and -not $script:ownedServer.HasExited){Stop-Process -InputObject $script:ownedServer;$script:ownedServer.WaitForExit()}
$script:ownedServer=$null
}
function Request-Oracle([string]$label,[string]$method,[string]$path,$body=$null,[int]$expected=200) {
$args=@{Uri="$base$path";Method=$method;Headers=@{Authorization="Bearer $testToken"};SkipHttpErrorCheck=$true;TimeoutSec=40}
if($null -ne $body){$json=ConvertTo-Json -InputObject $body -Depth 100 -Compress;[IO.File]::WriteAllText((Join-Path $runRoot "$label.request.json"),$json);$args.ContentType='application/json';$args.Body=[Text.Encoding]::UTF8.GetBytes($json)}
$r=Invoke-WebRequest @args
[IO.File]::WriteAllText((Join-Path $runRoot "$label.response.json"),[string]$r.Content)
$tag=[string]($r.Headers.ETag | Select-Object -First 1)
@{status=[int]$r.StatusCode;etag=$tag;path=$path} | ConvertTo-Json | Set-Content (Join-Path $runRoot "$label.meta.json")
if($r.StatusCode -ne $expected){throw "$label expected $expected, got $($r.StatusCode): $($r.Content)"}
return @{data=(ConvertFrom-Json -InputObject $r.Content -AsHashtable);etag=$tag;location=[string]($r.Headers.Location | Select-Object -First 1)}
}
function Ordered-Json($value) {
if($null -eq $value){return $null}
if($value -is [Collections.IDictionary]){$map=[ordered]@{};foreach($key in @($value.Keys | Sort-Object)){$map[$key]=Ordered-Json $value[$key]};return $map}
if($value -is [Collections.IList]){$items=@(foreach($item in $value){Ordered-Json $item});return ,$items}
return $value
}
function Equal-Graph($left,$right,[string]$label) {
$a=ConvertTo-Json -InputObject (Ordered-Json $left) -Depth 100 -Compress
$b=ConvertTo-Json -InputObject (Ordered-Json $right) -Depth 100 -Compress
if($a -cne $b){[IO.File]::WriteAllText((Join-Path $runRoot "$label.expected.json"),$a);[IO.File]::WriteAllText((Join-Path $runRoot "$label.actual.json"),$b);throw "$label differs"}
}
try {
$hashes=@(Get-FileHash -LiteralPath $exe,$pristine,$saved,$include -Algorithm SHA256 | Select-Object Path,Hash)
$hashes | ConvertTo-Json | Set-Content (Join-Path $runRoot 'input-hashes.json')
$pristineBundle=@{entry=[IO.Path]::GetFileName($pristine);files=@{}}
$pristineBundle.files[$pristineBundle.entry]=[IO.File]::ReadAllText($pristine)
$savedBundle=@{entry=[IO.Path]::GetFileName($saved);files=@{}}
$savedBundle.files[$savedBundle.entry]=[IO.File]::ReadAllText($saved)
$savedBundle.files['sa/model.arcadia']=[IO.File]::ReadAllText($include)
Start-Oracle
$oldImport=Request-Oracle '01-pristine-import' 'POST' '/api/v1/projects/import-source-project' $pristineBundle 201
$newImport=Request-Oracle '02-saved-import' 'POST' '/api/v1/projects/import-source-project' $savedBundle 201
$old=Request-Oracle '03-pristine-model' 'GET' "$($oldImport.location)/model"
$new=Request-Oracle '04-saved-model' 'GET' "$($newImport.location)/model"
$bundle=Request-Oracle '05-saved-source-project' 'GET' "$($newImport.location)/source-project"
Equal-Graph $savedBundle $bundle.data 'saved-source-bytes'
$eventId='49044cb7-5f91-408a-86fc-1cc012eb2972'
$oldEventId='f0060906-0000-4000-8000-000000000013'
$transitionId='f0060906-0000-4000-8000-000000000033'
if($old.data.elements.Contains($eventId)){throw 'Event already exists in pristine model'}
$expectedModel=ConvertFrom-Json -InputObject (ConvertTo-Json -InputObject $old.data -Depth 100) -AsHashtable
$event=ConvertFrom-Json -InputObject (ConvertTo-Json -InputObject $expectedModel.elements[$oldEventId] -Depth 100) -AsHashtable
$event.id=$eventId;$event.alias='after_30_s';$event.name='After 30 s'
$event.body.Sa.Shared.Event.body.Time.expression='30 s'
$expectedModel.elements[$eventId]=$event
$expectedModel.relations[$transitionId].name='Unfinished timed refresh'
$expectedModel.relations[$transitionId].body.StateTransition.triggers=@($expectedModel.relations[$transitionId].body.StateTransition.triggers)+@($eventId)
Equal-Graph $expectedModel $new.data 'exact-three-deltas'
$validation=Request-Oracle '06-saved-validation' 'GET' "$($newImport.location)/validation"
if(-not $validation.data.valid -or @($validation.data.issues | Where-Object severity -eq 'error').Count){throw 'Saved graph invalid'}
Stop-Oracle
Start-Oracle
$reopened=Request-Oracle '07-reopened-model' 'GET' "$($newImport.location)/model"
$reopenedBundle=Request-Oracle '08-reopened-source-project' 'GET' "$($newImport.location)/source-project"
Equal-Graph $new.data $reopened.data 'reopened-model'
Equal-Graph $savedBundle $reopenedBundle.data 'reopened-source-bytes'
if($new.etag -cne $reopened.etag){throw 'Restart changed ETag'}
$afterHashes=@(Get-FileHash -LiteralPath $exe,$pristine,$saved,$include -Algorithm SHA256 | Select-Object Path,Hash)
Equal-Graph $hashes $afterHashes 'input-files-unchanged'
@{result='passed';evidence=$runRoot;pristine_elements=$old.data.elements.Count;saved_elements=$new.data.elements.Count;relations=$new.data.relations.Count;diagrams=$new.data.diagrams.Count;placements=@($new.data.diagrams.Values | ForEach-Object {$_.nodes.Count});new_event=$new.data.elements[$eventId];transition_before=$old.data.relations[$transitionId];transition_after=$new.data.relations[$transitionId];issues=$validation.data.issues} | ConvertTo-Json -Depth 40 | Set-Content (Join-Path $runRoot 'result.json')
Get-Content (Join-Path $runRoot 'result.json')
} catch {$_ | Out-String | Set-Content (Join-Path $runRoot 'failure.txt');throw}
finally {Stop-Oracle}