Testing/Unit/PowerShell/Orchestrator/Invoke-CyberCached.Tests.ps1

$OrchestratorPath = '../../../../Modules/Orchestrator.psm1'
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath $OrchestratorPath) -Function 'Invoke-CyberAssessmentCached' -Force

InModuleScope Orchestrator {
    Describe -Tag 'Orchestrator' -Name 'Invoke-CyberAssessmentCached' {
        BeforeAll {
            Mock -ModuleName Orchestrator Remove-Resources {}
            Mock -ModuleName Orchestrator Import-Resources {}
            function Invoke-Connection {}
            Mock -ModuleName Orchestrator Invoke-Connection { @() }
            function Get-TenantDetail {}
            Mock -ModuleName Orchestrator Get-TenantDetail { '{"DisplayName": "displayName"}' }
            function Invoke-ProviderList {}
            Mock -ModuleName Orchestrator Invoke-ProviderList {}
            function Invoke-RunRego {}
            Mock -ModuleName Orchestrator Invoke-RunRego {}
            function Invoke-ReportCreation {}
            Mock -ModuleName Orchestrator Invoke-ReportCreation {}
            function Merge-JsonOutput {throw 'this will be mocked'}
            Mock -ModuleName Orchestrator Merge-JsonOutput {}
            function Disconnect-CyberAssessmentTenant {}
            Mock -ModuleName Orchestrator Disconnect-CyberAssessmentTenant
            function ConvertTo-ResultsCsv {throw 'this will be mocked'}
            Mock -ModuleName Orchestrator ConvertTo-ResultsCsv {}
            function Set-Utf8NoBom {}
            Mock -ModuleName Orchestrator Set-Utf8NoBom

            Mock -CommandName Write-Debug {}
            Mock -CommandName New-Item {}
            Mock -CommandName Get-Content { "" }
            Mock -CommandName Get-Member { $true }
            Mock -CommandName New-Guid { "00000000-0000-0000-0000-000000000000" }
            Mock -CommandName Get-ChildItem {
                [pscustomobject]@{"FullName"="CyberResults.json"; "CreationTime"=[DateTime]"2024-01-01"}
            }
            Mock -CommandName Remove-Item {}
            Mock -CommandName ConvertFrom-Json {
                [PSCustomObject]@{"report_uuid"="00000000-0000-0000-0000-000000000000"}
            }
        }
        Context 'When checking the conformance of commercial tenants' {
            BeforeAll {
                [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'SplatParams')]
                $SplatParams = @{
                    M365Environment = 'commercial'
                }
            }
            It 'Given -ProductNames aad should not throw' {
                $SplatParams += @{
                    ProductNames = @("aad")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames defender should not throw' {
                $SplatParams += @{
                    ProductNames = @("defender")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames exo should not throw' {
                $SplatParams += @{
                    ProductNames = @("exo")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames powerplatform should not throw' {
                $SplatParams += @{
                    ProductNames = @("powerplatform")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames teams should not throw' {
                $SplatParams += @{
                    ProductNames = @("teams")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames * should not throw' {
                $SplatParams += @{
                    ProductNames = @("*")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
        }
        Context 'When omitting the export of the commercial tenant provider json' {
            BeforeAll {
                [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'SplatParams')]
                $SplatParams = @{
                    M365Environment = 'commercial'
                    ExportProvider = $false
                }
            }
            It 'Given -ProductNames aad should not throw' {
                $SplatParams += @{
                    ProductNames = @("aad")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames defender should not throw' {
                $SplatParams += @{
                    ProductNames = @("defender")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames exo should not throw' {
                $SplatParams += @{
                    ProductNames = @("exo")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames powerplatform should not throw' {
                $SplatParams += @{
                    ProductNames = @("powerplatform")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames teams should not throw' {
                $SplatParams += @{
                    ProductNames = @("teams")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -ProductNames * should not throw' {
                $SplatParams += @{
                    ProductNames = @("*")
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given an existing UUID should not generate a new one' {
                # Get-Member was mocked above to return True so as far as the
                # provider can tell, the existing output already has a UUID
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
                Should -Invoke -CommandName New-Guid -Exactly -Times 0
            }
            It 'Given output without a UUID should generate a new one' {
                Mock -CommandName ConvertFrom-Json { [PSCustomObject]@{} }
                Mock -CommandName Get-Member { $false }
                # Now Get-Member will return False so as far as the provider
                # can tell, the existing output does not have a UUID
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
                Should -Invoke -CommandName New-Guid -Exactly -Times 1
            }
        }
        Context 'When checking module version' {
            It 'Given -Version should not throw' {
                {Invoke-CyberAssessmentCached -Version} | Should -Not -Throw
            }
        }
        Context 'When modifying the CSV output files names' {
            It 'Given -OutCsvFileName should not throw' {
                $SplatParams += @{
                    OutCsvFileName = "a"
                }
                {Invoke-CyberAssessmentCached -Version} | Should -Not -Throw
            }
            It 'Given -OutActionPlanFileName should not throw' {
                $SplatParams += @{
                    OutActionPlanFileName = "a"
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given both -OutCsvFileName and -OutActionPlanFileName should not throw' {
                $SplatParams += @{
                    OutCsvFileName = "a"
                    OutActionPlanFileName = "b"
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Not -Throw
            }
            It 'Given -OutCsvFileName and -OutActionPlanFileName equal should throw' {
                $SplatParams += @{
                    OutCsvFileName = "a"
                    OutActionPlanFileName = "a"
                }
                {Invoke-CyberAssessmentCached @SplatParams} | Should -Throw
            }
        }
        Context "When there are multiple CyberResults*.json files" {
        # It's possible (but not expected) that there are multiple files matching
        # "CyberResults*.json". In this case, CyberAssessment should choose the file
        # created most recently.
            It 'Should select the most recently created' {
                Mock -CommandName Get-ChildItem { @(
                    [pscustomobject]@{"FullName"="CyberResultsOld.json"; "CreationTime"=[DateTime]"2023-01-01"},
                    [pscustomobject]@{"FullName"="CyberResultsNew.json"; "CreationTime"=[DateTime]"2024-01-01"},
                    [pscustomobject]@{"FullName"="CyberResultsOldest.json"; "CreationTime"=[DateTime]"2022-01-01"}
                ) }

                Mock -CommandName Get-Content {
                    if ($Path -ne "CyberResultsNew.json") {
                        # Should be the new one, throw if not
                        throw
                    }
                }

                {Invoke-CyberAssessmentCached @SplatParams} | Should -Throw
            }
        }
    }
}

AfterAll {
    Remove-Module Orchestrator -ErrorAction SilentlyContinue
}