WinGetLookup.Tests.ps1

#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' }

<#
.SYNOPSIS
    Pester v5 tests for the WinGetLookup PowerShell module.
 
.DESCRIPTION
    Comprehensive test suite for the WinGetLookup module including:
    - Unit tests with mocked API responses
    - Parameter validation tests
    - Error handling tests
    - Integration tests (optional, requires internet)
 
.NOTES
    Author: Ringo
    Version: 1.7.0
    Date: December 27, 2025
     
    Requires: Pester v5.0.0 or higher
     
    Run all tests:
        Invoke-Pester -Path .\WinGetLookup.Tests.ps1
     
    Run with detailed output:
        Invoke-Pester -Path .\WinGetLookup.Tests.ps1 -Output Detailed
     
    Run with code coverage:
        Invoke-Pester -Path .\WinGetLookup.Tests.ps1 -CodeCoverage .\WinGetLookup.psm1
     
    Run only unit tests (exclude integration):
        Invoke-Pester -Path .\WinGetLookup.Tests.ps1 -ExcludeTag 'Integration'
     
    Run only integration tests:
        Invoke-Pester -Path .\WinGetLookup.Tests.ps1 -Tag 'Integration'
#>


BeforeAll {
    # Import the module before all tests
    $ModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'WinGetLookup.psd1'
    Import-Module $ModulePath -Force -ErrorAction Stop
    
    # Define mock responses for API calls
    $Script:MockFoundResponse = '{"Packages":[{"Id":"Test.Package","Versions":["1.0"],"Latest":{"Name":"Test Package","Publisher":"Test Publisher","Description":"Test Description","Homepage":"https://test.com","License":"MIT","Tags":["test"]}}],"Total":1}'
    $Script:MockNotFoundResponse = '{"Packages":[],"Total":0}'
    $Script:MockPackageInfoResponse = '{"Packages":[{"Id":"7zip.7zip","Versions":["22.01","21.07"],"Latest":{"Name":"7-Zip","Publisher":"Igor Pavlov","Description":"File archiver","Homepage":"https://7-zip.org","License":"LGPL","Tags":["archiver","compression"]}}],"Total":1}'
    $Script:Mock64BitPackageResponse = '{"Packages":[{"Id":"Adobe.Acrobat.Reader.64-bit","Versions":["24.001"],"Latest":{"Name":"Adobe Acrobat Reader 64-bit","Publisher":"Adobe","Description":"PDF Reader","Homepage":"https://adobe.com","License":"Proprietary","Tags":["pdf","reader","64-bit"]}}],"Total":1}'
    $Script:MockNo64BitPackageResponse = '{"Packages":[{"Id":"SomeApp.App","Versions":["1.0"],"Latest":{"Name":"Some App","Publisher":"Some Publisher","Description":"An application","Homepage":"https://example.com","License":"MIT","Tags":["app"]}}],"Total":1}'
    
    # Mock responses for smart matching tests
    $Script:MockMultiplePackagesResponse = '{"Packages":[{"Id":"TTYPlus.MTPutty","Versions":["1.8"],"Latest":{"Name":"MTPuTTY","Publisher":"TTYPlus","Description":"Multi-Tabbed PuTTY","Homepage":"https://ttyplus.com","License":"Freeware","Tags":["ssh","terminal"]}},{"Id":"PuTTY.PuTTY","Versions":["0.80"],"Latest":{"Name":"PuTTY","Publisher":"Simon Tatham","Description":"SSH and telnet client","Homepage":"https://putty.org","License":"MIT","Tags":["ssh","telnet"]}},{"Id":"9XCODE.ExtraPuTTY","Versions":["1.0"],"Latest":{"Name":"ExtraPuTTY","Publisher":"9XCODE","Description":"Enhanced PuTTY","Homepage":"https://example.com","License":"MIT","Tags":["ssh"]}}],"Total":3}'
    $Script:MockPublisherMatchResponse = '{"Packages":[{"Id":"Test.WrongApp","Versions":["1.0"],"Latest":{"Name":"Test App","Publisher":"Wrong Publisher","Description":"Wrong app","Homepage":"https://wrong.com","License":"MIT","Tags":[]}},{"Id":"Test.CorrectApp","Versions":["1.0"],"Latest":{"Name":"Test App","Publisher":"Correct Publisher","Description":"Correct app","Homepage":"https://correct.com","License":"MIT","Tags":[]}}],"Total":2}'
}

AfterAll {
    # Clean up - remove the module after all tests
    Remove-Module WinGetLookup -Force -ErrorAction SilentlyContinue
}

Describe 'WinGetLookup Module' {
    Context 'Module Import' {
        It 'Should import the module without errors' {
            { Import-Module (Join-Path $PSScriptRoot 'WinGetLookup.psd1') -Force } | Should -Not -Throw
        }

        It 'Should export Test-WinGetPackage function' {
            Get-Command -Module WinGetLookup -Name 'Test-WinGetPackage' | Should -Not -BeNullOrEmpty
        }

        It 'Should export Get-WinGetPackageInfo function' {
            Get-Command -Module WinGetLookup -Name 'Get-WinGetPackageInfo' | Should -Not -BeNullOrEmpty
        }

        It 'Should export exactly 7 functions' {
            (Get-Command -Module WinGetLookup).Count | Should -Be 7
        }

        It 'Should export Clear-WinGetCache function' {
            Get-Command -Module WinGetLookup -Name 'Clear-WinGetCache' | Should -Not -BeNullOrEmpty
        }

        It 'Should export Get-WinGetCacheStatistics function' {
            Get-Command -Module WinGetLookup -Name 'Get-WinGetCacheStatistics' | Should -Not -BeNullOrEmpty
        }

        It 'Should export Test-WinGet64BitAvailable function' {
            Get-Command -Module WinGetLookup -Name 'Test-WinGet64BitAvailable' | Should -Not -BeNullOrEmpty
        }

        It 'Should export Get-WinGet64BitPackageId function' {
            Get-Command -Module WinGetLookup -Name 'Get-WinGet64BitPackageId' | Should -Not -BeNullOrEmpty
        }

        It 'Should export Initialize-WinGetPackageCache function' {
            Get-Command -Module WinGetLookup -Name 'Initialize-WinGetPackageCache' | Should -Not -BeNullOrEmpty
        }

        It 'Should have a valid module manifest' {
            $manifest = Test-ModuleManifest -Path (Join-Path $PSScriptRoot 'WinGetLookup.psd1')
            $manifest | Should -Not -BeNullOrEmpty
        }

        It 'Should have version 1.7.0' {
            $manifest = Test-ModuleManifest -Path (Join-Path $PSScriptRoot 'WinGetLookup.psd1')
            $manifest.Version | Should -Be '1.7.0'
        }

        It 'Should have an author specified' {
            $manifest = Test-ModuleManifest -Path (Join-Path $PSScriptRoot 'WinGetLookup.psd1')
            $manifest.Author | Should -Not -BeNullOrEmpty
        }

        It 'Should have a description' {
            $manifest = Test-ModuleManifest -Path (Join-Path $PSScriptRoot 'WinGetLookup.psd1')
            $manifest.Description | Should -Not -BeNullOrEmpty
        }
    }
}

Describe 'Cache Functions' {
    BeforeEach {
        # Clear cache before each test
        Clear-WinGetCache
    }

    Context 'Get-WinGetCacheStatistics' {
        It 'Should return a PSCustomObject' {
            $stats = Get-WinGetCacheStatistics
            $stats | Should -BeOfType [PSCustomObject]
        }

        It 'Should have CachedEntries property' {
            $stats = Get-WinGetCacheStatistics
            $stats.PSObject.Properties.Name | Should -Contain 'CachedEntries'
        }

        It 'Should have CacheHits property' {
            $stats = Get-WinGetCacheStatistics
            $stats.PSObject.Properties.Name | Should -Contain 'CacheHits'
        }

        It 'Should have CacheMisses property' {
            $stats = Get-WinGetCacheStatistics
            $stats.PSObject.Properties.Name | Should -Contain 'CacheMisses'
        }

        It 'Should have EfficiencyPct property' {
            $stats = Get-WinGetCacheStatistics
            $stats.PSObject.Properties.Name | Should -Contain 'EfficiencyPct'
        }

        It 'Should start with zero entries after clear' {
            $stats = Get-WinGetCacheStatistics
            $stats.CachedEntries | Should -Be 0
            $stats.CacheHits | Should -Be 0
            $stats.CacheMisses | Should -Be 0
        }
    }

    Context 'Clear-WinGetCache' {
        It 'Should not throw' {
            { Clear-WinGetCache } | Should -Not -Throw
        }

        It 'Should reset cache statistics' {
            # Make a mock call to populate cache
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
            Test-WinGetPackage -DisplayName 'TestApp'
            
            # Verify cache has entry
            $statsBefore = Get-WinGetCacheStatistics
            $statsBefore.CacheMisses | Should -BeGreaterThan 0
            
            # Clear and verify
            Clear-WinGetCache
            $statsAfter = Get-WinGetCacheStatistics
            $statsAfter.CachedEntries | Should -Be 0
            $statsAfter.CacheHits | Should -Be 0
            $statsAfter.CacheMisses | Should -Be 0
        }
    }

    Context 'Caching Behavior' {
        It 'Should cache API response and use it on second call' {
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
            
            # First call - should be cache miss
            Test-WinGetPackage -DisplayName 'TestApp'
            $stats1 = Get-WinGetCacheStatistics
            $stats1.CacheMisses | Should -Be 1
            $stats1.CacheHits | Should -Be 0
            
            # Second call with same term - should be cache hit
            Test-WinGetPackage -DisplayName 'TestApp'
            $stats2 = Get-WinGetCacheStatistics
            $stats2.CacheMisses | Should -Be 1
            $stats2.CacheHits | Should -Be 1
        }

        It 'Should have cache hit on case-insensitive search' {
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
            
            Test-WinGetPackage -DisplayName 'TestApp'
            Test-WinGetPackage -DisplayName 'testapp'  # lowercase
            
            $stats = Get-WinGetCacheStatistics
            $stats.CacheHits | Should -Be 1
        }

        It 'Should calculate efficiency percentage correctly' {
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
            
            # 1 miss, then 3 hits = 75% efficiency
            Test-WinGetPackage -DisplayName 'TestApp'
            Test-WinGetPackage -DisplayName 'TestApp'
            Test-WinGetPackage -DisplayName 'TestApp'
            Test-WinGetPackage -DisplayName 'TestApp'
            
            $stats = Get-WinGetCacheStatistics
            $stats.EfficiencyPct | Should -Be 75
        }
    }
}

Describe 'Test-WinGetPackage' {
    Context 'Parameter Validation' {
        It 'Should have mandatory DisplayName parameter' {
            $param = (Get-Command Test-WinGetPackage).Parameters['DisplayName']
            $mandatory = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
            $mandatory.Mandatory | Should -Be $true
        }

        It 'Should accept DisplayName as positional parameter (position 0)' {
            $param = (Get-Command Test-WinGetPackage).Parameters['DisplayName']
            $posAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
            $posAttr.Position | Should -Be 0
        }

        It 'Should accept pipeline input for DisplayName' {
            $param = (Get-Command Test-WinGetPackage).Parameters['DisplayName']
            $pipeAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
            $pipeAttr.ValueFromPipeline | Should -Be $true
        }

        It 'Should have Name alias for DisplayName' {
            $param = (Get-Command Test-WinGetPackage).Parameters['DisplayName']
            $param.Aliases | Should -Contain 'Name'
        }

        It 'Should have AppName alias for DisplayName' {
            $param = (Get-Command Test-WinGetPackage).Parameters['DisplayName']
            $param.Aliases | Should -Contain 'AppName'
        }

        It 'Should have ApplicationName alias for DisplayName' {
            $param = (Get-Command Test-WinGetPackage).Parameters['DisplayName']
            $param.Aliases | Should -Contain 'ApplicationName'
        }

        It 'Should have TimeoutSeconds parameter' {
            (Get-Command Test-WinGetPackage).Parameters['TimeoutSeconds'] | Should -Not -BeNullOrEmpty
        }

        It 'Should have Require64Bit parameter' {
            (Get-Command Test-WinGetPackage).Parameters['Require64Bit'] | Should -Not -BeNullOrEmpty
        }

        It 'Should have Require64Bit as switch parameter' {
            $param = (Get-Command Test-WinGetPackage).Parameters['Require64Bit']
            $param.ParameterType.Name | Should -Be 'SwitchParameter'
        }

        It 'Should have Publisher parameter' {
            (Get-Command Test-WinGetPackage).Parameters['Publisher'] | Should -Not -BeNullOrEmpty
        }

        It 'Should have Vendor alias for Publisher' {
            $param = (Get-Command Test-WinGetPackage).Parameters['Publisher']
            $param.Aliases | Should -Contain 'Vendor'
        }

        It 'Should have Author alias for Publisher' {
            $param = (Get-Command Test-WinGetPackage).Parameters['Publisher']
            $param.Aliases | Should -Contain 'Author'
        }

        It 'Should have PackageId parameter' {
            (Get-Command Test-WinGetPackage).Parameters['PackageId'] | Should -Not -BeNullOrEmpty
        }

        It 'Should have Id alias for PackageId' {
            $param = (Get-Command Test-WinGetPackage).Parameters['PackageId']
            $param.Aliases | Should -Contain 'Id'
        }

        It 'Should have WinGetId alias for PackageId' {
            $param = (Get-Command Test-WinGetPackage).Parameters['PackageId']
            $param.Aliases | Should -Contain 'WinGetId'
        }

        It 'Should validate TimeoutSeconds minimum range is 5' {
            $param = (Get-Command Test-WinGetPackage).Parameters['TimeoutSeconds']
            $validateRange = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] }
            $validateRange.MinRange | Should -Be 5
        }

        It 'Should validate TimeoutSeconds maximum range is 300' {
            $param = (Get-Command Test-WinGetPackage).Parameters['TimeoutSeconds']
            $validateRange = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] }
            $validateRange.MaxRange | Should -Be 300
        }

        It 'Should throw when DisplayName is null' {
            { Test-WinGetPackage -DisplayName $null } | Should -Throw
        }

        It 'Should throw when DisplayName is empty string' {
            { Test-WinGetPackage -DisplayName '' } | Should -Throw
        }

        It 'Should throw when TimeoutSeconds is below minimum (1)' {
            { Test-WinGetPackage -DisplayName 'Test' -TimeoutSeconds 1 } | Should -Throw
        }

        It 'Should throw when TimeoutSeconds is above maximum (500)' {
            { Test-WinGetPackage -DisplayName 'Test' -TimeoutSeconds 500 } | Should -Throw
        }
    }

    Context 'Return Values with Mocked API' {
        It 'Should return 1 when package is found' {
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'TestPackage'
            $result | Should -Be 1
        }

        It 'Should return 0 when package is not found' {
            Mock Invoke-RestMethod { return $Script:MockNotFoundResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'NonExistentPackage12345'
            $result | Should -Be 0
        }

        It 'Should return 0 when API throws an exception' {
            Clear-WinGetCache  # Ensure no cached data
            Mock Invoke-RestMethod { throw 'Network error' } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'UniqueFailTest12345' -WarningAction SilentlyContinue
            $result | Should -Be 0
        }

        It 'Should return integer type' {
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'TestPackage'
            $result | Should -BeOfType [int]
        }

        It 'Should only return 0 or 1' {
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'TestPackage'
            $result | Should -BeIn @(0, 1)
        }
    }

    Context 'Require64Bit Parameter with Mocked API' {
        It 'Should return 1 when package has 64-bit in ID' {
            Mock Invoke-RestMethod { return $Script:Mock64BitPackageResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'Adobe Acrobat Reader' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 0 when package has no 64-bit indicators and -Require64Bit is specified' {
            Mock Invoke-RestMethod { return $Script:MockNo64BitPackageResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'SomeApp' -Require64Bit
            $result | Should -Be 0
        }

        It 'Should return 1 for known 64-bit package (7zip.7zip) with -Require64Bit' {
            Mock Invoke-RestMethod { return $Script:MockPackageInfoResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName '7-Zip' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 1 without -Require64Bit even when package has no 64-bit indicators' {
            Mock Invoke-RestMethod { return $Script:MockNo64BitPackageResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'SomeApp'
            $result | Should -Be 1
        }

        It 'Should return 0 when package not found, regardless of -Require64Bit' {
            Mock Invoke-RestMethod { return $Script:MockNotFoundResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'NonExistent' -Require64Bit
            $result | Should -Be 0
        }

        It 'Should detect 64-bit from package name containing x64' {
            $mockX64Response = '{"Packages":[{"Id":"Test.App","Versions":["1.0"],"Latest":{"Name":"Test App x64","Publisher":"Test","Description":"Test","Homepage":"https://test.com","License":"MIT","Tags":["test"]}}],"Total":1}'
            Mock Invoke-RestMethod { return $mockX64Response } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'Test App' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should detect 64-bit from tags containing 64-bit' {
            $mockTaggedResponse = '{"Packages":[{"Id":"Test.App","Versions":["1.0"],"Latest":{"Name":"Test App","Publisher":"Test","Description":"Test","Homepage":"https://test.com","License":"MIT","Tags":["test","64-bit"]}}],"Total":1}'
            Mock Invoke-RestMethod { return $mockTaggedResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'Test App' -Require64Bit
            $result | Should -Be 1
        }
    }

    Context 'Smart Matching with Mocked API' {
        It 'Should select PuTTY.PuTTY when searching for PuTTY (best match scoring)' {
            Mock Invoke-RestMethod { return $Script:MockMultiplePackagesResponse } -ModuleName WinGetLookup
            
            # The smart matching should pick PuTTY.PuTTY over MTPuTTY
            $result = Test-WinGetPackage -DisplayName 'PuTTY'
            $result | Should -Be 1
        }

        It 'Should return 1 when using -PackageId for exact match' {
            Mock Invoke-RestMethod { return $Script:MockMultiplePackagesResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'PuTTY' -PackageId 'PuTTY.PuTTY'
            $result | Should -Be 1
        }

        It 'Should return 0 when -PackageId does not match any package' {
            Mock Invoke-RestMethod { return $Script:MockMultiplePackagesResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'PuTTY' -PackageId 'NonExistent.Package'
            $result | Should -Be 0
        }

        It 'Should filter by publisher when -Publisher is specified' {
            Mock Invoke-RestMethod { return $Script:MockMultiplePackagesResponse } -ModuleName WinGetLookup
            
            $result = Test-WinGetPackage -DisplayName 'PuTTY' -Publisher 'Simon Tatham'
            $result | Should -Be 1
        }

        It 'Should prefer publisher match over other matches' {
            Mock Invoke-RestMethod { return $Script:MockPublisherMatchResponse } -ModuleName WinGetLookup
            
            # Without publisher filter, first match might be wrong
            # With publisher filter, should find correct one
            $result = Test-WinGetPackage -DisplayName 'Test App' -Publisher 'Correct Publisher'
            $result | Should -Be 1
        }
    }

    Context 'URL Encoding' {
        It 'Should call API when DisplayName contains special characters' {
            Mock Invoke-RestMethod { return $Script:MockNotFoundResponse } -ModuleName WinGetLookup
            
            Test-WinGetPackage -DisplayName 'Notepad++'
            Should -Invoke Invoke-RestMethod -ModuleName WinGetLookup -Times 1
        }
    }

    Context 'Pipeline Support' {
        BeforeEach {
            Mock Invoke-RestMethod { return $Script:MockFoundResponse } -ModuleName WinGetLookup
        }

        It 'Should accept pipeline input' {
            $result = 'TestApp' | Test-WinGetPackage
            $result | Should -Be 1
        }

        It 'Should process multiple pipeline inputs' {
            $results = @('App1', 'App2', 'App3') | ForEach-Object { Test-WinGetPackage $_ }
            $results.Count | Should -Be 3
        }

        It 'Should return correct count for each input' {
            $results = @('App1', 'App2') | ForEach-Object { Test-WinGetPackage $_ }
            $results | Should -HaveCount 2
        }
    }

    Context 'Verbose Output' {
        It 'Should produce verbose output when -Verbose is specified' {
            Mock Invoke-RestMethod { return $Script:MockNotFoundResponse } -ModuleName WinGetLookup
            
            $verboseOutput = Test-WinGetPackage -DisplayName 'TestApp' -Verbose 4>&1
            $verboseOutput | Should -Not -BeNullOrEmpty
        }

        It 'Should include package name in verbose output' {
            Mock Invoke-RestMethod { return $Script:MockNotFoundResponse } -ModuleName WinGetLookup
            
            $verboseOutput = Test-WinGetPackage -DisplayName 'MyTestApp' -Verbose 4>&1
            ($verboseOutput | Out-String) | Should -Match 'MyTestApp'
        }
    }
}

Describe 'Get-WinGetPackageInfo' {
    Context 'Parameter Validation' {
        It 'Should have mandatory DisplayName parameter' {
            $param = (Get-Command Get-WinGetPackageInfo).Parameters['DisplayName']
            $mandatory = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
            $mandatory.Mandatory | Should -Be $true
        }

        It 'Should accept DisplayName as positional parameter (position 0)' {
            $param = (Get-Command Get-WinGetPackageInfo).Parameters['DisplayName']
            $posAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
            $posAttr.Position | Should -Be 0
        }

        It 'Should accept pipeline input for DisplayName' {
            $param = (Get-Command Get-WinGetPackageInfo).Parameters['DisplayName']
            $pipeAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
            $pipeAttr.ValueFromPipeline | Should -Be $true
        }

        It 'Should have TimeoutSeconds parameter' {
            (Get-Command Get-WinGetPackageInfo).Parameters['TimeoutSeconds'] | Should -Not -BeNullOrEmpty
        }

        It 'Should have Publisher parameter' {
            (Get-Command Get-WinGetPackageInfo).Parameters['Publisher'] | Should -Not -BeNullOrEmpty
        }

        It 'Should have PackageId parameter' {
            (Get-Command Get-WinGetPackageInfo).Parameters['PackageId'] | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Return Values with Mocked API' {
        It 'Should return PSCustomObject when package is found' {
            Mock Invoke-RestMethod { return $Script:MockPackageInfoResponse } -ModuleName WinGetLookup
            
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result | Should -BeOfType [PSCustomObject]
        }

        It 'Should return object with Found=True when package exists' {
            Mock Invoke-RestMethod { return $Script:MockPackageInfoResponse } -ModuleName WinGetLookup
            
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.Found | Should -Be $true
        }

        It 'Should return object with Id when package exists' {
            Mock Invoke-RestMethod { return $Script:MockPackageInfoResponse } -ModuleName WinGetLookup
            
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.Id | Should -Not -BeNullOrEmpty
        }

        It 'Should return object with Name when package exists' {
            Mock Invoke-RestMethod { return $Script:MockPackageInfoResponse } -ModuleName WinGetLookup
            
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.Name | Should -Not -BeNullOrEmpty
        }

        It 'Should return object with Found=False when package not found' {
            Mock Invoke-RestMethod { return $Script:MockNotFoundResponse } -ModuleName WinGetLookup
            
            $result = Get-WinGetPackageInfo -DisplayName 'NonExistentApp'
            $result.Found | Should -Be $false
        }

        It 'Should return Found=False when API throws an exception' {
            Clear-WinGetCache  # Ensure no cached data
            Mock Invoke-RestMethod { throw 'Network error' } -ModuleName WinGetLookup
            
            $result = Get-WinGetPackageInfo -DisplayName 'UniqueFailTest67890' -WarningAction SilentlyContinue
            $result.Found | Should -Be $false
        }
    }

    Context 'Return Object Properties' {
        BeforeEach {
            Mock Invoke-RestMethod { return $Script:MockPackageInfoResponse } -ModuleName WinGetLookup
        }

        It 'Should have Id property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'Id'
        }

        It 'Should have Name property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'Name'
        }

        It 'Should have Publisher property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'Publisher'
        }

        It 'Should have Description property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'Description'
        }

        It 'Should have Homepage property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'Homepage'
        }

        It 'Should have License property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'License'
        }

        It 'Should have Found property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'Found'
        }

        It 'Should have Has64BitIndicator property' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.PSObject.Properties.Name | Should -Contain 'Has64BitIndicator'
        }

        It 'Has64BitIndicator should be boolean' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.Has64BitIndicator | Should -BeOfType [bool]
        }
    }
}

Describe 'Test-WinGet64BitAvailable' {
    Context 'Parameter Validation' {
        It 'Should have PackageId as mandatory parameter' {
            $param = (Get-Command Test-WinGet64BitAvailable).Parameters['PackageId']
            $param.Attributes.Where({ $_ -is [System.Management.Automation.ParameterAttribute] }).Mandatory | Should -Be $true
        }

        It 'Should accept pipeline input for PackageId' {
            $param = (Get-Command Test-WinGet64BitAvailable).Parameters['PackageId']
            $param.Attributes.Where({ $_ -is [System.Management.Automation.ParameterAttribute] }).ValueFromPipeline | Should -Be $true
        }

        It 'Should have TimeoutSeconds parameter' {
            (Get-Command Test-WinGet64BitAvailable).Parameters['TimeoutSeconds'] | Should -Not -BeNullOrEmpty
        }

        It 'Should have Id alias for PackageId' {
            $param = (Get-Command Test-WinGet64BitAvailable).Parameters['PackageId']
            $param.Aliases | Should -Contain 'Id'
        }
    }

    Context 'CLI-based 64-bit Detection' -Tag 'Integration' {
        It 'Should return true for known 64-bit package (7zip.7zip)' {
            $result = Test-WinGet64BitAvailable -PackageId '7zip.7zip'
            $result | Should -Be $true
        }

        It 'Should return true for WinRAR (CLI verification catches what heuristics miss)' {
            $result = Test-WinGet64BitAvailable -PackageId 'RARLab.WinRAR'
            $result | Should -Be $true
        }

        It 'Should return false for 32-bit only package' {
            $result = Test-WinGet64BitAvailable -PackageId 'Adobe.Acrobat.Reader.32-bit'
            $result | Should -Be $false
        }

        It 'Should return false for non-existent package' {
            $result = Test-WinGet64BitAvailable -PackageId 'NonExistent.FakePackage.12345'
            $result | Should -Be $false
        }

        It 'Should return boolean type' {
            $result = Test-WinGet64BitAvailable -PackageId '7zip.7zip'
            $result | Should -BeOfType [bool]
        }
    }
}

Describe 'Get-WinGet64BitPackageId' {
    Context 'Parameter Validation' {
        It 'Should have PackageId as mandatory parameter' {
            $param = (Get-Command Get-WinGet64BitPackageId).Parameters['PackageId']
            $param.Attributes.Where({ $_ -is [System.Management.Automation.ParameterAttribute] }).Mandatory | Should -Be $true
        }

        It 'Should accept pipeline input for PackageId' {
            $param = (Get-Command Get-WinGet64BitPackageId).Parameters['PackageId']
            $param.Attributes.Where({ $_ -is [System.Management.Automation.ParameterAttribute] }).ValueFromPipeline | Should -Be $true
        }

        It 'Should have Id alias for PackageId' {
            $param = (Get-Command Get-WinGet64BitPackageId).Parameters['PackageId']
            $param.Aliases | Should -Contain 'Id'
        }
    }

    Context 'Separate 64-bit Package Detection' -Tag 'Integration' {
        It 'Should return 64-bit variant for Adobe.Acrobat.Reader.32-bit' {
            $result = Get-WinGet64BitPackageId -PackageId 'Adobe.Acrobat.Reader.32-bit'
            $result | Should -Be 'Adobe.Acrobat.Reader.64-bit'
        }

        It 'Should return same ID for packages with x64 installer (7zip)' {
            $result = Get-WinGet64BitPackageId -PackageId '7zip.7zip'
            $result | Should -Be '7zip.7zip'
        }

        It 'Should return same ID for packages with x64 installer (WinRAR)' {
            $result = Get-WinGet64BitPackageId -PackageId 'RARLab.WinRAR'
            $result | Should -Be 'RARLab.WinRAR'
        }

        It 'Should return null for non-existent package' {
            $result = Get-WinGet64BitPackageId -PackageId 'NonExistent.FakePackage.12345'
            $result | Should -BeNullOrEmpty
        }

        It 'Should return string type for valid package' {
            $result = Get-WinGet64BitPackageId -PackageId '7zip.7zip'
            $result | Should -BeOfType [string]
        }
    }
}

Describe 'Help Documentation' {
    Context 'Test-WinGetPackage Help' {
        BeforeAll {
            $script:help = Get-Help Test-WinGetPackage -Full
        }

        It 'Should have a synopsis' {
            $script:help.Synopsis | Should -Not -BeNullOrEmpty
        }

        It 'Should have a description' {
            $script:help.Description | Should -Not -BeNullOrEmpty
        }

        It 'Should have parameter documentation for DisplayName' {
            $paramHelp = $script:help.Parameters.Parameter | Where-Object { $_.Name -eq 'DisplayName' }
            $paramHelp.Description | Should -Not -BeNullOrEmpty
        }

        It 'Should have parameter documentation for TimeoutSeconds' {
            $paramHelp = $script:help.Parameters.Parameter | Where-Object { $_.Name -eq 'TimeoutSeconds' }
            $paramHelp.Description | Should -Not -BeNullOrEmpty
        }

        It 'Should have at least 2 examples' {
            $script:help.Examples.Example.Count | Should -BeGreaterOrEqual 2
        }

        It 'Should have notes section' {
            $script:help.alertSet | Should -Not -BeNullOrEmpty
        }

        It 'Should have related links' {
            $script:help.relatedLinks | Should -Not -BeNullOrEmpty
        }

        It 'Should document output type' {
            $script:help.returnValues | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Get-WinGetPackageInfo Help' {
        BeforeAll {
            $script:help = Get-Help Get-WinGetPackageInfo -Full
        }

        It 'Should have a synopsis' {
            $script:help.Synopsis | Should -Not -BeNullOrEmpty
        }

        It 'Should have a description' {
            $script:help.Description | Should -Not -BeNullOrEmpty
        }

        It 'Should have examples' {
            $script:help.Examples.Example | Should -Not -BeNullOrEmpty
        }

        It 'Should have parameter documentation for DisplayName' {
            $paramHelp = $script:help.Parameters.Parameter | Where-Object { $_.Name -eq 'DisplayName' }
            $paramHelp.Description | Should -Not -BeNullOrEmpty
        }
    }
}

Describe 'Integration Tests' -Tag 'Integration' {
    # These tests require internet connectivity and hit the live API
    # Run with: Invoke-Pester -Path .\WinGetLookup.Tests.ps1 -Tag 'Integration'
    
    Context 'Live API - Test-WinGetPackage' {
        It 'Should find Notepad++ in WinGet repository' {
            $result = Test-WinGetPackage -DisplayName 'Notepad++'
            $result | Should -Be 1
        }

        It 'Should find 7-Zip in WinGet repository' {
            $result = Test-WinGetPackage -DisplayName '7-Zip'
            $result | Should -Be 1
        }

        It 'Should find VLC in WinGet repository' {
            $result = Test-WinGetPackage -DisplayName 'VLC'
            $result | Should -Be 1
        }

        It 'Should not find a non-existent package' {
            $result = Test-WinGetPackage -DisplayName 'ThisPackageDefinitelyDoesNotExist123456789xyz'
            $result | Should -Be 0
        }

        It 'Should work with -Name alias' {
            $result = Test-WinGetPackage -Name 'Firefox'
            $result | Should -Be 1
        }
    }

    Context 'Live API - Get-WinGetPackageInfo' {
        It 'Should return detailed info for 7-Zip' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip'
            $result.Found | Should -Be $true
            $result.Id | Should -Match '7zip'
            $result.Publisher | Should -Not -BeNullOrEmpty
        }

        It 'Should return detailed info for VLC' {
            $result = Get-WinGetPackageInfo -DisplayName 'VLC'
            $result.Found | Should -Be $true
            $result.Name | Should -Match 'VLC'
        }

        It 'Should return Found=False for non-existent package' {
            $result = Get-WinGetPackageInfo -DisplayName 'NonExistentApp987654321'
            $result.Found | Should -Be $false
        }
    }

    Context 'Live API - Require64Bit Tests' {
        It 'Should return 1 for 7-Zip with -Require64Bit (known 64-bit package)' {
            $result = Test-WinGetPackage -DisplayName '7-Zip' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 1 for Notepad++ with -Require64Bit (known 64-bit package)' {
            $result = Test-WinGetPackage -DisplayName 'Notepad++' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 1 for VLC with -Require64Bit (known 64-bit package)' {
            $result = Test-WinGetPackage -DisplayName 'VLC' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 0 for non-existent package with -Require64Bit' {
            $result = Test-WinGetPackage -DisplayName 'ThisPackageDefinitelyDoesNotExist123456789xyz' -Require64Bit
            $result | Should -Be 0
        }

        It 'Should return 1 for Visual Studio Code with -Require64Bit (known 64-bit package)' {
            $result = Test-WinGetPackage -DisplayName 'Visual Studio Code' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 1 for Git with -Require64Bit (known 64-bit package)' {
            $result = Test-WinGetPackage -DisplayName 'Git' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 1 for WinSCP with -Require64Bit (known 64-bit package)' {
            $result = Test-WinGetPackage -DisplayName 'WinSCP' -Require64Bit
            $result | Should -Be 1
        }

        It 'Should return 1 for Bitwarden with -Require64Bit (known 64-bit package)' {
            $result = Test-WinGetPackage -DisplayName 'Bitwarden' -Require64Bit
            $result | Should -Be 1
        }
    }

    Context 'Live API - Smart Matching Tests' {
        It 'Should find PuTTY.PuTTY when searching for PuTTY (not MTPuTTY)' {
            $result = Get-WinGetPackageInfo -DisplayName 'PuTTY'
            $result.Found | Should -Be $true
            $result.Id | Should -Be 'PuTTY.PuTTY'
        }

        It 'Should find PuTTY by Simon Tatham when using -Publisher' {
            $result = Get-WinGetPackageInfo -DisplayName 'PuTTY' -Publisher 'Simon Tatham'
            $result.Found | Should -Be $true
            $result.Id | Should -Be 'PuTTY.PuTTY'
            $result.Publisher | Should -Match 'Simon Tatham'
        }

        It 'Should find exact package when using -PackageId' {
            $result = Get-WinGetPackageInfo -DisplayName 'PuTTY' -PackageId 'PuTTY.PuTTY'
            $result.Found | Should -Be $true
            $result.Id | Should -Be 'PuTTY.PuTTY'
        }

        It 'Should return Found=False when -PackageId does not match' {
            $result = Get-WinGetPackageInfo -DisplayName 'PuTTY' -PackageId 'NonExistent.Package'
            $result.Found | Should -Be $false
        }

        It 'Should find 7-Zip by Igor Pavlov when using -Publisher' {
            $result = Get-WinGetPackageInfo -DisplayName '7-Zip' -Publisher 'Igor Pavlov'
            $result.Found | Should -Be $true
            $result.Id | Should -Match '7zip'
        }

        It 'Should work with Test-WinGetPackage and -Publisher' {
            $result = Test-WinGetPackage -DisplayName 'PuTTY' -Publisher 'Simon Tatham'
            $result | Should -Be 1
        }

        It 'Should work with Test-WinGetPackage and -PackageId' {
            $result = Test-WinGetPackage -DisplayName 'PuTTY' -PackageId 'PuTTY.PuTTY'
            $result | Should -Be 1
        }
    }
}