Tests/Core.Tests.ps1

#Requires -Module Pester
<#
.SYNOPSIS
    Pester v5 tests for core LicenseGuard module functions.
#>


BeforeAll {
    $modulePath = Join-Path (Split-Path $PSScriptRoot) 'LicenseGuard.psm1'
    Import-Module $modulePath -Force -ErrorAction Stop
}

AfterAll {
    Remove-Module LicenseGuard -ErrorAction SilentlyContinue
}

# ──────────────────────────────────────────────────────────────────────────────
Describe 'Initialize-LicenseGuard' {
    It 'sets module strings to English' {
        Initialize-LicenseGuard -Language en
        $strings = & (Get-Module LicenseGuard) { Get-LGEffectiveStrings }
        $strings['allowed'] | Should -Be 'COMPLIANT'
    }

    It 'sets module strings to Turkish' {
        Initialize-LicenseGuard -Language tr
        $strings = & (Get-Module LicenseGuard) { Get-LGEffectiveStrings }
        $strings['allowed'] | Should -Be 'UYUMLU'
    }

    It 'loads config defaults when config file does not exist' {
        Initialize-LicenseGuard -ConfigPath 'C:\DoesNotExist\lg-config.json'
        $cfg = & (Get-Module LicenseGuard) { Get-LGEffectiveConfig }
        $cfg.WarnDaysBeforeExpiry | Should -Be 30
    }
}

# ──────────────────────────────────────────────────────────────────────────────
Describe 'Get-LGWindowsActivation' {
    Context 'Licensed machine' {
        BeforeEach {
            Mock Get-CimInstance {
                [PSCustomObject]@{
                    LicenseStatus        = 1
                    GracePeriodRemaining = 0
                    Name                 = 'Windows(R) Operating System, VOLUME_KMSCLIENT channel'
                }
            } -ModuleName LicenseGuard
        }

        It 'returns Status OK for licensed machine' {
            $result = Get-LGWindowsActivation
            $result.Status | Should -Be 'OK'
        }

        It 'returns correct module name' {
            $result = Get-LGWindowsActivation
            $result.Module | Should -Be 'WindowsActivation'
        }

        It 'includes ComputerName property' {
            $result = Get-LGWindowsActivation
            $result.ComputerName | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Unlicensed machine' {
        BeforeEach {
            Mock Get-CimInstance {
                [PSCustomObject]@{ LicenseStatus = 0; GracePeriodRemaining = 0 }
            } -ModuleName LicenseGuard
        }

        It 'returns Status EXPIRED for unlicensed machine' {
            $result = Get-LGWindowsActivation
            $result.Status | Should -Be 'EXPIRED'
        }
    }

    Context 'Grace period' {
        BeforeEach {
            Mock Get-CimInstance {
                [PSCustomObject]@{ LicenseStatus = 2; GracePeriodRemaining = 43200 }
            } -ModuleName LicenseGuard
        }

        It 'returns Status WARN for grace period' {
            $result = Get-LGWindowsActivation
            $result.Status | Should -Be 'WARN'
        }

        It 'mentions remaining days in detail' {
            $result = Get-LGWindowsActivation
            $result.Detail | Should -Match 'Remaining'
        }
    }

    Context 'WMI failure' {
        BeforeEach {
            Mock Get-CimInstance { throw 'Access denied' } -ModuleName LicenseGuard
        }

        It 'returns Status ERROR on WMI failure' {
            $result = Get-LGWindowsActivation
            $result.Status | Should -Be 'ERROR'
        }
    }
}

# ──────────────────────────────────────────────────────────────────────────────
Describe 'Get-LGInstalledSoftware' {
    BeforeAll {
        Initialize-LicenseGuard -Language en
        Mock Get-LGSoftwareRegistryRows {
            @(
                [PSCustomObject]@{ Name='TestApp'; Version='1.0'; Publisher='TestCo'; Status='OK'; ExpireInfo=''; InstallDate='2023-01-01' }
                [PSCustomObject]@{ Name='OldApp';  Version='2.5'; Publisher='OldCo';  Status='EXPIRED'; ExpireInfo='EXPIRED (30 days ago)'; InstallDate='2020-01-01' }
                [PSCustomObject]@{ Name='WarnApp'; Version='3.0'; Publisher='WarnCo'; Status='WARN'; ExpireInfo='Expires: 2026-05-01 (10 days)'; InstallDate='2024-01-01' }
            )
        } -ModuleName LicenseGuard
    }

    It 'returns PSCustomObject array' {
        $result = Get-LGInstalledSoftware
        $result | Should -Not -BeNullOrEmpty
        $result[0] | Should -BeOfType [PSCustomObject]
    }

    It 'includes required properties' {
        $result = Get-LGInstalledSoftware
        $props  = $result[0].PSObject.Properties.Name
        $props  | Should -Contain 'Name'
        $props  | Should -Contain 'Status'
        $props  | Should -Contain 'Version'
        $props  | Should -Contain 'Publisher'
        $props  | Should -Contain 'Module'
    }

    It 'correctly reflects EXPIRED status' {
        $result  = Get-LGInstalledSoftware
        $expired = $result | Where-Object { $_.Status -eq 'EXPIRED' }
        $expired | Should -Not -BeNullOrEmpty
        $expired[0].Name | Should -Be 'OldApp'
    }

    It 'sets Module to Software' {
        $result = Get-LGInstalledSoftware
        $result | ForEach-Object { $_.Module | Should -Be 'Software' }
    }
}

# ──────────────────────────────────────────────────────────────────────────────
Describe 'Get-LGEolStatus' {
    BeforeAll {
        Initialize-LicenseGuard -Language en
    }

    It 'detects Internet Explorer as EOL' {
        $sw     = @([PSCustomObject]@{ Name='Internet Explorer 11'; Version='11.0'; Publisher='Microsoft'; Status='OK'; ExpireInfo=''; InstallDate='-' })
        $result = Get-LGEolStatus -SoftwareCache $sw
        $result | Should -Not -BeNullOrEmpty
        $result[0].Status | Should -Be 'EXPIRED'
        $result[0].Module | Should -Be 'EOL'
    }

    It 'detects Office 2013 as EOL' {
        $sw     = @([PSCustomObject]@{ Name='Microsoft Office 2013'; Version='15.0'; Publisher='Microsoft'; Status='OK'; ExpireInfo=''; InstallDate='-' })
        $result = Get-LGEolStatus -SoftwareCache $sw
        $result | Should -Not -BeNullOrEmpty
        $result[0].Status | Should -Be 'EXPIRED'
    }

    It 'returns empty for modern software' {
        $sw     = @([PSCustomObject]@{ Name='ModernApp 2025'; Version='1.0'; Publisher='ModernCo'; Status='OK'; ExpireInfo=''; InstallDate='-' })
        $result = Get-LGEolStatus -SoftwareCache $sw
        $result | Should -BeNullOrEmpty
    }

    It 'includes EolDate property' {
        $sw     = @([PSCustomObject]@{ Name='Adobe Flash Player'; Version='32.0'; Publisher='Adobe'; Status='OK'; ExpireInfo=''; InstallDate='-' })
        $result = Get-LGEolStatus -SoftwareCache $sw
        $result[0].EolDate | Should -Be '2020-12-31'
    }
}

# ──────────────────────────────────────────────────────────────────────────────
Describe 'Invoke-LGPolicyCheck' {
    BeforeAll {
        Initialize-LicenseGuard -Language en

        $policyJson = @'
{
  "rules": [
    { "id":"R001","category":"P2P","pattern":"BitTorrent","matchType":"contains","status":"PROHIBITED","reason":"P2P not allowed","severity":"HIGH","alternative":"N/A","referenceUrl":"" },
    { "id":"R002","category":"Remote","pattern":"TeamViewer","matchType":"contains","status":"REQUIRES_LICENSE","reason":"License required","severity":"MEDIUM","alternative":"","referenceUrl":"" },
    { "id":"R003","category":"Browser","pattern":"Google Chrome","matchType":"contains","status":"ALLOWED","reason":"Approved","severity":"LOW","alternative":"","referenceUrl":"" }
  ]
}
'@

        $script:tmpPolicy = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.json'
        $policyJson | Out-File $script:tmpPolicy -Encoding UTF8
    }

    AfterAll {
        Remove-Item $script:tmpPolicy -ErrorAction SilentlyContinue
    }

    It 'marks BitTorrent as PROHIBITED' {
        $sw     = @([PSCustomObject]@{ Name='BitTorrent 7.10'; Version='7.10'; Publisher='BitTorrent Inc' })
        $result = Invoke-LGPolicyCheck -PolicyPath $script:tmpPolicy -SoftwareCache $sw
        $hit    = $result | Where-Object { $_.PolicyStatus -eq 'PROHIBITED' }
        $hit | Should -Not -BeNullOrEmpty
    }

    It 'marks TeamViewer as REQUIRES_LICENSE' {
        $sw     = @([PSCustomObject]@{ Name='TeamViewer 15'; Version='15'; Publisher='TeamViewer GmbH' })
        $result = Invoke-LGPolicyCheck -PolicyPath $script:tmpPolicy -SoftwareCache $sw
        $hit    = $result | Where-Object { $_.PolicyStatus -eq 'REQUIRES_LICENSE' }
        $hit | Should -Not -BeNullOrEmpty
    }

    It 'marks Google Chrome as ALLOWED' {
        $sw     = @([PSCustomObject]@{ Name='Google Chrome'; Version='120'; Publisher='Google LLC' })
        $result = Invoke-LGPolicyCheck -PolicyPath $script:tmpPolicy -SoftwareCache $sw
        $hit    = $result | Where-Object { $_.PolicyStatus -eq 'ALLOWED' }
        $hit | Should -Not -BeNullOrEmpty
    }

    It 'marks unmatched software as ALLOWED with N/A rule' {
        $sw     = @([PSCustomObject]@{ Name='Unknown App 1.0'; Version='1.0'; Publisher='NoCo' })
        $result = Invoke-LGPolicyCheck -PolicyPath $script:tmpPolicy -SoftwareCache $sw
        $hit    = $result | Where-Object { $_.RuleId -eq 'N/A' }
        $hit | Should -Not -BeNullOrEmpty
        $hit[0].PolicyStatus | Should -Be 'ALLOWED'
    }

    It 'honours whitelist in config' {
        & (Get-Module LicenseGuard) { $script:LGConfig.Whitelist = @('BitTorrent') }
        $sw     = @([PSCustomObject]@{ Name='BitTorrent 7.10'; Version='7.10'; Publisher='BitTorrent Inc' })
        $result = Invoke-LGPolicyCheck -PolicyPath $script:tmpPolicy -SoftwareCache $sw
        $result[0].PolicyStatus | Should -Be 'ALLOWED'
        $result[0].RuleId       | Should -Be 'WL'
        & (Get-Module LicenseGuard) { $script:LGConfig.Whitelist = @() }
    }

    It 'returns empty array when policy file not found' {
        $result = Invoke-LGPolicyCheck -PolicyPath 'C:\DoesNotExist\policy.json'
        @($result).Count | Should -Be 0
    }
}

# ──────────────────────────────────────────────────────────────────────────────
Describe 'Save-LGSnapshot and Get-LGDelta' {
    BeforeAll {
        Initialize-LicenseGuard
        $script:snapPath = [System.IO.Path]::GetTempFileName()
        & (Get-Module LicenseGuard) { param($p) $script:LGConfig.SnapshotPath = $p } -p $script:snapPath
    }
    AfterAll { Remove-Item $script:snapPath -ErrorAction SilentlyContinue }

    It 'saves snapshot without error' {
        $results  = @([PSCustomObject]@{ Name='App1'; Status='EXPIRED' })
        $findings = @([PSCustomObject]@{ Name='App2'; PolicyStatus='PROHIBITED' })
        { Save-LGSnapshot -AllResults $results -PolicyFindings $findings } | Should -Not -Throw
        Test-Path $script:snapPath | Should -BeTrue
    }

    It 'detects new issues in delta' {
        # Previous snapshot: App1 EXPIRED
        # Current: App1 + App3 EXPIRED
        $results  = @(
            [PSCustomObject]@{ Name='App1'; Status='EXPIRED' }
            [PSCustomObject]@{ Name='App3'; Status='EXPIRED' }
        )
        $findings = @([PSCustomObject]@{ Name='App2'; PolicyStatus='ALLOWED' })
        $delta    = Get-LGDelta -CurrentResults $results -CurrentPolicyFindings $findings
        $delta              | Should -Not -BeNullOrEmpty
        $delta.NewIssues    | Should -Contain 'App3'
    }

    It 'detects resolved issues in delta' {
        $results  = @()   # App1 resolved
        $findings = @([PSCustomObject]@{ Name='App2'; PolicyStatus='ALLOWED' })
        $delta    = Get-LGDelta -CurrentResults $results -CurrentPolicyFindings $findings
        $delta.ResolvedIssues | Should -Contain 'App1'
    }

    It 'returns null when no snapshot exists' {
        $delta = Get-LGDelta -SnapshotPath 'C:\DoesNotExist\snap.json' `
            -CurrentResults @() -CurrentPolicyFindings @()
        $delta | Should -BeNullOrEmpty
    }
}