Tests/AD-LinuxInventory.Tests.ps1

BeforeAll {
    $modulePath = Split-Path -Parent $PSScriptRoot
    Import-Module "$modulePath\AD-LinuxInventory.psd1" -Force
}

Describe 'AD-LinuxInventory Module' {

    Context 'Module Loading' {
        It 'Should import without errors' {
            { Import-Module "$PSScriptRoot\..\AD-LinuxInventory.psd1" -Force } | Should -Not -Throw
        }

        It 'Should export exactly 5 public functions' {
            $commands = Get-Command -Module AD-LinuxInventory
            $commands.Count | Should -Be 5
        }

        It 'Should export all expected functions' {
            $expected = @('Register-LinuxServer', 'Import-LinuxInventory', 'Get-LinuxInventory', 'Update-LinuxServer', 'Sync-LinuxInventory')
            foreach ($func in $expected) {
                Get-Command -Module AD-LinuxInventory -Name $func | Should -Not -BeNullOrEmpty
            }
        }

        It 'Should not export private functions' {
            { Get-Command -Module AD-LinuxInventory -Name Initialize-LinuxOU -ErrorAction Stop } | Should -Throw
            { Get-Command -Module AD-LinuxInventory -Name New-HtmlDashboard -ErrorAction Stop } | Should -Throw
        }
    }

    Context 'Register-LinuxServer Parameter Validation' {
        It 'Should have mandatory Name parameter' {
            (Get-Command Register-LinuxServer).Parameters['Name'].Attributes.Mandatory | Should -Contain $true
        }

        It 'Should have mandatory IPAddress parameter' {
            (Get-Command Register-LinuxServer).Parameters['IPAddress'].Attributes.Mandatory | Should -Contain $true
        }

        It 'Should default OperatingSystem to Linux' {
            $default = (Get-Command Register-LinuxServer).Parameters['OperatingSystem'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.PSDefaultValueAttribute] -or $_.TypeId.Name -eq 'ParameterAttribute' }
            # The default is set in code, not via attribute -- verify the param exists
            (Get-Command Register-LinuxServer).Parameters.ContainsKey('OperatingSystem') | Should -BeTrue
        }

        It 'Should support -WhatIf' {
            (Get-Command Register-LinuxServer).Parameters.ContainsKey('WhatIf') | Should -BeTrue
        }

        It 'Should support -Confirm' {
            (Get-Command Register-LinuxServer).Parameters.ContainsKey('Confirm') | Should -BeTrue
        }

        It 'Should have Force switch' {
            (Get-Command Register-LinuxServer).Parameters['Force'].SwitchParameter | Should -BeTrue
        }

        It 'Should validate IPAddress format' {
            $validatePattern = (Get-Command Register-LinuxServer).Parameters['IPAddress'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.ValidatePatternAttribute] }
            $validatePattern | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Import-LinuxInventory Parameter Validation' {
        It 'Should have mandatory CsvPath parameter' {
            (Get-Command Import-LinuxInventory).Parameters['CsvPath'].Attributes.Mandatory | Should -Contain $true
        }

        It 'Should validate CsvPath with ValidateScript' {
            $validateScript = (Get-Command Import-LinuxInventory).Parameters['CsvPath'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.ValidateScriptAttribute] }
            $validateScript | Should -Not -BeNullOrEmpty
        }

        It 'Should have Force switch' {
            (Get-Command Import-LinuxInventory).Parameters['Force'].SwitchParameter | Should -BeTrue
        }
    }

    Context 'Get-LinuxInventory Parameter Validation' {
        It 'Should have Online switch' {
            (Get-Command Get-LinuxInventory).Parameters['Online'].SwitchParameter | Should -BeTrue
        }

        It 'Should accept OutputPath parameter' {
            (Get-Command Get-LinuxInventory).Parameters.ContainsKey('OutputPath') | Should -BeTrue
        }

        It 'Should accept OrganizationalUnit parameter' {
            (Get-Command Get-LinuxInventory).Parameters.ContainsKey('OrganizationalUnit') | Should -BeTrue
        }
    }

    Context 'Update-LinuxServer Parameter Validation' {
        It 'Should have mandatory Name parameter' {
            (Get-Command Update-LinuxServer).Parameters['Name'].Attributes.Mandatory | Should -Contain $true
        }

        It 'Should accept pipeline input for Name' {
            (Get-Command Update-LinuxServer).Parameters['Name'].Attributes.ValueFromPipeline | Should -Contain $true
        }

        It 'Should support -WhatIf' {
            (Get-Command Update-LinuxServer).Parameters.ContainsKey('WhatIf') | Should -BeTrue
        }

        It 'Should validate IPAddress format' {
            $validatePattern = (Get-Command Update-LinuxServer).Parameters['IPAddress'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.ValidatePatternAttribute] }
            $validatePattern | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Sync-LinuxInventory Parameter Validation' {
        It 'Should have UpdateDescription switch' {
            (Get-Command Sync-LinuxInventory).Parameters['UpdateDescription'].SwitchParameter | Should -BeTrue
        }

        It 'Should have TimeoutMs parameter with default 1000' {
            (Get-Command Sync-LinuxInventory).Parameters.ContainsKey('TimeoutMs') | Should -BeTrue
        }

        It 'Should accept OrganizationalUnit parameter' {
            (Get-Command Sync-LinuxInventory).Parameters.ContainsKey('OrganizationalUnit') | Should -BeTrue
        }
    }

    Context 'Register-LinuxServer Mocked Execution' {
        BeforeAll {
            Mock -ModuleName AD-LinuxInventory Import-Module { }
            Mock -ModuleName AD-LinuxInventory Get-ADDomain {
                [PSCustomObject]@{
                    DistinguishedName = 'DC=contoso,DC=com'
                    DNSRoot           = 'contoso.com'
                }
            }
            Mock -ModuleName AD-LinuxInventory Get-ADOrganizationalUnit { $true }
            Mock -ModuleName AD-LinuxInventory New-ADOrganizationalUnit { }
            Mock -ModuleName AD-LinuxInventory Get-ADComputer { throw [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]::new('Not found') }
            Mock -ModuleName AD-LinuxInventory New-ADComputer { }
            Mock -ModuleName AD-LinuxInventory Set-ADComputer { }
            Mock -ModuleName AD-LinuxInventory Remove-ADComputer { }
        }

        It 'Should call New-ADComputer with correct name' {
            Register-LinuxServer -Name 'test-srv-01' -IPAddress '10.0.0.1' -OperatingSystem 'Ubuntu 22.04'
            Should -Invoke -ModuleName AD-LinuxInventory -CommandName New-ADComputer -Times 1 -ParameterFilter {
                $Name -eq 'TEST-SRV-01'
            }
        }

        It 'Should call Set-ADComputer to set OS and IP properties' {
            Register-LinuxServer -Name 'test-srv-02' -IPAddress '10.0.0.2' -OperatingSystem 'RHEL 9'
            Should -Invoke -ModuleName AD-LinuxInventory -CommandName Set-ADComputer -Times 1
        }

        It 'Should return an object with Registered status' {
            $result = Register-LinuxServer -Name 'test-srv-03' -IPAddress '10.0.0.3'
            $result.Status | Should -Be 'Registered'
            $result.Name | Should -Be 'TEST-SRV-03'
            $result.IPv4Address | Should -Be '10.0.0.3'
        }

        It 'Should set ManagedBy when specified' {
            Register-LinuxServer -Name 'test-srv-04' -IPAddress '10.0.0.4' -ManagedBy 'jsmith'
            Should -Invoke -ModuleName AD-LinuxInventory -CommandName Set-ADComputer -Times 1 -ParameterFilter {
                $ManagedBy -eq 'jsmith'
            }
        }
    }

    Context 'Get-LinuxInventory Mocked Execution' {
        BeforeAll {
            Mock -ModuleName AD-LinuxInventory Import-Module { }
            Mock -ModuleName AD-LinuxInventory Get-ADDomain {
                [PSCustomObject]@{
                    DistinguishedName = 'DC=contoso,DC=com'
                    DNSRoot           = 'contoso.com'
                }
            }
            Mock -ModuleName AD-LinuxInventory Get-ADOrganizationalUnit { $true }
            Mock -ModuleName AD-LinuxInventory Get-ADComputer {
                @(
                    [PSCustomObject]@{
                        Name                   = 'WEB-PROD-01'
                        DNSHostName            = 'web-prod-01.contoso.com'
                        IPv4Address            = '10.1.2.50'
                        OperatingSystem        = 'Ubuntu 22.04 LTS'
                        OperatingSystemVersion = '22.04'
                        Description            = 'Production web server'
                        ManagedBy              = 'CN=John Smith,OU=Users,DC=contoso,DC=com'
                        Created                = (Get-Date).AddMonths(-6)
                        Modified               = (Get-Date).AddDays(-1)
                        Enabled                = $true
                    },
                    [PSCustomObject]@{
                        Name                   = 'DB-PROD-01'
                        DNSHostName            = 'db-prod-01.contoso.com'
                        IPv4Address            = '10.1.3.10'
                        OperatingSystem        = 'RHEL 9'
                        OperatingSystemVersion = '9.3'
                        Description            = 'Production database'
                        ManagedBy              = $null
                        Created                = (Get-Date).AddMonths(-3)
                        Modified               = (Get-Date).AddDays(-7)
                        Enabled                = $true
                    }
                )
            }
        }

        It 'Should return all servers from the OU' {
            $results = Get-LinuxInventory
            @($results).Count | Should -Be 2
        }

        It 'Should include expected properties on output objects' {
            $results = Get-LinuxInventory
            $first = $results[0]
            $first.Name | Should -Be 'WEB-PROD-01'
            $first.IPv4Address | Should -Be '10.1.2.50'
            $first.OperatingSystem | Should -Be 'Ubuntu 22.04 LTS'
            $first.Description | Should -Be 'Production web server'
        }

        It 'Should have Online property as null when -Online is not specified' {
            $results = Get-LinuxInventory
            $results[0].Online | Should -BeNullOrEmpty
        }
    }

    Context 'Sync-LinuxInventory Mocked Execution' {
        BeforeAll {
            Mock -ModuleName AD-LinuxInventory Import-Module { }
            Mock -ModuleName AD-LinuxInventory Get-ADDomain {
                [PSCustomObject]@{
                    DistinguishedName = 'DC=contoso,DC=com'
                    DNSRoot           = 'contoso.com'
                }
            }
            Mock -ModuleName AD-LinuxInventory Get-ADOrganizationalUnit { $true }
            Mock -ModuleName AD-LinuxInventory Set-ADComputer { }
            Mock -ModuleName AD-LinuxInventory Get-ADComputer {
                @(
                    [PSCustomObject]@{
                        Name            = 'WEB-PROD-01'
                        DNSHostName     = 'web-prod-01.contoso.com'
                        IPv4Address     = '10.1.2.50'
                        Description     = 'Production web server'
                        OperatingSystem = 'Ubuntu 22.04 LTS'
                    },
                    [PSCustomObject]@{
                        Name            = 'DB-OFFLINE-01'
                        DNSHostName     = 'db-offline-01.contoso.com'
                        IPv4Address     = '10.1.3.99'
                        Description     = 'Decommissioned DB'
                        OperatingSystem = 'CentOS 7'
                    }
                )
            }
            Mock -ModuleName AD-LinuxInventory Test-Connection {
                param($ComputerName)
                if ($ComputerName -eq '10.1.2.50') {
                    [PSCustomObject]@{ ResponseTime = 2 }
                }
                else {
                    $null
                }
            }
        }

        It 'Should return results for all servers' {
            $results = Sync-LinuxInventory
            @($results).Count | Should -Be 2
        }

        It 'Should detect online server correctly' {
            $results = Sync-LinuxInventory
            $online = $results | Where-Object { $_.Name -eq 'WEB-PROD-01' }
            $online.Online | Should -BeTrue
        }

        It 'Should detect offline server correctly' {
            $results = Sync-LinuxInventory
            $offline = $results | Where-Object { $_.Name -eq 'DB-OFFLINE-01' }
            $offline.Online | Should -BeFalse
        }

        It 'Should include IPAddress in results' {
            $results = Sync-LinuxInventory
            $results[0].IPAddress | Should -Be '10.1.2.50'
        }
    }

    Context 'Manifest Validation' {
        BeforeAll {
            $manifest = Test-ModuleManifest -Path "$PSScriptRoot\..\AD-LinuxInventory.psd1"
        }

        It 'Should have version 1.0.0' {
            $manifest.Version.ToString() | Should -Be '1.0.0'
        }

        It 'Should have correct author' {
            $manifest.Author | Should -Be 'Larry Roberts'
        }

        It 'Should have correct GUID' {
            $manifest.GUID.ToString() | Should -Be 'd2e8f3a1-5b49-4c7d-ae12-9f3c6d8b7e45'
        }

        It 'Should have ProjectUri pointing to GitHub' {
            $manifest.PrivateData.PSData.ProjectUri | Should -Be 'https://github.com/larro1991/AD-LinuxInventory'
        }

        It 'Should have LicenseUri pointing to GitHub' {
            $manifest.PrivateData.PSData.LicenseUri | Should -Be 'https://github.com/larro1991/AD-LinuxInventory/blob/master/LICENSE'
        }

        It 'Should include expected tags' {
            $tags = $manifest.PrivateData.PSData.Tags
            $tags | Should -Contain 'ActiveDirectory'
            $tags | Should -Contain 'Linux'
            $tags | Should -Contain 'Inventory'
            $tags | Should -Contain 'CrossPlatform'
            $tags | Should -Contain 'ServerManagement'
        }

        It 'Should require PowerShell 5.1' {
            $manifest.PowerShellVersion.ToString() | Should -Be '5.1'
        }

        It 'Should export exactly 5 functions in manifest' {
            $manifest.ExportedFunctions.Count | Should -Be 5
        }
    }
}