125 lines
5.1 KiB
PowerShell
125 lines
5.1 KiB
PowerShell
#Requires -Version 5.1
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string] $NodePath
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Resolve-NodePath {
|
|
param([AllowEmptyString()][string] $ExplicitPath)
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($ExplicitPath)) {
|
|
if (-not [IO.Path]::IsPathRooted($ExplicitPath) -or
|
|
-not (Test-Path -LiteralPath $ExplicitPath -PathType Leaf)) {
|
|
throw 'NodePath must identify an existing absolute node executable path.'
|
|
}
|
|
|
|
return [IO.Path]::GetFullPath($ExplicitPath)
|
|
}
|
|
|
|
$command = Get-Command node -CommandType Application -ErrorAction SilentlyContinue |
|
|
Select-Object -First 1
|
|
if ($null -ne $command) {
|
|
return $command.Source
|
|
}
|
|
|
|
$programFiles = [Environment]::GetFolderPath(
|
|
[Environment+SpecialFolder]::ProgramFiles)
|
|
$candidates = @(
|
|
(Join-Path $programFiles 'Microsoft Visual Studio\18\Community\MSBuild\Microsoft\VisualStudio\NodeJs\node.exe'),
|
|
(Join-Path $programFiles 'Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VisualStudio\NodeJs\node.exe')
|
|
)
|
|
foreach ($candidate in $candidates) {
|
|
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
|
|
return [IO.Path]::GetFullPath($candidate)
|
|
}
|
|
}
|
|
|
|
throw 'A Node.js executable was not found. Pass its absolute path with -NodePath.'
|
|
}
|
|
|
|
$node = Resolve-NodePath -ExplicitPath $NodePath
|
|
$repositoryRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
$scripts = @(
|
|
(Join-Path $repositoryRoot 'Web\playout-safety.js'),
|
|
(Join-Path $repositoryRoot 'Web\app.js')
|
|
)
|
|
$test = Join-Path $repositoryRoot 'tests\Web\playout-safety.test.cjs'
|
|
|
|
foreach ($script in $scripts) {
|
|
& $node --check $script
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "JavaScript syntax validation failed with exit code $LASTEXITCODE."
|
|
}
|
|
}
|
|
|
|
$appScript = Get-Content -LiteralPath (Join-Path $repositoryRoot 'Web\app.js') -Raw -Encoding UTF8
|
|
$mainWindowScript = Get-Content -LiteralPath (Join-Path $repositoryRoot 'MainWindow.xaml.cs') -Raw -Encoding UTF8
|
|
$sceneCatalogMatches = [regex]::Matches(
|
|
$appScript,
|
|
'builderKey:\s*"s[0-9]+"')
|
|
if ($sceneCatalogMatches.Count -ne 35) {
|
|
throw "Web scene catalog must contain exactly 35 builder entries; found $($sceneCatalogMatches.Count)."
|
|
}
|
|
$selectionPresetMatches = [regex]::Matches(
|
|
$appScript,
|
|
'(?m)^\s+s[0-9]+:\s+\[')
|
|
if ($selectionPresetMatches.Count -ne 34 -or
|
|
$appScript -notmatch 'Every reachable legacy builder must have a Web selection preset') {
|
|
throw "Web scene selection presets must cover exactly 34 reachable builders; found $($selectionPresetMatches.Count)."
|
|
}
|
|
if ($appScript -notmatch 'reachableAliases\.size !== 45' -or
|
|
$appScript -notmatch 'sceneCutAlias' -or
|
|
$appScript -notmatch 'sceneAliases\(definition\)\.includes\(storedCode\)' -or
|
|
$appScript -notmatch 'resolveStoredCatalogIndex') {
|
|
throw 'Web scene selection must expose and preserve all 45 active cut aliases.'
|
|
}
|
|
if ($appScript -match 'payload\.cue' -or
|
|
$appScript -notmatch 'payload\.playlist' -or
|
|
$appScript -notmatch 'payload\.selectedIndex') {
|
|
throw 'Web playout must send a PREPARE playlist snapshot and must not choose the NEXT cue.'
|
|
}
|
|
if ($appScript -notmatch 'const DEFAULT_FADE_DURATION = 6;' -or
|
|
$appScript -notmatch 'fadeDuration: DEFAULT_FADE_DURATION') {
|
|
throw 'Web playout default fade must match MainForm ComboDi.SelectedIndex 6.'
|
|
}
|
|
if ($appScript -notmatch 'normalizeScenePreviewFields' -or
|
|
$appScript -notmatch 'currentPageItemCount' -or
|
|
$appScript -notmatch 'renderSceneDataPreview') {
|
|
throw 'Web playout must render bounded authoritative scene data and complete page state.'
|
|
}
|
|
if ($appScript -notmatch 'row-enabled' -or
|
|
$appScript -notmatch 'item\.enabled = include\.checked' -or
|
|
$appScript -notmatch 'isPlaylistSnapshotLocked' -or
|
|
$appScript -notmatch 'pending: Boolean\(state\.playout\.pending\)' -or
|
|
$appScript -notmatch 'outcomeUnknown: state\.playout\.sessionQuarantined') {
|
|
throw 'Web playlist must expose the legacy active flag and freeze its native snapshot.'
|
|
}
|
|
if ($appScript -notmatch 'postNative\("playout-timeout-quarantine", \{ requestId, command \}\)' -or
|
|
$appScript -notmatch 'browserCorrelationQuarantined') {
|
|
throw 'Web response timeouts must latch the process-lifetime native quarantine.'
|
|
}
|
|
if ($mainWindowScript -notmatch 'OnNavigationStarting' -or
|
|
$mainWindowScript -notmatch 'OnProcessFailed' -or
|
|
$mainWindowScript -notmatch 'ReloadBrowserSafely' -or
|
|
$mainWindowScript -notmatch 'QuarantineIfBrowserCorrelationCanBeLost' -or
|
|
$mainWindowScript -notmatch 'Volatile\.Read\(ref _playoutCommandInFlight\)') {
|
|
throw 'Reload, navigation, and WebView process failure must quarantine in-flight playout correlation.'
|
|
}
|
|
|
|
& $node --test $test
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Web playout tests failed with exit code $LASTEXITCODE."
|
|
}
|
|
|
|
[pscustomobject][ordered]@{
|
|
Status = 'Passed'
|
|
SyntaxFiles = $scripts.Count
|
|
SceneCatalogEntries = $sceneCatalogMatches.Count
|
|
ReachableSelectionPresets = $selectionPresetMatches.Count
|
|
TestFile = [IO.Path]::GetFileName($test)
|
|
}
|