285 lines
11 KiB
Python
285 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
SCRIPTS = Path(__file__).resolve().parent
|
|
CONTRACT = SCRIPTS / "public-runtime-task-definition-cutover.ps1"
|
|
REPO_ROOT = SCRIPTS.parent
|
|
|
|
|
|
class PublicRuntimeTaskDefinitionCutoverTest(unittest.TestCase):
|
|
def _run_powershell(self, body: str) -> subprocess.CompletedProcess[str]:
|
|
powershell = shutil.which("powershell.exe")
|
|
if powershell is None:
|
|
self.skipTest("Windows PowerShell 5.1 is not available")
|
|
with tempfile.TemporaryDirectory(prefix="vignette-task-cutover-") as raw:
|
|
harness = Path(raw) / "task-cutover.ps1"
|
|
harness.write_text(body, encoding="utf-8-sig")
|
|
return subprocess.run(
|
|
[
|
|
powershell,
|
|
"-NoLogo",
|
|
"-NoProfile",
|
|
"-NonInteractive",
|
|
"-ExecutionPolicy",
|
|
"Bypass",
|
|
"-File",
|
|
str(harness),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
timeout=30,
|
|
check=False,
|
|
)
|
|
|
|
def _assert_harness_passes(self, body: str) -> None:
|
|
completed = self._run_powershell(body)
|
|
self.assertEqual(
|
|
0,
|
|
completed.returncode,
|
|
msg=f"stdout={completed.stdout}\nstderr={completed.stderr}",
|
|
)
|
|
|
|
def test_contract_digest_ignores_only_enabled_state(self) -> None:
|
|
contract = str(CONTRACT).replace("'", "''")
|
|
self._assert_harness_passes(
|
|
f"""
|
|
. '{contract}'
|
|
$enabled = '<Task><Settings><Enabled>true</Enabled></Settings><Actions><Exec><Command>one</Command></Exec></Actions></Task>'
|
|
$disabled = '<Task><Settings><Enabled>false</Enabled></Settings><Actions><Exec><Command>one</Command></Exec></Actions></Task>'
|
|
$drifted = '<Task><Settings><Enabled>false</Enabled></Settings><Actions><Exec><Command>two</Command></Exec></Actions></Task>'
|
|
$enabledDigest = Get-PublicRuntimeTaskXmlContractSha256 -Xml $enabled
|
|
$disabledDigest = Get-PublicRuntimeTaskXmlContractSha256 -Xml $disabled
|
|
$driftedDigest = Get-PublicRuntimeTaskXmlContractSha256 -Xml $drifted
|
|
if ($enabledDigest -cne $disabledDigest) {{ exit 2 }}
|
|
if ($enabledDigest -ceq $driftedDigest) {{ exit 3 }}
|
|
exit 0
|
|
"""
|
|
)
|
|
|
|
def test_exact_action_contract_accepts_disabled_and_operational_snapshots(self) -> None:
|
|
contract = str(CONTRACT).replace("'", "''")
|
|
root = str(REPO_ROOT).replace("'", "''")
|
|
self._assert_harness_passes(
|
|
f"""
|
|
. '{contract}'
|
|
$contracts = @(Get-PublicRuntimeExpectedTaskActionContracts `
|
|
-StableSourceRoot '{root}' `
|
|
-ExpectedSourceCommit ('a' * 40) `
|
|
-ExpectedSourceTree ('b' * 40) `
|
|
-PythonPath 'C:\\Python311\\python.exe' `
|
|
-UserUploadDir 'C:\\Runtime\\uploads' `
|
|
-UserUploadManifestPath 'C:\\RuntimeState\\manifest.json' `
|
|
-ExpectedUserUploadManifestSha256 ('c' * 64) `
|
|
-UserUploadWriteFreezePath 'C:\\RuntimeState\\freeze.json' `
|
|
-CloudflaredPath 'C:\\Tools\\cloudflared.exe' `
|
|
-CloudflaredConfigPath 'C:\\RuntimeState\\cloudflared.yml' `
|
|
-PublicHealthUrl 'https://api-vignette.chanpaca.net/health')
|
|
$entries = @()
|
|
foreach ($item in $contracts) {{
|
|
$entries += [pscustomobject]@{{
|
|
role = $item.role
|
|
enabled = $false
|
|
action_execute = $item.execute
|
|
action_arguments = $item.arguments
|
|
action_working_directory = $item.working_directory
|
|
}}
|
|
}}
|
|
$snapshot = [pscustomobject]@{{ entries = $entries }}
|
|
$null = Assert-NewPublicRuntimeTaskDefinitionsPinned `
|
|
-Snapshot $snapshot `
|
|
-StableSourceRoot '{root}' `
|
|
-ExpectedSourceCommit ('a' * 40) `
|
|
-ExpectedSourceTree ('b' * 40) `
|
|
-PythonPath 'C:\\Python311\\python.exe' `
|
|
-UserUploadDir 'C:\\Runtime\\uploads' `
|
|
-UserUploadManifestPath 'C:\\RuntimeState\\manifest.json' `
|
|
-ExpectedUserUploadManifestSha256 ('c' * 64) `
|
|
-UserUploadWriteFreezePath 'C:\\RuntimeState\\freeze.json' `
|
|
-CloudflaredPath 'C:\\Tools\\cloudflared.exe' `
|
|
-CloudflaredConfigPath 'C:\\RuntimeState\\cloudflared.yml' `
|
|
-PublicHealthUrl 'https://api-vignette.chanpaca.net/health'
|
|
foreach ($entry in $entries) {{ $entry.enabled = $true }}
|
|
$null = Assert-NewPublicRuntimeTaskDefinitionsPinned `
|
|
-Snapshot $snapshot `
|
|
-StableSourceRoot '{root}' `
|
|
-ExpectedSourceCommit ('a' * 40) `
|
|
-ExpectedSourceTree ('b' * 40) `
|
|
-PythonPath 'C:\\Python311\\python.exe' `
|
|
-UserUploadDir 'C:\\Runtime\\uploads' `
|
|
-UserUploadManifestPath 'C:\\RuntimeState\\manifest.json' `
|
|
-ExpectedUserUploadManifestSha256 ('c' * 64) `
|
|
-UserUploadWriteFreezePath 'C:\\RuntimeState\\freeze.json' `
|
|
-CloudflaredPath 'C:\\Tools\\cloudflared.exe' `
|
|
-CloudflaredConfigPath 'C:\\RuntimeState\\cloudflared.yml' `
|
|
-PublicHealthUrl 'https://api-vignette.chanpaca.net/health' `
|
|
-AllowEnabled
|
|
$entries[0].action_arguments += ' -InjectedDrift'
|
|
try {{
|
|
$null = Assert-NewPublicRuntimeTaskDefinitionsPinned `
|
|
-Snapshot $snapshot `
|
|
-StableSourceRoot '{root}' `
|
|
-ExpectedSourceCommit ('a' * 40) `
|
|
-ExpectedSourceTree ('b' * 40) `
|
|
-PythonPath 'C:\\Python311\\python.exe' `
|
|
-UserUploadDir 'C:\\Runtime\\uploads' `
|
|
-UserUploadManifestPath 'C:\\RuntimeState\\manifest.json' `
|
|
-ExpectedUserUploadManifestSha256 ('c' * 64) `
|
|
-UserUploadWriteFreezePath 'C:\\RuntimeState\\freeze.json' `
|
|
-CloudflaredPath 'C:\\Tools\\cloudflared.exe' `
|
|
-CloudflaredConfigPath 'C:\\RuntimeState\\cloudflared.yml' `
|
|
-PublicHealthUrl 'https://api-vignette.chanpaca.net/health' `
|
|
-AllowEnabled
|
|
exit 2
|
|
}} catch {{
|
|
exit 0
|
|
}}
|
|
"""
|
|
)
|
|
|
|
def test_second_installer_failure_compensates_both_tasks_to_disabled(self) -> None:
|
|
contract = str(CONTRACT).replace("'", "''")
|
|
self._assert_harness_passes(
|
|
f"""
|
|
. '{contract}'
|
|
$script:states = @{{ Boot = $false; Watchdog = $false }}
|
|
$script:bootCalls = 0
|
|
$script:watchdogCalls = 0
|
|
$script:suspendCalls = 0
|
|
$script:assertCalls = 0
|
|
function Suspend-PublicRuntimeTasks {{
|
|
param($Snapshot, $TimeoutSec)
|
|
$script:suspendCalls += 1
|
|
$script:states.Boot = $false
|
|
$script:states.Watchdog = $false
|
|
}}
|
|
function Assert-PublicRuntimeTasksDisabledAndIdle {{
|
|
param($Snapshot, $TimeoutSec)
|
|
$script:assertCalls += 1
|
|
if ($script:states.Boot -or $script:states.Watchdog) {{
|
|
throw 'task was not compensated to disabled'
|
|
}}
|
|
}}
|
|
$bootInstaller = {{
|
|
$script:bootCalls += 1
|
|
$script:states.Boot = $true
|
|
}}
|
|
$watchdogInstaller = {{
|
|
$script:watchdogCalls += 1
|
|
throw 'synthetic second installer failure'
|
|
}}
|
|
$maintenance = @(
|
|
[pscustomobject]@{{ role='boot'; task_name='Boot' }},
|
|
[pscustomobject]@{{ role='watchdog'; task_name='Watchdog' }}
|
|
)
|
|
try {{
|
|
Invoke-PublicRuntimeTaskDefinitionInstallerPairDisabled `
|
|
-BootInstaller $bootInstaller `
|
|
-WatchdogInstaller $watchdogInstaller `
|
|
-MaintenanceSnapshot $maintenance `
|
|
-TimeoutSec 3
|
|
exit 2
|
|
}} catch {{
|
|
if ($script:bootCalls -ne 1 -or $script:watchdogCalls -ne 1) {{ exit 3 }}
|
|
if ($script:suspendCalls -ne 1 -or $script:assertCalls -ne 1) {{ exit 4 }}
|
|
if ($script:states.Boot -or $script:states.Watchdog) {{ exit 5 }}
|
|
exit 0
|
|
}}
|
|
"""
|
|
)
|
|
|
|
def test_second_enable_failure_compensates_both_tasks_to_disabled(self) -> None:
|
|
contract = str(CONTRACT).replace("'", "''")
|
|
self._assert_harness_passes(
|
|
f"""
|
|
. '{contract}'
|
|
$script:states = @{{ Boot = $false; Watchdog = $false }}
|
|
$script:taskPaths = @()
|
|
function Assert-PublicRuntimeTaskDefinitionSnapshotCurrent {{
|
|
param($ExpectedSnapshot)
|
|
return $ExpectedSnapshot
|
|
}}
|
|
function Enable-ScheduledTask {{
|
|
param($TaskName, $TaskPath, $ErrorAction)
|
|
$script:taskPaths += $TaskPath
|
|
if ($TaskPath -cne '\\') {{ throw 'wrong task path' }}
|
|
if ($TaskName -eq 'Watchdog') {{ throw 'synthetic second enable failure' }}
|
|
$script:states[$TaskName] = $true
|
|
}}
|
|
function Disable-ScheduledTask {{
|
|
param($TaskName, $TaskPath, $ErrorAction)
|
|
$script:taskPaths += $TaskPath
|
|
if ($TaskPath -cne '\\') {{ throw 'wrong task path' }}
|
|
$script:states[$TaskName] = $false
|
|
}}
|
|
function Get-PublicRuntimeTaskDefinitionSnapshot {{
|
|
param($BootTaskName, $WatchdogTaskName, [switch]$RequireEnabled)
|
|
return [pscustomobject]@{{ entries = @(
|
|
[pscustomobject]@{{ role='boot'; task_name='Boot'; enabled=$script:states.Boot }},
|
|
[pscustomobject]@{{ role='watchdog'; task_name='Watchdog'; enabled=$script:states.Watchdog }}
|
|
) }}
|
|
}}
|
|
$snapshot = [pscustomobject]@{{ entries = @(
|
|
[pscustomobject]@{{ role='boot'; task_name='Boot'; contract_sha256=('a' * 64); action_execute='one'; action_arguments='one'; action_working_directory='one' }},
|
|
[pscustomobject]@{{ role='watchdog'; task_name='Watchdog'; contract_sha256=('b' * 64); action_execute='two'; action_arguments='two'; action_working_directory='two' }}
|
|
) }}
|
|
try {{
|
|
Enable-NewPublicRuntimeTaskDefinitions -DisabledSnapshot $snapshot | Out-Null
|
|
exit 2
|
|
}} catch {{
|
|
if ($script:states.Boot -or $script:states.Watchdog) {{ exit 3 }}
|
|
if ($script:taskPaths.Count -lt 3) {{ exit 4 }}
|
|
if (@($script:taskPaths | Where-Object {{ $_ -cne '\\' }}).Count -ne 0) {{ exit 5 }}
|
|
exit 0
|
|
}}
|
|
"""
|
|
)
|
|
|
|
def test_operational_definition_drift_disables_both_tasks(self) -> None:
|
|
contract = str(CONTRACT).replace("'", "''")
|
|
self._assert_harness_passes(
|
|
f"""
|
|
. '{contract}'
|
|
$script:states = @{{ Boot = $false; Watchdog = $false }}
|
|
function Assert-PublicRuntimeTaskDefinitionSnapshotCurrent {{ param($ExpectedSnapshot); return $ExpectedSnapshot }}
|
|
function Enable-ScheduledTask {{
|
|
param($TaskName, $TaskPath, $ErrorAction)
|
|
if ($TaskPath -cne '\\') {{ throw 'wrong task path' }}
|
|
$script:states[$TaskName] = $true
|
|
}}
|
|
function Disable-ScheduledTask {{
|
|
param($TaskName, $TaskPath, $ErrorAction)
|
|
if ($TaskPath -cne '\\') {{ throw 'wrong task path' }}
|
|
$script:states[$TaskName] = $false
|
|
}}
|
|
function Get-PublicRuntimeTaskDefinitionSnapshot {{
|
|
param($BootTaskName, $WatchdogTaskName, [switch]$RequireEnabled)
|
|
return [pscustomobject]@{{ entries = @(
|
|
[pscustomobject]@{{ role='boot'; task_name='Boot'; enabled=$true; contract_sha256=('a' * 64); action_execute='one'; action_arguments='one'; action_working_directory='one' }},
|
|
[pscustomobject]@{{ role='watchdog'; task_name='Watchdog'; enabled=$true; contract_sha256=('c' * 64); action_execute='two'; action_arguments='two'; action_working_directory='two' }}
|
|
); set_sha256=('d' * 64) }}
|
|
}}
|
|
$snapshot = [pscustomobject]@{{ entries = @(
|
|
[pscustomobject]@{{ role='boot'; task_name='Boot'; contract_sha256=('a' * 64); action_execute='one'; action_arguments='one'; action_working_directory='one' }},
|
|
[pscustomobject]@{{ role='watchdog'; task_name='Watchdog'; contract_sha256=('b' * 64); action_execute='two'; action_arguments='two'; action_working_directory='two' }}
|
|
) }}
|
|
try {{
|
|
Enable-NewPublicRuntimeTaskDefinitions -DisabledSnapshot $snapshot | Out-Null
|
|
exit 2
|
|
}} catch {{
|
|
if ($script:states.Boot -or $script:states.Watchdog) {{ exit 3 }}
|
|
exit 0
|
|
}}
|
|
"""
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|