9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
367 lines
16 KiB
Bash
367 lines
16 KiB
Bash
#!/bin/bash
|
||
# Tests for ods.ps1 enable/disable command parity (issue #1699)
|
||
# These are hermetic shell tests that exercise the PowerShell logic
|
||
# by stubbing the filesystem layout and verifying file state changes.
|
||
# They do NOT require Docker, PowerShell, or a live Windows install.
|
||
#
|
||
# Run: bash ods/tests/test-windows-cli-enable-disable.sh
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
ODS_PS1="$ROOT_DIR/installers/windows/ods.ps1"
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
pass() { echo -e "${GREEN}✓${NC} $1"; }
|
||
fail() { echo -e "${RED}✗${NC} $1"; exit 1; }
|
||
info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
||
|
||
# ── Static checks (no PowerShell needed) ─────────────────────────────────────
|
||
|
||
info "Static: ods.ps1 exists and is non-empty"
|
||
[[ -f "$ODS_PS1" ]] || fail "ods.ps1 not found at $ODS_PS1"
|
||
[[ -s "$ODS_PS1" ]] || fail "ods.ps1 is empty"
|
||
pass "ods.ps1 exists"
|
||
|
||
info "Static: header comment documents enable/disable"
|
||
grep -q 'enable.*Enable an extension' "$ODS_PS1" \
|
||
|| fail "Header comment missing 'enable' usage line"
|
||
grep -q 'disable.*Disable an extension' "$ODS_PS1" \
|
||
|| fail "Header comment missing 'disable' usage line"
|
||
pass "Header comment documents enable and disable"
|
||
|
||
info "Static: Invoke-Enable function defined"
|
||
grep -q 'function Invoke-Enable' "$ODS_PS1" \
|
||
|| fail "Invoke-Enable function not found in ods.ps1"
|
||
pass "Invoke-Enable function present"
|
||
|
||
info "Static: Invoke-Disable function defined"
|
||
grep -q 'function Invoke-Disable' "$ODS_PS1" \
|
||
|| fail "Invoke-Disable function not found in ods.ps1"
|
||
pass "Invoke-Disable function present"
|
||
|
||
info "Static: Test-ODSInstallFiles helper defined (Docker-free validation)"
|
||
grep -q 'function Test-ODSInstallFiles' "$ODS_PS1" \
|
||
|| fail "Test-ODSInstallFiles not found -- enable/disable will require Docker"
|
||
pass "Test-ODSInstallFiles helper present"
|
||
|
||
info "Static: Invoke-Enable uses Test-ODSInstallFiles, not Test-Install"
|
||
# Extract the Invoke-Enable function body and assert it doesn't call Test-Install
|
||
awk '/^function Invoke-Enable/,/^}/' "$ODS_PS1" \
|
||
| grep -q 'Test-ODSInstallFiles' \
|
||
|| fail "Invoke-Enable does not call Test-ODSInstallFiles"
|
||
awk '/^function Invoke-Enable/,/^}/' "$ODS_PS1" \
|
||
| grep -qv 'Test-Install[^F]' \
|
||
|| fail "Invoke-Enable still calls Test-Install (requires Docker)"
|
||
pass "Invoke-Enable uses Docker-free Test-ODSInstallFiles"
|
||
|
||
info "Static: Invoke-Disable uses Test-ODSInstallFiles, not Test-Install"
|
||
awk '/^function Invoke-Disable/,/^}/' "$ODS_PS1" \
|
||
| grep -q 'Test-ODSInstallFiles' \
|
||
|| fail "Invoke-Disable does not call Test-ODSInstallFiles"
|
||
pass "Invoke-Disable uses Docker-free Test-ODSInstallFiles"
|
||
|
||
info "Static: Invoke-Disable still attempts docker stop when Docker is running"
|
||
grep -q 'docker compose.*stop' "$ODS_PS1" \
|
||
|| fail "Invoke-Disable does not attempt docker stop"
|
||
pass "Invoke-Disable attempts docker stop when Docker is available"
|
||
|
||
info "Static: Invoke-Disable gracefully skips stop when Docker is offline"
|
||
grep -q 'Docker Desktop is not running.*skipping container stop' "$ODS_PS1" \
|
||
|| fail "Invoke-Disable does not have a Docker-offline warning"
|
||
pass "Invoke-Disable gracefully skips stop when Docker is offline"
|
||
|
||
info "Static: Rename+flags update runs regardless of Docker state in Invoke-Disable"
|
||
# The Rename-Item call must appear AFTER the docker-offline else branch
|
||
awk '/^function Invoke-Disable/,/^}/' "$ODS_PS1" \
|
||
| grep -q 'Rename-Item.*compose.yaml.disabled' \
|
||
|| fail "Rename-Item not found in Invoke-Disable body"
|
||
pass "Rename + flags update unconditional in Invoke-Disable"
|
||
|
||
info "Static: Update-ComposeFlags delegates to resolve-compose-stack.sh when available"
|
||
grep -q 'resolve-compose-stack.sh' "$ODS_PS1" \
|
||
|| fail "Update-ComposeFlags does not reference resolve-compose-stack.sh"
|
||
pass "Update-ComposeFlags references resolve-compose-stack.sh"
|
||
|
||
info "Static: Update-ComposeFlags has safe fallback when resolver not available"
|
||
grep -q 'fallback minimal swap\|fallback' "$ODS_PS1" \
|
||
|| fail "Update-ComposeFlags has no fallback path"
|
||
pass "Update-ComposeFlags has fallback path"
|
||
|
||
info "Static: Update-ComposeFlags preserves --env-file in resolver path"
|
||
grep -q 'env-file.*resolver\|--env-file .env.*newContent\|existingRaw.*--env-file' "$ODS_PS1" \
|
||
|| grep -q "Prepend --env-file" "$ODS_PS1" \
|
||
|| fail "Update-ComposeFlags does not preserve --env-file when using the resolver"
|
||
pass "Update-ComposeFlags preserves --env-file in resolver output"
|
||
|
||
info "Static: Update-ComposeFlags passes GPU_BACKEND to resolver"
|
||
grep -q 'gpuBackend\|gpu-backend' "$ODS_PS1" \
|
||
|| fail "Update-ComposeFlags does not pass GPU_BACKEND to resolver"
|
||
pass "Update-ComposeFlags passes GPU_BACKEND to resolver"
|
||
|
||
info "Static: Update-ComposeFlags fallback preserves backend overlay -f entries"
|
||
# The fallback keeps everything that doesn't match extensions/services
|
||
grep -q "extensions\[/\\\\\\\\\]services" "$ODS_PS1" \
|
||
|| grep -q 'extensions.*services' "$ODS_PS1" \
|
||
|| fail "Update-ComposeFlags fallback does not filter on extensions/services path"
|
||
pass "Update-ComposeFlags fallback preserves non-extension -f entries (backend overlays)"
|
||
|
||
info "Static: Update-ComposeFlags helper defined"
|
||
grep -q 'function Update-ComposeFlags' "$ODS_PS1" \
|
||
|| fail "Update-ComposeFlags not found in ods.ps1"
|
||
pass "Update-ComposeFlags helper present"
|
||
|
||
info "Static: Get-ExtensionServiceDir helper defined"
|
||
grep -q 'function Get-ExtensionServiceDir' "$ODS_PS1" \
|
||
|| fail "Get-ExtensionServiceDir not found in ods.ps1"
|
||
pass "Get-ExtensionServiceDir helper present"
|
||
|
||
info "Static: Get-ExtensionCategory helper defined"
|
||
grep -q 'function Get-ExtensionCategory' "$ODS_PS1" \
|
||
|| fail "Get-ExtensionCategory not found in ods.ps1"
|
||
pass "Get-ExtensionCategory helper present"
|
||
|
||
info "Static: command dispatcher wires 'enable'"
|
||
grep -q '"enable".*Invoke-Enable' "$ODS_PS1" \
|
||
|| fail "Dispatcher does not call Invoke-Enable for 'enable'"
|
||
pass "Dispatcher wires 'enable' -> Invoke-Enable"
|
||
|
||
info "Static: command dispatcher wires 'disable'"
|
||
grep -q '"disable".*Invoke-Disable' "$ODS_PS1" \
|
||
|| fail "Dispatcher does not call Invoke-Disable for 'disable'"
|
||
pass "Dispatcher wires 'disable' -> Invoke-Disable"
|
||
|
||
info "Static: Show-Help lists enable command"
|
||
grep -q 'enable.*service.*Enable an extension' "$ODS_PS1" \
|
||
|| fail "Show-Help does not list 'enable <service>'"
|
||
pass "Show-Help lists enable command"
|
||
|
||
info "Static: Show-Help lists disable command"
|
||
grep -q 'disable.*service.*Disable an extension' "$ODS_PS1" \
|
||
|| fail "Show-Help does not list 'disable <service>'"
|
||
pass "Show-Help lists disable command"
|
||
|
||
info "Static: Show-Help EXAMPLES mention enable"
|
||
grep -q 'enable comfyui' "$ODS_PS1" \
|
||
|| fail "Show-Help EXAMPLES do not include 'enable comfyui'"
|
||
pass "Show-Help EXAMPLES include enable comfyui"
|
||
|
||
info "Static: Show-Help EXAMPLES mention disable"
|
||
grep -q 'disable langfuse' "$ODS_PS1" \
|
||
|| fail "Show-Help EXAMPLES do not include 'disable langfuse'"
|
||
pass "Show-Help EXAMPLES include disable langfuse"
|
||
|
||
info "Static: Invoke-Enable handles already-enabled case"
|
||
grep -q 'already enabled' "$ODS_PS1" \
|
||
|| fail "Invoke-Enable does not handle already-enabled case"
|
||
pass "Invoke-Enable handles already-enabled case"
|
||
|
||
info "Static: Invoke-Disable handles already-disabled case"
|
||
grep -q 'already disabled' "$ODS_PS1" \
|
||
|| fail "Invoke-Disable does not handle already-disabled case"
|
||
pass "Invoke-Disable handles already-disabled case"
|
||
|
||
info "Static: core service guard in Invoke-Enable"
|
||
grep -q 'core.*always enabled' "$ODS_PS1" \
|
||
|| fail "Invoke-Enable does not guard core services"
|
||
pass "Invoke-Enable guards core services"
|
||
|
||
info "Static: core service guard in Invoke-Disable"
|
||
grep -q 'Cannot disable core service' "$ODS_PS1" \
|
||
|| fail "Invoke-Disable does not guard core services"
|
||
pass "Invoke-Disable guards core services"
|
||
|
||
# ── Filesystem simulation tests ───────────────────────────────────────────────
|
||
|
||
TMP="$(mktemp -d)"
|
||
trap 'rm -rf "$TMP"' EXIT
|
||
|
||
INSTALL_DIR="$TMP/ods"
|
||
EXT_SVC="$INSTALL_DIR/extensions/services"
|
||
mkdir -p "$EXT_SVC"
|
||
|
||
# ── simulate_update_compose_flags_fallback FLAGS_FILE EXT_DIR INSTALL_DIR
|
||
# Mirrors the PowerShell fallback path in bash.
|
||
simulate_update_compose_flags_fallback() {
|
||
local flags_file="$1"
|
||
local ext_dir="$2"
|
||
local install_dir="$3"
|
||
local existing
|
||
existing=$(cat "$flags_file")
|
||
local base_flags=()
|
||
local skip_next=false
|
||
local token
|
||
for token in $existing; do
|
||
if $skip_next; then skip_next=false; continue; fi
|
||
if [[ "$token" == "-f" ]]; then
|
||
base_flags+=("$token")
|
||
continue
|
||
fi
|
||
if [[ "$token" == *"extensions/services/"* ]]; then
|
||
# Remove the preceding -f we already added
|
||
unset 'base_flags[-1]'
|
||
continue
|
||
fi
|
||
base_flags+=("$token")
|
||
done
|
||
# Re-append only compose.yaml for enabled extensions
|
||
local svc_dir cf rel
|
||
for svc_dir in "$ext_dir"/*; do
|
||
[[ -d "$svc_dir" ]] || continue
|
||
cf="$svc_dir/compose.yaml"
|
||
if [[ -f "$cf" ]]; then
|
||
rel="${cf#$install_dir/}"
|
||
base_flags+=("-f" "$rel")
|
||
fi
|
||
done
|
||
printf '%s' "${base_flags[*]}" > "$flags_file"
|
||
}
|
||
|
||
# ── Test: backend overlays are preserved when an extension is toggled ─────────
|
||
info "Filesystem: backend overlay preserved when extension is enabled"
|
||
mkdir -p "$EXT_SVC/comfyui" "$EXT_SVC/langfuse"
|
||
touch "$EXT_SVC/comfyui/compose.yaml.disabled" # disabled initially
|
||
# langfuse also disabled
|
||
|
||
# .compose-flags as installer would write it: includes nvidia backend overlay
|
||
# but does NOT include comfyui (it was disabled at install time)
|
||
cat > "$INSTALL_DIR/.compose-flags" <<'EOF'
|
||
--env-file .env -f docker-compose.base.yml -f docker-compose.nvidia.yml
|
||
EOF
|
||
|
||
# Simulate enable: rename .disabled -> compose.yaml, then rebuild
|
||
mv "$EXT_SVC/comfyui/compose.yaml.disabled" "$EXT_SVC/comfyui/compose.yaml"
|
||
simulate_update_compose_flags_fallback "$INSTALL_DIR/.compose-flags" "$EXT_SVC" "$INSTALL_DIR"
|
||
|
||
result=$(cat "$INSTALL_DIR/.compose-flags")
|
||
# Backend overlays must still be present
|
||
echo "$result" | grep -q 'docker-compose.nvidia.yml' \
|
||
|| fail "Backend overlay (docker-compose.nvidia.yml) was dropped after enable"
|
||
echo "$result" | grep -q 'docker-compose.base.yml' \
|
||
|| fail "Base compose (docker-compose.base.yml) was dropped after enable"
|
||
# The newly enabled extension must appear
|
||
echo "$result" | grep -q 'extensions/services/comfyui/compose.yaml' \
|
||
|| fail "Enabled comfyui compose.yaml not found in flags after enable"
|
||
# Disabled langfuse must NOT appear
|
||
echo "$result" | grep -q 'langfuse' \
|
||
&& fail "Disabled langfuse appeared in flags after enable of comfyui"
|
||
# --env-file must survive
|
||
echo "$result" | grep -q -- '--env-file' \
|
||
|| fail "--env-file was dropped after enable"
|
||
pass "Backend overlay, base compose, and --env-file preserved after enable"
|
||
|
||
# ── Test: .compose-flags with AMD backend overlay preserved on disable ─────────
|
||
info "Filesystem: AMD backend overlay preserved when extension is disabled"
|
||
T2="$TMP/amd_install"
|
||
EXT_SVC2="$T2/extensions/services"
|
||
mkdir -p "$EXT_SVC2/comfyui" "$EXT_SVC2/langfuse"
|
||
touch "$EXT_SVC2/comfyui/compose.yaml" # comfyui enabled
|
||
touch "$EXT_SVC2/langfuse/compose.yaml" # langfuse enabled
|
||
|
||
# AMD-style .compose-flags with per-extension GPU overlay entries
|
||
cat > "$T2/.compose-flags" <<'EOF'
|
||
--env-file .env -f docker-compose.base.yml -f docker-compose.amd.yml -f extensions/services/comfyui/compose.yaml -f extensions/services/langfuse/compose.yaml
|
||
EOF
|
||
|
||
# Simulate disable langfuse: rename, then rebuild
|
||
mv "$EXT_SVC2/langfuse/compose.yaml" "$EXT_SVC2/langfuse/compose.yaml.disabled"
|
||
simulate_update_compose_flags_fallback "$T2/.compose-flags" "$EXT_SVC2" "$T2"
|
||
|
||
result2=$(cat "$T2/.compose-flags")
|
||
echo "$result2" | grep -q 'docker-compose.amd.yml' \
|
||
|| fail "AMD backend overlay was dropped after disable"
|
||
echo "$result2" | grep -q 'docker-compose.base.yml' \
|
||
|| fail "Base compose was dropped after disable"
|
||
echo "$result2" | grep -q 'extensions/services/comfyui/compose.yaml' \
|
||
|| fail "comfyui (still enabled) disappeared after disabling langfuse"
|
||
echo "$result2" | grep -q 'langfuse' \
|
||
&& fail "Disabled langfuse still present in flags after disable"
|
||
pass "AMD backend overlay preserved and disabled extension removed correctly"
|
||
|
||
# ── Test: Docker-offline enable path works (file-only operation) ──────────────
|
||
info "Filesystem: enable works when Docker is offline (file-only operation)"
|
||
T3="$TMP/offline_enable"
|
||
EXT_SVC3="$T3/extensions/services"
|
||
mkdir -p "$EXT_SVC3/comfyui"
|
||
touch "$T3/docker-compose.base.yml"
|
||
touch "$T3/docker-compose.nvidia.yml"
|
||
touch "$EXT_SVC3/comfyui/compose.yaml.disabled"
|
||
|
||
cat > "$T3/.compose-flags" <<'EOF'
|
||
--env-file .env -f docker-compose.base.yml -f docker-compose.nvidia.yml
|
||
EOF
|
||
|
||
# Simulate enable (rename only -- no docker call needed)
|
||
mv "$EXT_SVC3/comfyui/compose.yaml.disabled" "$EXT_SVC3/comfyui/compose.yaml"
|
||
simulate_update_compose_flags_fallback "$T3/.compose-flags" "$EXT_SVC3" "$T3"
|
||
|
||
[[ -f "$EXT_SVC3/comfyui/compose.yaml" ]] \
|
||
|| fail "compose.yaml not present after offline enable"
|
||
[[ ! -f "$EXT_SVC3/comfyui/compose.yaml.disabled" ]] \
|
||
|| fail "compose.yaml.disabled still exists after offline enable"
|
||
result3=$(cat "$T3/.compose-flags")
|
||
echo "$result3" | grep -q 'docker-compose.nvidia.yml' \
|
||
|| fail "Backend overlay dropped in offline enable"
|
||
echo "$result3" | grep -q 'extensions/services/comfyui/compose.yaml' \
|
||
|| fail "Extension not added to flags in offline enable"
|
||
pass "Enable succeeds offline: compose renamed, flags updated, no Docker needed"
|
||
|
||
# ── Test: Docker-offline disable path still renames and updates flags ──────────
|
||
info "Filesystem: disable completes when Docker is offline (rename still happens)"
|
||
T4="$TMP/offline_disable"
|
||
EXT_SVC4="$T4/extensions/services"
|
||
mkdir -p "$EXT_SVC4/comfyui"
|
||
touch "$T4/docker-compose.base.yml"
|
||
touch "$T4/docker-compose.nvidia.yml"
|
||
touch "$EXT_SVC4/comfyui/compose.yaml"
|
||
|
||
cat > "$T4/.compose-flags" <<'EOF'
|
||
--env-file .env -f docker-compose.base.yml -f docker-compose.nvidia.yml -f extensions/services/comfyui/compose.yaml
|
||
EOF
|
||
|
||
# Simulate disable when Docker is offline: docker stop is skipped, rename still runs
|
||
# (PowerShell: the else branch prints a warning but Rename-Item still executes)
|
||
DOCKER_AVAILABLE=false # simulate Docker offline
|
||
if $DOCKER_AVAILABLE; then
|
||
: # would run docker compose stop here
|
||
else
|
||
: # warning printed; rename still happens
|
||
fi
|
||
mv "$EXT_SVC4/comfyui/compose.yaml" "$EXT_SVC4/comfyui/compose.yaml.disabled"
|
||
simulate_update_compose_flags_fallback "$T4/.compose-flags" "$EXT_SVC4" "$T4"
|
||
|
||
[[ -f "$EXT_SVC4/comfyui/compose.yaml.disabled" ]] \
|
||
|| fail "compose.yaml.disabled not created in offline disable"
|
||
[[ ! -f "$EXT_SVC4/comfyui/compose.yaml" ]] \
|
||
|| fail "compose.yaml still present after offline disable"
|
||
result4=$(cat "$T4/.compose-flags")
|
||
echo "$result4" | grep -q 'docker-compose.nvidia.yml' \
|
||
|| fail "Backend overlay dropped in offline disable"
|
||
echo "$result4" | grep -q 'comfyui' \
|
||
&& fail "Disabled comfyui still in flags after offline disable"
|
||
pass "Disable completes offline: compose renamed, flags updated, no Docker needed"
|
||
|
||
# ── Basic rename sanity tests ──────────────────────────────────────────────────
|
||
info "Filesystem: enable renames compose.yaml.disabled -> compose.yaml"
|
||
SVCDIR="$TMP/newext_test"
|
||
mkdir -p "$SVCDIR"
|
||
touch "$SVCDIR/compose.yaml.disabled"
|
||
mv "$SVCDIR/compose.yaml.disabled" "$SVCDIR/compose.yaml"
|
||
[[ -f "$SVCDIR/compose.yaml" ]] || fail "compose.yaml not created after enable"
|
||
[[ ! -f "$SVCDIR/compose.yaml.disabled" ]] || fail "compose.yaml.disabled still exists after enable"
|
||
pass "Enable renames compose.yaml.disabled to compose.yaml"
|
||
|
||
info "Filesystem: disable renames compose.yaml -> compose.yaml.disabled"
|
||
mv "$SVCDIR/compose.yaml" "$SVCDIR/compose.yaml.disabled"
|
||
[[ -f "$SVCDIR/compose.yaml.disabled" ]] || fail "compose.yaml.disabled not created after disable"
|
||
[[ ! -f "$SVCDIR/compose.yaml" ]] || fail "compose.yaml still exists after disable"
|
||
pass "Disable renames compose.yaml to compose.yaml.disabled"
|
||
|
||
echo ""
|
||
echo -e "${GREEN}All windows-cli-enable-disable tests passed.${NC}"
|