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 7 public functions' { $commands = Get-Command -Module AD-LinuxInventory $commands.Count | Should -Be 7 } It 'Should export all expected functions' { $expected = @( 'Register-LinuxServer', 'Import-LinuxInventory', 'Get-LinuxInventory', 'Update-LinuxServer', 'Sync-LinuxInventory', 'Import-AgentRegistration', 'Get-RegistrationHeartbeat' ) 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 'Import-AgentRegistration Parameter Validation' { It 'Should have mandatory RegistrationPath parameter' { (Get-Command Import-AgentRegistration).Parameters['RegistrationPath'].Attributes.Mandatory | Should -Contain $true } It 'Should validate RegistrationPath with ValidateScript' { $validateScript = (Get-Command Import-AgentRegistration).Parameters['RegistrationPath'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateScriptAttribute] } $validateScript | Should -Not -BeNullOrEmpty } It 'Should have Token parameter' { (Get-Command Import-AgentRegistration).Parameters.ContainsKey('Token') | Should -BeTrue } It 'Should have OrganizationalUnit parameter' { (Get-Command Import-AgentRegistration).Parameters.ContainsKey('OrganizationalUnit') | Should -BeTrue } It 'Should have AutoApprove switch' { (Get-Command Import-AgentRegistration).Parameters['AutoApprove'].SwitchParameter | Should -BeTrue } It 'Should have OutputPath parameter' { (Get-Command Import-AgentRegistration).Parameters.ContainsKey('OutputPath') | Should -BeTrue } It 'Should support -WhatIf' { (Get-Command Import-AgentRegistration).Parameters.ContainsKey('WhatIf') | Should -BeTrue } It 'Should support -Confirm' { (Get-Command Import-AgentRegistration).Parameters.ContainsKey('Confirm') | Should -BeTrue } It 'Should have SupportsShouldProcess attribute' { $cmdInfo = Get-Command Import-AgentRegistration $cmdletBinding = $cmdInfo.ScriptBlock.Attributes | Where-Object { $_ -is [System.Management.Automation.CmdletBindingAttribute] } $cmdletBinding.SupportsShouldProcess | Should -BeTrue } } Context 'Get-RegistrationHeartbeat Parameter Validation' { It 'Should have RegistrationPath parameter' { (Get-Command Get-RegistrationHeartbeat).Parameters.ContainsKey('RegistrationPath') | Should -BeTrue } It 'Should have OrganizationalUnit parameter' { (Get-Command Get-RegistrationHeartbeat).Parameters.ContainsKey('OrganizationalUnit') | Should -BeTrue } It 'Should have StaleHours parameter with default 2' { (Get-Command Get-RegistrationHeartbeat).Parameters.ContainsKey('StaleHours') | Should -BeTrue } It 'Should have OfflineHours parameter with default 24' { (Get-Command Get-RegistrationHeartbeat).Parameters.ContainsKey('OfflineHours') | Should -BeTrue } It 'Should have OutputPath parameter' { (Get-Command Get-RegistrationHeartbeat).Parameters.ContainsKey('OutputPath') | Should -BeTrue } It 'Should validate StaleHours range' { $validateRange = (Get-Command Get-RegistrationHeartbeat).Parameters['StaleHours'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } $validateRange | Should -Not -BeNullOrEmpty } It 'Should validate OfflineHours range' { $validateRange = (Get-Command Get-RegistrationHeartbeat).Parameters['OfflineHours'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } $validateRange | Should -Not -BeNullOrEmpty } } 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 'Import-AgentRegistration Mocked Execution' { BeforeAll { # Create a temp directory with sample JSON files $script:tempRegDir = Join-Path -Path $env:TEMP -ChildPath "ad-reg-test-$(Get-Random)" New-Item -Path $script:tempRegDir -ItemType Directory -Force | Out-Null # Sample registration JSON (Ubuntu) $ubuntuJson = @{ hostname = 'web-linux-01' fqdn = 'web-linux-01.contoso.com' ip_addresses = @('10.1.2.50', '10.1.2.51') operating_system = 'Ubuntu 24.04.1 LTS' operating_system_version = '24.04.1' kernel = '6.8.0-41-generic' os_family = 'Linux' cpu_cores = 8 memory_gb = 32 disk_gb = 500 uptime_hours = 720 services = @('nginx', 'postgresql', 'ssh') package_count = 423 agent_version = '2.0.0' registered_at = '2026-02-17T10:30:00Z' token = 'test-secret' } | ConvertTo-Json $ubuntuJson | Out-File -FilePath (Join-Path $script:tempRegDir 'web-linux-01.json') -Encoding UTF8 # Sample registration JSON (macOS) $macJson = @{ hostname = 'macbook-dev-01' fqdn = 'macbook-dev-01.contoso.com' ip_addresses = @('10.1.4.30') operating_system = 'macOS 15.2 Sequoia' operating_system_version = '15.2' kernel = '24.2.0' os_family = 'macOS' cpu_cores = 10 memory_gb = 16 disk_gb = 512 uptime_hours = 48 services = @('com.apple.Finder', 'com.apple.dock') package_count = 85 agent_version = '2.0.0' registered_at = '2026-02-17T10:30:00Z' token = 'test-secret' } | ConvertTo-Json $macJson | Out-File -FilePath (Join-Path $script:tempRegDir 'macbook-dev-01.json') -Encoding UTF8 # Bad token JSON $badTokenJson = @{ hostname = 'rogue-server' fqdn = 'rogue.contoso.com' ip_addresses = @('10.99.99.1') operating_system = 'Kali Linux 2024.1' operating_system_version = '2024.1' os_family = 'Linux' token = 'wrong-token' } | ConvertTo-Json $badTokenJson | Out-File -FilePath (Join-Path $script:tempRegDir 'rogue-server.json') -Encoding UTF8 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 New-ADComputer { } Mock -ModuleName AD-LinuxInventory Set-ADComputer { } Mock -ModuleName AD-LinuxInventory Get-ADComputer { param($Identity) throw [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]::new("Not found: $Identity") } } AfterAll { if (Test-Path $script:tempRegDir) { Remove-Item -Path $script:tempRegDir -Recurse -Force -ErrorAction SilentlyContinue } } It 'Should process JSON files and return summary' { $result = Import-AgentRegistration -RegistrationPath $script:tempRegDir -Token 'test-secret' -AutoApprove $result | Should -Not -BeNullOrEmpty $result.Processed | Should -BeGreaterOrEqual 1 } It 'Should reject files with wrong token' { # Re-create the bad-token file since previous test may have moved it $badTokenJson = @{ hostname = 'rogue-server'; fqdn = 'rogue.contoso.com'; ip_addresses = @('10.99.99.1') operating_system = 'Kali Linux'; operating_system_version = '2024.1'; os_family = 'Linux'; token = 'wrong-token' } | ConvertTo-Json $badTokenJson | Out-File -FilePath (Join-Path $script:tempRegDir 'rogue-server.json') -Encoding UTF8 $result = Import-AgentRegistration -RegistrationPath $script:tempRegDir -Token 'test-secret' -AutoApprove $result.Rejected | Should -BeGreaterOrEqual 1 } It 'Should call New-ADComputer for new registrations' { # Re-create a test file $testJson = @{ hostname = 'new-srv-01'; fqdn = 'new-srv-01.contoso.com'; ip_addresses = @('10.0.0.10') operating_system = 'Debian 12'; operating_system_version = '12'; os_family = 'Linux'; token = 'test-secret' } | ConvertTo-Json $testJson | Out-File -FilePath (Join-Path $script:tempRegDir 'new-srv-01.json') -Encoding UTF8 Import-AgentRegistration -RegistrationPath $script:tempRegDir -Token 'test-secret' -AutoApprove Should -Invoke -ModuleName AD-LinuxInventory -CommandName New-ADComputer -Times 1 -ParameterFilter { $Name -eq 'NEW-SRV-01' } } It 'Should call Set-ADComputer to set OS properties on new registrations' { $testJson = @{ hostname = 'new-srv-02'; fqdn = 'new-srv-02.contoso.com'; ip_addresses = @('10.0.0.11') operating_system = 'FreeBSD 14.1-RELEASE'; operating_system_version = '14.1'; os_family = 'FreeBSD'; token = 'test-secret' } | ConvertTo-Json $testJson | Out-File -FilePath (Join-Path $script:tempRegDir 'new-srv-02.json') -Encoding UTF8 Import-AgentRegistration -RegistrationPath $script:tempRegDir -Token 'test-secret' -AutoApprove Should -Invoke -ModuleName AD-LinuxInventory -CommandName Set-ADComputer -Times 1 } It 'Should archive processed files to processed subfolder' { $testJson = @{ hostname = 'archive-test'; fqdn = 'archive-test.contoso.com'; ip_addresses = @('10.0.0.20') operating_system = 'Alpine Linux 3.19'; operating_system_version = '3.19'; os_family = 'Linux'; token = 'test-secret' } | ConvertTo-Json $testJson | Out-File -FilePath (Join-Path $script:tempRegDir 'archive-test.json') -Encoding UTF8 Import-AgentRegistration -RegistrationPath $script:tempRegDir -Token 'test-secret' -AutoApprove $processedDir = Join-Path $script:tempRegDir 'processed' $archivedFiles = Get-ChildItem -Path $processedDir -Filter '*archive-test*' -ErrorAction SilentlyContinue $archivedFiles | Should -Not -BeNullOrEmpty } } Context 'Get-RegistrationHeartbeat Mocked Execution' { BeforeAll { # Create a temp directory with sample JSON files $script:tempHbDir = Join-Path -Path $env:TEMP -ChildPath "ad-hb-test-$(Get-Random)" New-Item -Path $script:tempHbDir -ItemType Directory -Force | Out-Null # Create a "recent" JSON file '{}' | Out-File -FilePath (Join-Path $script:tempHbDir 'ACTIVE-SRV-01.json') -Encoding UTF8 # Create an "old" JSON file (stale) $staleFile = Join-Path $script:tempHbDir 'STALE-SRV-01.json' '{}' | Out-File -FilePath $staleFile -Encoding UTF8 (Get-Item $staleFile).LastWriteTime = (Get-Date).AddHours(-5) # Create a very old JSON file (offline) $offlineFile = Join-Path $script:tempHbDir 'OFFLINE-SRV-01.json' '{}' | Out-File -FilePath $offlineFile -Encoding UTF8 (Get-Item $offlineFile).LastWriteTime = (Get-Date).AddHours(-48) 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 = 'ACTIVE-SRV-01' DNSHostName = 'active-srv-01.contoso.com' IPv4Address = '10.1.1.1' OperatingSystem = 'Ubuntu 24.04.1 LTS' OperatingSystemVersion = '24.04.1' Description = 'Linux | 4 cores | 8GB RAM' Created = (Get-Date).AddMonths(-1) Modified = (Get-Date).AddMinutes(-30) }, [PSCustomObject]@{ Name = 'STALE-SRV-01' DNSHostName = 'stale-srv-01.contoso.com' IPv4Address = '10.1.1.2' OperatingSystem = 'macOS 15.2 Sequoia' OperatingSystemVersion = '15.2' Description = 'macOS | 10 cores | 16GB RAM' Created = (Get-Date).AddMonths(-2) Modified = (Get-Date).AddHours(-5) }, [PSCustomObject]@{ Name = 'OFFLINE-SRV-01' DNSHostName = 'offline-srv-01.contoso.com' IPv4Address = '10.1.1.3' OperatingSystem = 'FreeBSD 14.1-RELEASE' OperatingSystemVersion = '14.1' Description = 'FreeBSD | 2 cores | 4GB RAM' Created = (Get-Date).AddMonths(-6) Modified = (Get-Date).AddHours(-48) }, [PSCustomObject]@{ Name = 'MANUAL-SRV-01' DNSHostName = 'manual-srv-01.contoso.com' IPv4Address = '10.1.1.4' OperatingSystem = 'RHEL 9' OperatingSystemVersion = '9.3' Description = 'Manually registered' Created = (Get-Date).AddMonths(-3) Modified = (Get-Date).AddMonths(-3) } ) } } AfterAll { if (Test-Path $script:tempHbDir) { Remove-Item -Path $script:tempHbDir -Recurse -Force -ErrorAction SilentlyContinue } } It 'Should return all systems from the OU' { $results = Get-RegistrationHeartbeat -RegistrationPath $script:tempHbDir @($results).Count | Should -Be 4 } It 'Should classify recent heartbeat as Active' { $results = Get-RegistrationHeartbeat -RegistrationPath $script:tempHbDir $active = $results | Where-Object { $_.ComputerName -eq 'ACTIVE-SRV-01' } $active.Status | Should -Be 'Active' } It 'Should classify old heartbeat as Stale' { $results = Get-RegistrationHeartbeat -RegistrationPath $script:tempHbDir -StaleHours 2 -OfflineHours 24 $stale = $results | Where-Object { $_.ComputerName -eq 'STALE-SRV-01' } $stale.Status | Should -Be 'Stale' } It 'Should classify very old heartbeat as Offline' { $results = Get-RegistrationHeartbeat -RegistrationPath $script:tempHbDir -StaleHours 2 -OfflineHours 24 $offline = $results | Where-Object { $_.ComputerName -eq 'OFFLINE-SRV-01' } $offline.Status | Should -Be 'Offline' } It 'Should classify systems without JSON file as Never' { $results = Get-RegistrationHeartbeat -RegistrationPath $script:tempHbDir $never = $results | Where-Object { $_.ComputerName -eq 'MANUAL-SRV-01' } $never.Status | Should -Be 'Never' } It 'Should include HoursSinceHeartbeat for systems with JSON files' { $results = Get-RegistrationHeartbeat -RegistrationPath $script:tempHbDir $active = $results | Where-Object { $_.ComputerName -eq 'ACTIVE-SRV-01' } $active.HoursSinceHeartbeat | Should -Not -BeNullOrEmpty } It 'Should have null HoursSinceHeartbeat for Never status' { $results = Get-RegistrationHeartbeat -RegistrationPath $script:tempHbDir $never = $results | Where-Object { $_.ComputerName -eq 'MANUAL-SRV-01' } $never.HoursSinceHeartbeat | Should -BeNullOrEmpty } } Context 'Manifest Validation' { BeforeAll { $manifest = Test-ModuleManifest -Path "$PSScriptRoot\..\AD-LinuxInventory.psd1" } It 'Should have version 2.0.0' { $manifest.Version.ToString() | Should -Be '2.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 original 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 include new tags for v2.0' { $tags = $manifest.PrivateData.PSData.Tags $tags | Should -Contain 'macOS' $tags | Should -Contain 'FreeBSD' $tags | Should -Contain 'Unix' $tags | Should -Contain 'SelfRegistration' $tags | Should -Contain 'Agent' } It 'Should require PowerShell 5.1' { $manifest.PowerShellVersion.ToString() | Should -Be '5.1' } It 'Should export exactly 7 functions in manifest' { $manifest.ExportedFunctions.Count | Should -Be 7 } } } |