diff --git a/scripts/test-install-guards.ps1 b/scripts/test-install-guards.ps1 new file mode 100644 index 0000000..3bed575 --- /dev/null +++ b/scripts/test-install-guards.ps1 @@ -0,0 +1,24 @@ +[CmdletBinding()] +param([switch]$Summary) +$ErrorActionPreference = 'Stop' +$root = Split-Path $PSScriptRoot -Parent +. (Join-Path $PSScriptRoot 'lib/manifest.ps1') +. (Join-Path $PSScriptRoot 'lib/install-targets.ps1') + +$work = Join-Path ([System.IO.Path]::GetTempPath()) ("ai-config-install-guard-test-" + [Guid]::NewGuid()) +try { + $installed = Join-Path $work 'installed' + $missing = Join-Path $work 'missing' + New-Item $installed -ItemType Directory -Force | Out-Null + New-Item (Get-ManagedManifestPath $installed) -ItemType File -Force | Out-Null + + if (-not (Test-AnyManifestPresent @($installed, $missing))) { throw 'Expected an installed destination to be detected.' } + if (Test-AnyManifestPresent @($missing)) { throw 'A destination without a manifest must not count as installed.' } + if (Test-AllManifestsPresent @($installed, $missing)) { throw 'Update guard must fail when any selected destination is not installed.' } + if (-not (Test-AllManifestsPresent @($installed))) { throw 'Update guard must pass when every selected destination is installed.' } + + if ($Summary) { Write-Output 'Tests: PASS | install guards' } else { Write-Output 'PASS install guards: already-installed and not-installed detection' } +} finally { + Remove-Item $work -Recurse -Force -ErrorAction SilentlyContinue +} +exit 0 diff --git a/scripts/test-install-guards.sh b/scripts/test-install-guards.sh new file mode 100644 index 0000000..888786f --- /dev/null +++ b/scripts/test-install-guards.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail +summary=false +while [ $# -gt 0 ]; do + case "$1" in + --summary) summary=true; shift ;; + *) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;; + esac +done + +root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +. "$root/scripts/lib/manifest.sh" +. "$root/scripts/lib/install-targets.sh" + +work=$(mktemp -d) +trap 'rm -rf -- "$work"' EXIT + +installed="$work/installed" +missing="$work/missing" +mkdir -p "$installed" +: > "$(manifest_path "$installed")" + +any_manifest_present "$installed" "$missing" || { printf 'Expected an installed destination to be detected.\n' >&2; exit 1; } +! any_manifest_present "$missing" || { printf 'A destination without a manifest must not count as installed.\n' >&2; exit 1; } +! all_manifests_present "$installed" "$missing" || { printf 'Update guard must fail when any selected destination is not installed.\n' >&2; exit 1; } +all_manifests_present "$installed" || { printf 'Update guard must pass when every selected destination is installed.\n' >&2; exit 1; } + +if [ "$summary" = true ]; then printf 'Tests: PASS | install guards\n'; else printf 'PASS install guards: already-installed and not-installed detection\n'; fi diff --git a/scripts/test-plugin-selection.ps1 b/scripts/test-plugin-selection.ps1 new file mode 100644 index 0000000..af7f33d --- /dev/null +++ b/scripts/test-plugin-selection.ps1 @@ -0,0 +1,49 @@ +[CmdletBinding()] +param([switch]$Summary) +$ErrorActionPreference = 'Stop' +function Write-Host { param($Object) if (-not $Summary) { Microsoft.PowerShell.Utility\Write-Host $Object } } +. (Join-Path $PSScriptRoot 'lib/plugins.ps1') + +$script:answers = [System.Collections.Generic.Queue[string]]::new() +$script:answers.Enqueue('1') +$script:answers.Enqueue('2') +$script:answers.Enqueue('done') +function Read-Host { param($Prompt) $answers.Dequeue() } + +$result = Read-PluginToggleSelection -Names @('A','B','C') -Checked @($true, $false, $true) +if ($result[0] -ne $false) { throw 'Toggling checked entry 1 must uncheck it.' } +if ($result[1] -ne $true) { throw 'Toggling unchecked entry 2 must check it.' } +if ($result[2] -ne $true) { throw 'Entry 3 was never toggled and must stay unchanged.' } + +$script:answers.Enqueue('') +$confirmed = @(Read-PluginToggleSelection -Names @('A') -Checked @($true)) +if ($confirmed.Count -ne 1 -or -not $confirmed[0]) { throw 'Enter must confirm without changing the selection.' } +function Test-PluginOutput { + param([string]$Mode) + Write-Output 'download progress' + Write-Output 'WARN test warning' + $global:LASTEXITCODE = if ($Mode -eq 'fail') { 7 } else { 0 } +} +$compact = @(Invoke-PluginCommand -Command Test-PluginOutput -Arguments @('success') -Summary) +if ($compact.Count -ne 1 -or $compact[0] -ne 'WARN test warning') { throw 'Plugin summary must retain warnings and suppress progress.' } +$detailed = @(Invoke-PluginCommand -Command Test-PluginOutput -Arguments @('success')) +if ($detailed.Count -ne 2 -or $detailed[0] -ne 'download progress') { throw 'Detailed output must retain progress.' } +$diagnostics = [System.Collections.Generic.List[string]]::new() +$failed = $false +try { Invoke-PluginCommand -Command Test-PluginOutput -Arguments @('fail') -Summary | ForEach-Object { $diagnostics.Add("$_") } } +catch { $failed = $true } +if (-not $failed -or $diagnostics -notcontains 'download progress' -or $diagnostics -notcontains 'WARN test warning') { throw 'Plugin failure must retain full diagnostics and fail.' } +$global:LASTEXITCODE = 0 +function Get-InstalledPlugins { [pscustomobject]@{ pluginId = 'test@market' } } +$script:commands = @() +function Invoke-PluginCommand { param($Command, $Arguments, [switch]$DryRun, [switch]$Summary) $script:commands += "$Command $($Arguments -join ' ')" } +$entry = [pscustomobject]@{ name = 'Test'; codex_method = 'plugin'; codex_marketplace = '-'; codex_plugin = 'test@market' } +$updated = @(Install-ConfiguredPlugins -RepositoryRoot (Split-Path $PSScriptRoot -Parent) -Client Codex -Update -Summary -Entries @($entry)) +if ($commands.Count -ne 1 -or $commands[0] -ne 'codex plugin add test@market' -or $updated[0] -ne 'PLUGINS Codex: 0 ensured, 0 already installed, 1 updated') { throw 'Updating an installed Codex plugin must report one update.' } +function Get-InstalledPlugins { throw 'Empty selection must not query installed plugins.' } +function Invoke-PluginCommand { throw 'Empty selection must not run plugin commands.' } +Install-ConfiguredPlugins -RepositoryRoot (Split-Path $PSScriptRoot -Parent) -Client Both -Entries @() +Uninstall-DeselectedPlugins -Client Both -Entries @() + +if ($Summary) { Write-Output 'Tests: PASS | plugin selection' } else { Write-Output 'PASS plugin toggle selection: toggle and confirm' } +exit 0 diff --git a/scripts/test-plugin-selection.sh b/scripts/test-plugin-selection.sh new file mode 100644 index 0000000..10d582d --- /dev/null +++ b/scripts/test-plugin-selection.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail +summary=false +while [ $# -gt 0 ]; do + case "$1" in + --summary) summary=true; shift ;; + *) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;; + esac +done + +root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +. "$root/scripts/lib/plugins.sh" + +names=(A B C) +checked=(true false true) +toggle_log=$(mktemp) +trap 'rm -f -- "$toggle_log"' EXIT +read_plugin_toggle_selection names checked <<< $'1\n2\ndone' > "$toggle_log" +[ "$summary" = true ] || cat "$toggle_log" + +[ "${checked[0]}" = false ] || { printf 'Toggling checked entry 1 must uncheck it.\n' >&2; exit 1; } +[ "${checked[1]}" = true ] || { printf 'Toggling unchecked entry 2 must check it.\n' >&2; exit 1; } +[ "${checked[2]}" = true ] || { printf 'Entry 3 was never toggled and must stay unchanged.\n' >&2; exit 1; } + +read_plugin_toggle_selection names checked <<< '' > "$toggle_log" +[ "${checked[0]}" = false ] && [ "${checked[1]}" = true ] +test_plugin_output() { + printf 'download progress\nWARN test warning\n' + [ "$1" != fail ] +} +compact=$(summary=true run_plugin_command false test_plugin_output success) +[ "$compact" = 'WARN test warning' ] || { printf 'Plugin summary lost warnings or leaked progress.\n' >&2; exit 1; } +if diagnostics=$(summary=true run_plugin_command false test_plugin_output fail 2>&1); then + printf 'Plugin failure must fail.\n' >&2 + exit 1 +fi +[[ "$diagnostics" == *'download progress'* ]] && [[ "$diagnostics" == *'WARN test warning'* ]] +selected=() +plugin_installed() { printf 'Empty selection must not query installed plugins.\n' >&2; exit 1; } +run_plugin_command() { printf 'Empty selection must not run commands.\n' >&2; exit 1; } +install_configured_plugins "$root" both false false selected false +uninstall_deselected_plugins "$root" both false selected false +if read_plugin_toggle_selection names checked "$toggle_log" 2>&1; then + printf 'Closed input must fail instead of looping.\n' >&2 + exit 1 +fi + +if [ "$summary" = true ]; then printf 'Tests: PASS | plugin selection\n'; else printf 'PASS plugin toggle selection: toggle and confirm\n'; fi