tests/Undo.Tests.ps1

$moduleRoot = Split-Path -Parent $PSScriptRoot
Import-Module (Join-Path $moduleRoot 'Octa.psd1') -Force
. (Join-Path $moduleRoot 'private\ActionHelpers.ps1')
. (Join-Path $moduleRoot 'private\StateSnapshot.ps1')
. (Join-Path $moduleRoot 'private\ElevationCheck.ps1')

# Get-WindowsOptionalFeature -Online requires elevation even to read.
$isElevated = Test-OctaElevation

Describe 'Save-OctaActionSnapshot / Restore-OctaActionSnapshot round-trip (registry)' {
    $testKey = 'HKCU:\Software\OctaPesterTest'
    $tempFolder = Join-Path $env:TEMP ('octa-undo-test-{0}' -f [guid]::NewGuid())
    New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null
    New-Item -Path $testKey -Force | Out-Null
    Set-ItemProperty -Path $testKey -Name 'Marker' -Value 1 -Type DWord

    It 'snapshots the prior value, then restores it after a change' {
        $action = New-OctaAction -TargetType Registry -TargetIdentifier "$testKey|Marker" `
            -CurrentValue 1 -PlannedValue 0 -Reversible $true

        $snapshot = Save-OctaActionSnapshot -Action $action -RunFolder $tempFolder
        $snapshot | Should Not Be $null

        Set-ItemProperty -Path $testKey -Name 'Marker' -Value 0 -Type DWord
        (Get-ItemProperty -Path $testKey -Name 'Marker').Marker | Should Be 0

        Restore-OctaActionSnapshot -Snapshot $snapshot -RunFolder $tempFolder
        (Get-ItemProperty -Path $testKey -Name 'Marker').Marker | Should Be 1
    }

    Remove-Item -Path $testKey -Recurse -Force -ErrorAction SilentlyContinue
    Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue
}

Describe 'Save-OctaActionSnapshot / Restore-OctaActionSnapshot round-trip (key created from scratch)' {
    $testKey = 'HKCU:\Software\OctaPesterTestNewKey'
    $tempFolder = Join-Path $env:TEMP ('octa-undo-test-{0}' -f [guid]::NewGuid())
    New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null
    Remove-Item -Path $testKey -Recurse -Force -ErrorAction SilentlyContinue

    It 'restores a key that did not exist before applying back to absent' {
        $action = New-OctaAction -TargetType Registry -TargetIdentifier "$testKey|Marker" `
            -CurrentValue $null -PlannedValue 1 -Reversible $true

        $snapshot = Save-OctaActionSnapshot -Action $action -RunFolder $tempFolder
        $snapshot | Should Not Be $null
        $snapshot.Type | Should Be 'RegistryKeyAbsent'

        New-Item -Path $testKey -Force | Out-Null
        Set-ItemProperty -Path $testKey -Name 'Marker' -Value 1 -Type DWord
        (Test-Path $testKey) | Should Be $true

        Restore-OctaActionSnapshot -Snapshot $snapshot -RunFolder $tempFolder
        (Test-Path $testKey) | Should Be $false
    }

    Remove-Item -Path $testKey -Recurse -Force -ErrorAction SilentlyContinue
    Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue
}

Describe 'Save-OctaActionSnapshot / Restore-OctaActionSnapshot round-trip (value created under an existing key)' {
    # Real bug found via VM validation (008): the key existed but the specific value did not
    # yet - `reg import` only adds/overwrites values present in the exported .reg, it never
    # deletes a value absent from it, so a value Octa creates from scratch under a pre-existing
    # key used to survive "undo" silently (Undo-OctaRun still reported Success/Restored).
    $testKey = 'HKCU:\Software\OctaPesterTestExistingKey'
    $tempFolder = Join-Path $env:TEMP ('octa-undo-test-{0}' -f [guid]::NewGuid())
    New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null
    New-Item -Path $testKey -Force | Out-Null
    Remove-ItemProperty -Path $testKey -Name 'NewMarker' -ErrorAction SilentlyContinue

    It 'deletes a value that did not exist before applying, even though the key did' {
        $action = New-OctaAction -TargetType Registry -TargetIdentifier "$testKey|NewMarker" `
            -CurrentValue $null -PlannedValue 1 -Reversible $true

        $snapshot = Save-OctaActionSnapshot -Action $action -RunFolder $tempFolder
        $snapshot | Should Not Be $null
        $snapshot.Type | Should Be 'Registry'
        $snapshot.ValueExisted | Should Be $false

        Set-ItemProperty -Path $testKey -Name 'NewMarker' -Value 1 -Type DWord
        (Get-ItemProperty -Path $testKey -Name 'NewMarker').NewMarker | Should Be 1

        Restore-OctaActionSnapshot -Snapshot $snapshot -RunFolder $tempFolder
        (Get-ItemProperty -Path $testKey -Name 'NewMarker' -ErrorAction SilentlyContinue) | Should Be $null
        (Test-Path $testKey) | Should Be $true
    }

    Remove-Item -Path $testKey -Recurse -Force -ErrorAction SilentlyContinue
    Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue
}

Describe 'Save-OctaOptionalFeatureState' {
    It 'captures a real Windows optional feature state without throwing or mutating it' {
        if (-not $isElevated) { return }
        # Real Enable/Disable-WindowsOptionalFeature round-trip needs elevation and is slow
        # (DISM) - deferred to the disposable VM per standing project practice, same as every
        # other elevated mutation. This only verifies the snapshot shape against real state.
        $state = Save-OctaOptionalFeatureState -FeatureName 'Microsoft-Windows-Subsystem-Linux'
        $state | Should Not Be $null
        $state.Type | Should Be 'OptionalFeature'
        $state.PriorState | Should Not BeNullOrEmpty
    }

    It 'returns $null for a feature name that does not exist' {
        if (-not $isElevated) { return }
        $state = Save-OctaOptionalFeatureState -FeatureName 'NotARealFeatureName-Octa-Test'
        $state | Should Be $null
    }
}

Describe 'Undo-OctaRun' {
    It 'reports NotFound for a run id that does not exist' {
        $result = Undo-OctaRun -RunId 'this-run-id-does-not-exist'
        $result.Status | Should Be 'NotFound'
    }
}