Testing/Unit/PowerShell/Support/Test-CyberAssessmentVersion.Tests.ps1

Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '../../../../Modules/Support')

InModuleScope 'Support' {
    Describe "Test-CyberAssessmentVersion" {
        BeforeAll {
            # Load the RequiredVersions.ps1 file
            $RequiredVersionsPath = Join-Path -Path $PSScriptRoot -ChildPath "..\..\..\..\RequiredVersions.ps1"
            if (Test-Path -Path $RequiredVersionsPath) {
                . $RequiredVersionsPath
                # Load the CyberAssessment module list to reference later
                $script:CyberAssessmentModuleList = $ModuleList
            } else {
                throw "Could not find RequiredVersions.ps1 at expected path: $RequiredVersionsPath"
            }

            # Load the CyberAssessment version from the manifest
            $ManifestPath = Join-Path -Path $PSScriptRoot -ChildPath "..\..\..\..\CyberAssessment.psd1"
            if (Test-Path -Path $ManifestPath) {
                $manifestData = Import-PowerShellDataFile -Path $ManifestPath
                $script:CurrentCyberAssessmentVersion = [version]$manifestData.ModuleVersion
            } else {
                throw "Could not find CyberAssessment.psd1 at expected path: $ManifestPath"
            }

            # for GitHub container
            if (-not (Get-Command -Name 'Get-DependencyStatus' -ErrorAction SilentlyContinue)) {
                function script:Get-DependencyStatus { }
            }

            $script:MockDependencyModules = @{}
            foreach ($module in $script:CyberAssessmentModuleList) {
                # Create a mock version that's within the acceptable range
                $mockVersion = $module.ModuleVersion
                if ($mockVersion -lt $module.MaximumVersion) {
                    # Use a version slightly higher than minimum but within range
                    $mockVersion = [version]"$($module.ModuleVersion.Major).$($module.ModuleVersion.Minor + 1).0"
                }

                $script:MockDependencyModules[$module.ModuleName] = @(
                    @{
                        Name = $module.ModuleName
                        Version = $mockVersion
                        ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\$($module.ModuleName)\$mockVersion"
                    }
                )
            }

            # Create mock CyberAssessment module using actual version
            $script:MockCyberAssessmentModule = @{
                Name = 'CyberAssessment'
                Version = $script:CurrentCyberAssessmentVersion
                ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\CyberAssessment\$($script:CurrentCyberAssessmentVersion)"
            }

            # Simulate a newer version for update testing using CyberAssessment's versioning pattern
            # CyberAssessment uses versions like 1.4.0, 1.5.0, 1.6.0, so increment minor version
            $script:MockNewerCyberAssessmentVersion = [version]"$($script:CurrentCyberAssessmentVersion.Major).$($script:CurrentCyberAssessmentVersion.Minor + 1).0"

            # Create an older version using CyberAssessment's versioning pattern
            # For current version 1.6.0, older would be 1.5.0
            if ($script:CurrentCyberAssessmentVersion.Minor -eq 0) {
                # If we're at X.0.0, go to (X-1).9.0 (though this is unlikely for CyberAssessment)
                $script:MockOlderCyberAssessmentVersion = [version]"$($script:CurrentCyberAssessmentVersion.Major - 1).9.0"
            } else {
                # Normal case: 1.6.0 -> 1.5.0
                $script:MockOlderCyberAssessmentVersion = [version]"$($script:CurrentCyberAssessmentVersion.Major).$($script:CurrentCyberAssessmentVersion.Minor - 1).0"
            }

            # Set up default mocks at BeforeAll level to ensure they're available
            Mock Get-DependencyStatus {
                return [PSCustomObject]@{
                    TotalRequired = $script:CyberAssessmentModuleList.Count
                    Installed = $script:CyberAssessmentModuleList.Count
                    Missing = @()
                    MultipleVersions = @()
                    ModuleFileLocations = @()
                    AdminRequired = $false
                    Status = "Optimal"
                    Recommendations = @("All dependencies are installed.")
                }
            }

            Mock Write-Information { }
            Mock Invoke-RestMethod { }

            # Mock Find-Module (this will work regardless of whether PowerShellGet is loaded)
            Mock Find-Module {
                param($Name)
                $null = $Name  # Satisfy PSScriptAnalyzer
                if ($Name -eq 'CyberAssessment') {
                    return @{ Version = $script:MockNewerCyberAssessmentVersion }
                }
                return $null
            }
        }
        BeforeEach {
            # Mock Find-Module (this will work regardless of whether PowerShellGet is loaded)
            Mock Find-Module {
                param($Name)
                $null = $Name  # Satisfy PSScriptAnalyzer
                if ($Name -eq 'CyberAssessment') {
                    return @{ Version = $script:MockNewerCyberAssessmentVersion }
                }
                return $null
            }

            Mock Get-DependencyStatus {
                return [PSCustomObject]@{
                    TotalRequired = $script:CyberAssessmentModuleList.Count
                    Installed = $script:CyberAssessmentModuleList.Count
                    Missing = @()
                    MultipleVersions = @()
                    ModuleFileLocations = @()
                    AdminRequired = $false
                    Status = "Optimal"
                    Recommendations = @("All dependencies are installed.")
                }
            }

            Mock Write-Information { }
            Mock Invoke-RestMethod { }
        }

        Context "When CyberAssessment is not installed" {
            It "Should report CyberAssessment as not installed" {
                Mock Get-Module {
                    param($Name, $ListAvailable)
                    $null = $Name, $ListAvailable  # Satisfy PSScriptAnalyzer
                    return $null
                }

                $result = Test-CyberAssessmentVersion

                $result[0].Status | Should -Be "Not Installed"
                $result[0].Recommendations | Should -Match "Install-Module CyberAssessment"
            }
        }

        Context "When CyberAssessment is up to date" {
            It "Should report CyberAssessment as up to date using actual current version" {
                Mock Get-Module {
                    param($Name, $ListAvailable)
                    $null = $Name, $ListAvailable  # Satisfy PSScriptAnalyzer
                    if ($Name -eq 'CyberAssessment' -and $ListAvailable) {
                        # Return a single module as an array
                        return [PSCustomObject]@{
                            Version = $script:CurrentCyberAssessmentVersion
                            ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\CyberAssessment\$($script:CurrentCyberAssessmentVersion)"
                        }
                    }
                    return $null
                }
                Mock Find-Module {
                    param($Name, $Repository)
                    $null = $Name, $Repository  # Satisfy PSScriptAnalyzer
                    if ($Name -eq 'CyberAssessment') {
                        return @{ Version = $script:CurrentCyberAssessmentVersion }
                    }
                    return $null
                }

                $result = Test-CyberAssessmentVersion

                $result[0].Status | Should -Be "Up to Date"
                $result[0].CurrentVersion | Should -Be $script:CurrentCyberAssessmentVersion
                $result[0].LatestVersion | Should -Be $script:CurrentCyberAssessmentVersion
                $result[0].CurrentVersion | Should -Be $script:CurrentCyberAssessmentVersion
                $result[0].LatestVersion | Should -Be $script:CurrentCyberAssessmentVersion
            }
        }

        Context "When CyberAssessment needs update" {
            It "Should detect when update is needed using real version numbers" {
                Mock Get-Module {
                    param($Name, $ListAvailable)
                    $null = $Name, $ListAvailable  # Satisfy PSScriptAnalyzer
                    if ($Name -eq 'CyberAssessment' -and $ListAvailable) {
                        return @{
                            Version = $script:CurrentCyberAssessmentVersion
                            ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\CyberAssessment\$($script:CurrentCyberAssessmentVersion)"
                        }
                    }
                    return $null
                }
                Mock Find-Module {
                    param($Name, $Repository)
                    $null = $Name, $Repository  # Satisfy PSScriptAnalyzer
                    if ($Name -eq 'CyberAssessment') {
                        return @{ Version = $script:MockNewerCyberAssessmentVersion }
                    }
                    return $null
                }

                $result = Test-CyberAssessmentVersion

                $result[0].CurrentVersion | Should -Be $script:CurrentCyberAssessmentVersion
                $result[0].LatestVersion | Should -Be $script:MockNewerCyberAssessmentVersion
                $result[0].Recommendations | Should -Match "Update-CyberAssessment"
            }
        }

        Context "When CyberAssessment has multiple versions" {
            It "Should detect multiple versions and return highest version as current" {
                # The function sorts by Version -Descending and selects the first (highest) version
                # as the current version, even when multiple versions are installed
                Mock Get-Module {
                    param($Name, $ListAvailable)
                    $null = $Name, $ListAvailable  # Satisfy PSScriptAnalyzer
                    if ($Name -eq 'CyberAssessment' -and $ListAvailable) {
                        # Put older version first in array, but function will find highest
                        return @(
                            @{
                                Version = $script:MockOlderCyberAssessmentVersion
                                ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\CyberAssessment\$($script:MockOlderCyberAssessmentVersion)"
                            },
                            @{
                                Version = $script:CurrentCyberAssessmentVersion
                                ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\CyberAssessment\$($script:CurrentCyberAssessmentVersion)"
                            }
                        )
                    }
                    return $null
                }

                $result = Test-CyberAssessmentVersion

                $result[0].MultipleVersionsInstalled | Should -Be $true
                # Function returns highest version (1.6.0) as current, not first in array
                $result[0].CurrentVersion | Should -Be $script:CurrentCyberAssessmentVersion
                # Check for the actual recommendation text that mentions versions
                $result[0].Recommendations | Should -Match "versions installed"
            }
        }

        Context "When dependencies have multiple versions" {
            It "Should report multiple versions for real modules" {
                # Use first two modules from actual requirements for testing
                $firstModule = $script:CyberAssessmentModuleList[0]
                $secondModule = $script:CyberAssessmentModuleList[1]

                # Add Get-Module mock to return CyberAssessment module
                Mock Get-Module {
                    param($Name, $ListAvailable)
                    if ($Name -eq 'CyberAssessment' -and $ListAvailable) {
                        return @{
                            Version = $script:CurrentCyberAssessmentVersion
                            ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\CyberAssessment\$($script:CurrentCyberAssessmentVersion)"
                        }
                    }
                    return $null
                }

                Mock Get-DependencyStatus {
                    return [PSCustomObject]@{
                        TotalRequired = $script:CyberAssessmentModuleList.Count
                        Installed = $script:CyberAssessmentModuleList.Count
                        Missing = @()
                        MultipleVersions = @($firstModule.ModuleName, $secondModule.ModuleName)
                        ModuleFileLocations = @(
                            [PSCustomObject]@{
                                ModuleName = $firstModule.ModuleName
                                VersionCount = 2
                                MinVersion = $firstModule.ModuleVersion.ToString()
                                MaxVersion = $firstModule.MaximumVersion.ToString()
                                Locations = @(
                                    "$($firstModule.ModuleVersion) [OK] (CurrentUser): $env:USERPROFILE\Documents\PowerShell\Modules\$($firstModule.ModuleName)\$($firstModule.ModuleVersion)",
                                    "$($firstModule.ModuleVersion.Major).$($firstModule.ModuleVersion.Minor + 1).0 [OK] (CurrentUser): $env:USERPROFILE\Documents\PowerShell\Modules\$($firstModule.ModuleName)\$($firstModule.ModuleVersion.Major).$($firstModule.ModuleVersion.Minor + 1).0"
                                )
                            },
                            [PSCustomObject]@{
                                ModuleName = $secondModule.ModuleName
                                VersionCount = 2
                                MinVersion = $secondModule.ModuleVersion.ToString()
                                MaxVersion = $secondModule.MaximumVersion.ToString()
                                Locations = @(
                                    "$($secondModule.ModuleVersion) [OK] (AllUsers): $env:ProgramFiles\PowerShell\Modules\$($secondModule.ModuleName)\$($secondModule.ModuleVersion)",
                                    "$($secondModule.ModuleVersion.Major).$($secondModule.ModuleVersion.Minor + 1).0 [OK] (CurrentUser): $env:USERPROFILE\Documents\PowerShell\Modules\$($secondModule.ModuleName)\$($secondModule.ModuleVersion.Major).$($secondModule.ModuleVersion.Minor + 1).0"
                                )
                            }
                        )
                        AdminRequired = $true
                        Status = "Needs Cleanup"
                        Recommendations = @("2 modules have multiple versions installed. Run 'Reset-CyberAssessmentDependencies' to clean up.")
                    }
                }

                $result = Test-CyberAssessmentVersion

                # Test against the dependency component (second element in array)
                $result[1].MultipleVersionsInstalled | Should -Be $true
                $result[1].MultipleVersionModules | Should -Contain $firstModule.ModuleName
                $result[1].MultipleVersionModules | Should -Contain $secondModule.ModuleName
                $result[1].ModuleFileLocations.Count | Should -Be 2
            }
        }

        Context "GitHub version check with real version" {
            It "Should attempt GitHub version check and compare with real version" {
                # Use CyberAssessment versioning pattern for GitHub mock
                $mockGitHubVersion = [version]"$($script:CurrentCyberAssessmentVersion.Major).$($script:CurrentCyberAssessmentVersion.Minor + 1).0"

                # Override the default mock specifically for this test
                Mock Invoke-RestMethod {
                    return @{ tag_name = "v$mockGitHubVersion" }
                } -ParameterFilter { $Uri -like "*github.com*" }

                # Also mock a successful Get-Module call to ensure CyberAssessment is "installed"
                Mock Get-Module {
                    param($Name, $ListAvailable)
                    $null = $Name, $ListAvailable  # Satisfy PSScriptAnalyzer
                    if ($Name -eq 'CyberAssessment' -and $ListAvailable) {
                        return @{
                            Version = $script:CurrentCyberAssessmentVersion
                            ModuleBase = "$env:USERPROFILE\Documents\PowerShell\Modules\CyberAssessment\$($script:CurrentCyberAssessmentVersion)"
                        }
                    }
                    return $null
                }

                $result = Test-CyberAssessmentVersion -CheckGitHub

                # Should have attempted to check GitHub
                Assert-MockCalled Invoke-RestMethod -Times 1 -ParameterFilter { $Uri -like "*github.com*" }
                $result[0].CurrentVersion | Should -Be $script:CurrentCyberAssessmentVersion
            }
        }

        Context "Parameter validation" {
            It "Should accept CheckGitHub switch parameter" {
                { Test-CyberAssessmentVersion -CheckGitHub } | Should -Not -Throw
            }

            It "Should work without parameters" {
                { Test-CyberAssessmentVersion } | Should -Not -Throw
            }
        }
    }
}