Tests/Tests.ps1

<#
.SYNOPSIS
    Pester tests for M365PlannerPro module
 
.DESCRIPTION
    Comprehensive test suite for M365PlannerPro module including:
    - Module structure validation
    - Function existence and parameter validation
    - Help content validation
    - CSV format validation
 
.NOTES
    Author: Microsoft Planner Pro Team
    Version: 1.0.0
    Requires: Pester module (Install-Module Pester -Force)
     
    Run tests: Invoke-Pester .\Tests.ps1 -Output Detailed
#>


BeforeAll {
    $ModulePath = Split-Path -Parent $PSCommandPath
    $ModuleName = 'M365PlannerPro'
    $ManifestPath = Join-Path $ModulePath "$ModuleName.psd1"
    
    if (Get-Module $ModuleName) {
        Remove-Module $ModuleName -Force
    }
    Import-Module $ManifestPath -Force
}

Describe "Module Structure Tests" {
    Context "Module Manifest" {
        It "Should have a valid module manifest" {
            $ManifestPath = Join-Path $ModulePath "$ModuleName.psd1"
            $ManifestPath | Should -Exist
        }

        It "Should have a valid module file" {
            $ModuleFile = Join-Path $ModulePath "$ModuleName.psm1"
            $ModuleFile | Should -Exist
        }

        It "Should import without errors" {
            { Import-Module $ManifestPath -Force -ErrorAction Stop } | Should -Not -Throw
        }

        It "Should have required module version" {
            $manifest = Test-ModuleManifest -Path $ManifestPath
            $manifest.Version | Should -BeOfType [version]
            $manifest.Version.Major | Should -BeGreaterOrEqual 1
        }

        It "Should require PowerShell 7.0 or higher" {
            $manifest = Test-ModuleManifest -Path $ManifestPath
            $manifest.PowerShellVersion | Should -BeGreaterOrEqual ([version]'7.0')
        }

        It "Should have Microsoft.Graph.Planner as a required module" {
            $manifest = Test-ModuleManifest -Path $ManifestPath
            $manifest.RequiredModules.Name | Should -Contain 'Microsoft.Graph.Planner'
        }
    }

    Context "Public Folder" {
        It "Should have a Public folder" {
            $PublicFolder = Join-Path $ModulePath "Public"
            $PublicFolder | Should -Exist
        }

        It "Should contain .ps1 files in Public folder" {
            $PublicFiles = Get-ChildItem (Join-Path $ModulePath "Public") -Filter *.ps1
            $PublicFiles.Count | Should -BeGreaterThan 0
        }
    }

    Context "Documentation" {
        It "Should have README.md" {
            Join-Path $ModulePath "README.md" | Should -Exist
        }

        It "Should have CHANGELOG.md" {
            Join-Path $ModulePath "CHANGELOG.md" | Should -Exist
        }

        It "Should have example-tasks.csv" {
            Join-Path $ModulePath "example-tasks.csv" | Should -Exist
        }
    }
}

Describe "Function Export Tests" {
    Context "Exported Functions" {
        $ExportedFunctions = @(
            'Copy-M365PPlan',
            'Import-M365PTasksFromCsv',
            'Get-M365PUserWorkload',
            'Update-M365PTaskSmart'
        )

        foreach ($FunctionName in $ExportedFunctions) {
            It "Should export function: $FunctionName" {
                $Module = Get-Module $ModuleName
                $Module.ExportedCommands.Keys | Should -Contain $FunctionName
            }

            It "Should have help content for: $FunctionName" {
                $Help = Get-Help $FunctionName
                $Help | Should -Not -BeNullOrEmpty
                $Help.Synopsis | Should -Not -BeNullOrEmpty
            }

            It "Should have examples for: $FunctionName" {
                $Help = Get-Help $FunctionName
                $Help.Examples | Should -Not -BeNullOrEmpty
                $Help.Examples.Example.Count | Should -BeGreaterThan 0
            }
        }
    }
}

Describe "Copy-M365PPlan Function Tests" {
    Context "Parameters" {
        It "Should have SourceGroupId parameter" {
            (Get-Command Copy-M365PPlan).Parameters.Keys | Should -Contain 'SourceGroupId'
        }

        It "Should have SourcePlanId parameter" {
            (Get-Command Copy-M365PPlan).Parameters.Keys | Should -Contain 'SourcePlanId'
        }

        It "Should have DestinationGroupId parameter" {
            (Get-Command Copy-M365PPlan).Parameters.Keys | Should -Contain 'DestinationGroupId'
        }

        It "Should have optional NewPlanTitle parameter" {
            $Param = (Get-Command Copy-M365PPlan).Parameters['NewPlanTitle']
            $Param | Should -Not -BeNullOrEmpty
            $Param.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $false
        }
    }

    Context "Help Content" {
        $Help = Get-Help Copy-M365PPlan

        It "Should have Synopsis" {
            $Help.Synopsis | Should -Not -BeNullOrEmpty
        }

        It "Should have Description" {
            $Help.Description | Should -Not -BeNullOrEmpty
        }

        It "Should have at least 2 examples" {
            $Help.Examples.Example.Count | Should -BeGreaterOrEqual 2
        }
    }
}

Describe "Import-M365PTasksFromCsv Function Tests" {
    Context "Parameters" {
        It "Should have PlanId parameter" {
            (Get-Command Import-M365PTasksFromCsv).Parameters.Keys | Should -Contain 'PlanId'
        }

        It "Should have CsvPath parameter" {
            (Get-Command Import-M365PTasksFromCsv).Parameters.Keys | Should -Contain 'CsvPath'
        }

        It "Should have GroupId parameter" {
            (Get-Command Import-M365PTasksFromCsv).Parameters.Keys | Should -Contain 'GroupId'
        }
    }

    Context "CSV Validation" {
        It "Should have example CSV file" {
            $CsvPath = Join-Path $ModulePath "example-tasks.csv"
            $CsvPath | Should -Exist
        }

        It "Example CSV should have required columns" {
            $CsvPath = Join-Path $ModulePath "example-tasks.csv"
            $Csv = Import-Csv $CsvPath
            $RequiredColumns = @('Title', 'BucketName')
            foreach ($Column in $RequiredColumns) {
                $Csv[0].PSObject.Properties.Name | Should -Contain $Column
            }
        }

        It "Example CSV should have valid content" {
            $CsvPath = Join-Path $ModulePath "example-tasks.csv"
            $Csv = Import-Csv $CsvPath
            $Csv.Count | Should -BeGreaterThan 0
            $Csv[0].Title | Should -Not -BeNullOrEmpty
        }
    }
}

Describe "Get-M365PUserWorkload Function Tests" {
    Context "Parameters" {
        It "Should have optional GroupId parameter" {
            $Param = (Get-Command Get-M365PUserWorkload).Parameters['GroupId']
            $Param | Should -Not -BeNullOrEmpty
            $Param.ParameterSets.Values.IsMandatory | Should -Contain $false
        }

        It "Should have optional PlanId parameter" {
            (Get-Command Get-M365PUserWorkload).Parameters.Keys | Should -Contain 'PlanId'
        }

        It "Should have IncludeCompletedTasks switch" {
            $Param = (Get-Command Get-M365PUserWorkload).Parameters['IncludeCompletedTasks']
            $Param.SwitchParameter | Should -Be $true
        }

        It "Should have Top parameter" {
            (Get-Command Get-M365PUserWorkload).Parameters.Keys | Should -Contain 'Top'
        }
    }
}

Describe "Update-M365PTaskSmart Function Tests" {
    Context "Parameters" {
        It "Should have TaskId parameter" {
            $Param = (Get-Command Update-M365PTaskSmart).Parameters['TaskId']
            $Param | Should -Not -BeNullOrEmpty
        }

        It "TaskId should accept pipeline input" {
            $Param = (Get-Command Update-M365PTaskSmart).Parameters['TaskId']
            $Param.Attributes.ValueFromPipeline | Should -Contain $true
        }

        It "Should have optional Title parameter" {
            (Get-Command Update-M365PTaskSmart).Parameters.Keys | Should -Contain 'Title'
        }

        It "Should have PercentComplete parameter" {
            (Get-Command Update-M365PTaskSmart).Parameters.Keys | Should -Contain 'PercentComplete'
        }

        It "Should have MaxRetries parameter" {
            (Get-Command Update-M365PTaskSmart).Parameters.Keys | Should -Contain 'MaxRetries'
        }

        It "Should support WhatIf" {
            (Get-Command Update-M365PTaskSmart).Parameters.Keys | Should -Contain 'WhatIf'
        }
    }

    Context "Parameter Validation" {
        It "PercentComplete should have range validation" {
            $Param = (Get-Command Update-M365PTaskSmart).Parameters['PercentComplete']
            $ValidateRange = $Param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] }
            $ValidateRange | Should -Not -BeNullOrEmpty
            $ValidateRange.MinRange | Should -Be 0
            $ValidateRange.MaxRange | Should -Be 100
        }

        It "Priority should have range validation" {
            $Param = (Get-Command Update-M365PTaskSmart).Parameters['Priority']
            $ValidateRange = $Param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] }
            $ValidateRange | Should -Not -BeNullOrEmpty
            $ValidateRange.MinRange | Should -Be 0
            $ValidateRange.MaxRange | Should -Be 10
        }
    }
}

Describe "PowerShell Best Practices" {
    Context "Function Naming" {
        $Functions = Get-Command -Module $ModuleName

        foreach ($Function in $Functions) {
            It "$($Function.Name) should follow Verb-Noun naming" {
                $Function.Name | Should -Match '^[A-Z][a-z]+-[A-Z][a-zA-Z0-9]*$'
            }

            It "$($Function.Name) should use approved verb" {
                $Verb = $Function.Name.Split('-')[0]
                $ApprovedVerbs = Get-Verb | Select-Object -ExpandProperty Verb
                $ApprovedVerbs | Should -Contain $Verb
            }
        }
    }

    Context "Comment-Based Help" {
        $Functions = Get-Command -Module $ModuleName

        foreach ($Function in $Functions) {
            $Help = Get-Help $Function.Name

            It "$($Function.Name) should have Synopsis" {
                $Help.Synopsis | Should -Not -BeNullOrEmpty
                $Help.Synopsis | Should -Not -Match $Function.Name  # Should not be auto-generated
            }

            It "$($Function.Name) should have Description" {
                $Help.Description | Should -Not -BeNullOrEmpty
            }

            It "$($Function.Name) should have at least one Example" {
                $Help.Examples.Example.Count | Should -BeGreaterOrEqual 1
            }

            It "$($Function.Name) should have parameter descriptions" {
                $Params = $Help.Parameters.Parameter
                foreach ($Param in $Params) {
                    $Param.Description | Should -Not -BeNullOrEmpty
                }
            }
        }
    }
}

Describe "Integration Tests" -Tag "Integration" {
    Context "Module Load Performance" {
        It "Should load module in reasonable time" {
            $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
            Remove-Module $ModuleName -Force -ErrorAction SilentlyContinue
            Import-Module $ManifestPath -Force
            $Stopwatch.Stop()
            
            $Stopwatch.ElapsedMilliseconds | Should -BeLessThan 5000  # 5 seconds
        }
    }

    Context "Function Execution (Dry Run)" {
        It "Update-M365PTaskSmart should support WhatIf without errors" {
            { Update-M365PTaskSmart -TaskId "test-id" -Title "Test" -WhatIf } | Should -Not -Throw
        }
    }
}

AfterAll {
    Remove-Module $ModuleName -Force -ErrorAction SilentlyContinue
}