tests/Get-MsecDefenderCertificateUsage.Tests.ps1

#Requires -Module Pester
#
# Tests for Get-MsecDefenderCertificateUsage. It groups DeviceFileCertificateInfo by signing
# certificate and projects validity, trust and device counts.
#
# Covered:
# - IsTrusted arrives as a NUMBER through the OData surface. Casting to [bool] would make
# an untrusted certificate read as trusted, since [bool]"0" is $true - the same trap that
# made Secure Score report every control as enabled.
# - DaysUntilExpiry is $null, never a sentinel, when the record carries no expiry.
# - -ExpiringWithinDays must not silently include certificates with unknown expiry.
# - SignerHash is passed through verbatim: it is the thumbprint a Defender indicator takes.

BeforeAll {
    Import-Module (Join-Path $PSScriptRoot '..' 'Msec.psm1') -Force -ErrorAction Stop
}
AfterAll { Remove-Module Msec -Force -ErrorAction SilentlyContinue }

Describe 'Get-MsecDefenderCertificateUsage' {
    BeforeEach {
        InModuleScope Msec {
            Mock Assert-MsecSession -MockWith { }
            Mock Search-MsecDefenderHunting -MockWith {
                @(
                    [pscustomobject]@{
                        Signer = 'Anthropic, PBC'; Issuer = 'DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1'
                        SignerHash = '0d7581d2c51c59df686c3000c70bf543f9f6c6cb'; IssuerHash = '7b0f'
                        CertificateSerialNumber = '0ed59ca47f15d09a57755e4b12d31a50'
                        CertificateCreationTime = '2025-10-14T00:00:00Z'
                        CertificateExpirationTime = (Get-Date).ToUniversalTime().AddDays(13).ToString('o')
                        Files = 144; Devices = 91; IsTrusted = 1; IsRootSignerMicrosoft = 0
                        FirstSeen = '2026-10-01T00:00:00Z'; LastSeen = '2026-10-07T00:00:00Z'
                    }
                    [pscustomobject]@{
                        Signer = 'Code Sign Test (DO NOT TRUST)'; Issuer = 'Microsoft Testing PCA 2010'
                        SignerHash = 'deadbeef'; IssuerHash = 'cafe'; CertificateSerialNumber = '1'
                        CertificateCreationTime = '2020-01-01T00:00:00Z'
                        CertificateExpirationTime = (Get-Date).ToUniversalTime().AddDays(900).ToString('o')
                        Files = 3; Devices = 53; IsTrusted = 0; IsRootSignerMicrosoft = 0
                        FirstSeen = '2026-10-01T00:00:00Z'; LastSeen = '2026-10-07T00:00:00Z'
                    }
                    [pscustomobject]@{
                        Signer = 'No Expiry Recorded'; Issuer = 'Somewhere'
                        SignerHash = 'abc'; IssuerHash = 'def'; CertificateSerialNumber = '2'
                        CertificateCreationTime = $null; CertificateExpirationTime = $null
                        Files = 1; Devices = 1; IsTrusted = 1; IsRootSignerMicrosoft = 0
                        FirstSeen = '2026-10-01T00:00:00Z'; LastSeen = '2026-10-07T00:00:00Z'
                    }
                )
            }
        }
    }

    It 'treats a numeric IsTrusted of 0 as untrusted' {
        $rows = InModuleScope Msec { , @(Get-MsecDefenderCertificateUsage) }

        $anthropic = $rows | Where-Object Signer -eq 'Anthropic, PBC'
        $test      = $rows | Where-Object Signer -match 'DO NOT TRUST'
        $anthropic.IsTrusted | Should -BeTrue
        # [bool] on the value 0 coming back as a string would be $true, and an untrusted
        # certificate on 53 devices would read as trusted.
        $test.IsTrusted | Should -BeFalse
        $test.IsTrusted | Should -BeOfType [bool]
    }

    It 'passes SignerHash through unchanged, because it is the indicator thumbprint' {
        $row = InModuleScope Msec { Get-MsecDefenderCertificateUsage -Signer 'Anthropic' }
        $row.SignerHash | Should -Be '0d7581d2c51c59df686c3000c70bf543f9f6c6cb'
        $row.SignerHash | Should -Match '^[0-9a-f]{40}$'
    }

    It 'reports DaysUntilExpiry as null, not a sentinel, when there is no expiry' {
        $row = InModuleScope Msec { Get-MsecDefenderCertificateUsage -Signer 'No Expiry' }
        $row.DaysUntilExpiry | Should -BeNullOrEmpty
        $row.ValidTo | Should -BeNullOrEmpty
        # Unknown is not the same as 'does not expire'; both would be wrong to assert.
        $row.Expired | Should -BeNullOrEmpty
    }

    It 'excludes unknown-expiry certificates from -ExpiringWithinDays rather than assuming either way' {
        $rows = InModuleScope Msec { , @(Get-MsecDefenderCertificateUsage -ExpiringWithinDays 30) }

        @($rows).Count | Should -Be 1
        $rows[0].Signer | Should -Be 'Anthropic, PBC'
        # Including it would claim an expiry nobody measured; the row is still available
        # without the filter.
        @($rows | Where-Object Signer -eq 'No Expiry Recorded') | Should -BeNullOrEmpty
    }

    It 'computes DaysUntilExpiry against the recorded expiry' {
        $row = InModuleScope Msec { Get-MsecDefenderCertificateUsage -Signer 'Anthropic' }
        $row.DaysUntilExpiry | Should -BeGreaterThan 12
        $row.DaysUntilExpiry | Should -BeLessThan 14
        $row.Expired | Should -BeFalse
    }

    It 'filters by issuer, which is how an Apple signing identity is told from an Authenticode one' {
        $rows = InModuleScope Msec { , @(Get-MsecDefenderCertificateUsage -Issuer 'DigiCert') }
        @($rows).Count | Should -Be 1
        $rows[0].Signer | Should -Be 'Anthropic, PBC'
    }

    It 'warns rather than returning silence when hunting comes back empty' {
        $warning = InModuleScope Msec {
            Mock Search-MsecDefenderHunting -MockWith { @() }
            $w = @()
            Get-MsecDefenderCertificateUsage -WarningVariable w -WarningAction SilentlyContinue | Out-Null
            "$($w -join ' ')"
        }
        $warning | Should -Match 'NOT evidence that nothing is signed'
    }
}