Tests/Unit/Phase2.Tests.ps1

BeforeAll {
    $publicDir = "d:\Project\SPClean\Public"
    $privateDir = "d:\Project\SPClean\Private"
    
    . "$privateDir\Calculate-SPCScoreInternal.ps1"
    . "$privateDir\Get-SPCLibraryBrokenInheritanceInternal.ps1"
    . "$privateDir\Test-SPCConnection.ps1"
    
    if (Test-Path "$publicDir\Report\Get-SPCPermissionHealthScore.ps1") {
        . "$publicDir\Report\Get-SPCPermissionHealthScore.ps1"
    }
    if (Test-Path "$publicDir\Scan\Get-SPCBrokenInheritance.ps1") {
        . "$publicDir\Scan\Get-SPCBrokenInheritance.ps1"
    }
    if (Test-Path "$publicDir\Report\Compare-SPCPermissionSnapshot.ps1") {
        . "$publicDir\Report\Compare-SPCPermissionSnapshot.ps1"
    }

    # Dummy module properties
    $script:SPCContext = @{
        TenantId = "tenant"
        IsConnected = $true
    }
}

Describe "Phase 2 Unit Tests" {
    Context "Calculate-SPCScoreInternal" {
        It "F5-01: Tính điểm với rủi ro bằng 0" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 0 -HighRiskGuestCount 0 -OverPermissionedUserCount 0 -BrokenInheritanceSiteCount 0 -MissingOwnerSiteCount 0
            $res.OverallScore | Should -Be 100
            $res.Breakdown.OrphanedUserDeduction | Should -Be 0
        }
        It "F5-02: Trừ điểm Orphaned User chưa chạm trần" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 10 -HighRiskGuestCount 0 -OverPermissionedUserCount 0 -BrokenInheritanceSiteCount 0 -MissingOwnerSiteCount 0
            $res.OverallScore | Should -Be 80
            $res.Breakdown.OrphanedUserDeduction | Should -Be 20
        }
        It "F5-03: Trừ điểm Orphaned User vượt trần" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 20 -HighRiskGuestCount 0 -OverPermissionedUserCount 0 -BrokenInheritanceSiteCount 0 -MissingOwnerSiteCount 0
            $res.OverallScore | Should -Be 70
            $res.Breakdown.OrphanedUserDeduction | Should -Be 30
        }
        It "F5-04: Trừ điểm High-Risk Guest chưa chạm trần" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 0 -HighRiskGuestCount 10 -OverPermissionedUserCount 0 -BrokenInheritanceSiteCount 0 -MissingOwnerSiteCount 0
            $res.OverallScore | Should -Be 85
            $res.Breakdown.HighRiskGuestDeduction | Should -Be 15
        }
        It "F5-05: Trừ điểm High-Risk Guest vượt trần" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 0 -HighRiskGuestCount 20 -OverPermissionedUserCount 0 -BrokenInheritanceSiteCount 0 -MissingOwnerSiteCount 0
            $res.OverallScore | Should -Be 75
            $res.Breakdown.HighRiskGuestDeduction | Should -Be 25
        }
        It "F5-06: Trừ điểm Over-Permissioned User vượt trần" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 0 -HighRiskGuestCount 0 -OverPermissionedUserCount 15 -BrokenInheritanceSiteCount 0 -MissingOwnerSiteCount 0
            $res.OverallScore | Should -Be 80
            $res.Breakdown.OverPermissionedDeduction | Should -Be 20
        }
        It "F5-07: Trừ điểm Broken Inheritance & Missing Owner" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 0 -HighRiskGuestCount 0 -OverPermissionedUserCount 0 -BrokenInheritanceSiteCount 2 -MissingOwnerSiteCount 1
            $res.OverallScore | Should -Be 60
            $res.Breakdown.BrokenInheritanceDeduction | Should -Be 30
            $res.Breakdown.MissingOwnerDeduction | Should -Be 10
        }
        It "F5-08: Tổ hợp tất cả rủi ro vượt trần (Worst case)" {
            $res = Calculate-SPCScoreInternal -OrphanedUserCount 100 -HighRiskGuestCount 100 -OverPermissionedUserCount 100 -BrokenInheritanceSiteCount 10 -MissingOwnerSiteCount 10
            $res.OverallScore | Should -Be 0
        }
    }

    Context "Get-SPCLibraryBrokenInheritanceInternal" {
        It "F6-02: Có Folder bẻ gãy kế thừa" {
            Mock Get-PnPList {
                return @(
                    [PSCustomObject]@{
                        Title = "DocLib1"
                        BaseType = 'DocumentLibrary'
                        Hidden = $false
                        HasUniqueRoleAssignments = $false
                        RootFolder = [PSCustomObject]@{ ServerRelativeUrl = "/sites/test/DocLib1" }
                    }
                )
            }
            Mock Get-PnPListItem {
                return @(
                    [PSCustomObject]@{
                        Id = 1
                        HasUniqueRoleAssignments = $true
                        FileSystemObjectType = 'Folder'
                        FieldValues = @{ FileRef = "/sites/test/DocLib1/Folder1" }
                    }
                )
            }
            $res = Get-SPCLibraryBrokenInheritanceInternal -SiteUrl "https://test.sharepoint.com/sites/test"
            $res.Count | Should -Be 1
            $res[0].Path | Should -Be "/sites/test/DocLib1/Folder1"
            $res[0].Type | Should -Be "Folder"
        }
    }

    Context "Compare-SPCPermissionSnapshot" {
        BeforeAll {
            $baseline = @(
                @{ user = @{ upn = "admin@test.com" }; isSCA = $true; isOwner = $false; permissions = @() },
                @{ user = @{ upn = "owner@test.com" }; isSCA = $false; isOwner = $true; permissions = @() },
                @{ user = @{ upn = "user1@test.com" }; isSCA = $false; isOwner = $false; permissions = @("Full Control") },
                @{ user = @{ upn = "guest#EXT#@external.com" }; isSCA = $false; isOwner = $false; permissions = @() }
            ) | ConvertTo-Json -Depth 5
            
            $current = @(
                @{ user = @{ upn = "admin@test.com" }; isSCA = $true; isOwner = $false; permissions = @() },
                @{ user = @{ upn = "newadmin@test.com" }; isSCA = $true; isOwner = $false; permissions = @() },
                @{ user = @{ upn = "owner@test.com" }; isSCA = $false; isOwner = $true; permissions = @() },
                @{ user = @{ upn = "newowner@test.com" }; isSCA = $false; isOwner = $true; permissions = @() },
                @{ user = @{ upn = "user1@test.com" }; isSCA = $false; isOwner = $false; permissions = @("Full Control") },
                @{ user = @{ upn = "user2@test.com" }; isSCA = $false; isOwner = $false; permissions = @("Full Control") },
                @{ user = @{ upn = "guest#EXT#@external.com" }; isSCA = $false; isOwner = $false; permissions = @() },
                @{ user = @{ upn = "newguest#EXT#@external.com" }; isSCA = $false; isOwner = $false; permissions = @() }
            ) | ConvertTo-Json -Depth 5
            
            $baselinePath = "d:\Project\SPClean\Tests\Unit\baseline.json"
            $currentPath = "d:\Project\SPClean\Tests\Unit\current.json"
            Set-Content -Path $baselinePath -Value $baseline
            Set-Content -Path $currentPath -Value $current
        }
        
        It "F7-01 to F7-04: Phát hiện biến động" {
            $baselinePath = "d:\Project\SPClean\Tests\Unit\baseline.json"
            $currentPath = "d:\Project\SPClean\Tests\Unit\current.json"
            
            Mock Test-SPCConnection { return $true }
            
            $res = Compare-SPCPermissionSnapshot -BaselineSnapshotPath $baselinePath -CurrentSnapshotPath $currentPath
            
            $res.NewSCAOrOwners | Should -Contain "newadmin@test.com"
            $res.NewSCAOrOwners | Should -Contain "newowner@test.com"
            $res.NewDirectFullControlAssignments | Should -Contain "user2@test.com"
            $res.NewGuestAccounts | Should -Contain "newguest#EXT#@external.com"
        }
        
        AfterAll {
            Remove-Item "d:\Project\SPClean\Tests\Unit\baseline.json" -ErrorAction SilentlyContinue
            Remove-Item "d:\Project\SPClean\Tests\Unit\current.json" -ErrorAction SilentlyContinue
        }
    }
    
    Context "Security Test Cases" {
        It "SEC-01: Không rò rỉ Token/Secret trong luồng Verbose" {
            $verboseLog = @()
            # We mock write-verbose to capture it
            Mock Write-Verbose {
                param($Message)
                $script:verboseLog += $Message
            }
            # Run something that generates verbose output
            Calculate-SPCScoreInternal -OrphanedUserCount 0 -HighRiskGuestCount 0 -OverPermissionedUserCount 0 -BrokenInheritanceSiteCount 0 -MissingOwnerSiteCount 0 -Verbose
            
            $leak = $script:verboseLog | Where-Object { $_ -match "password|secret|token|bearer" }
            $leak | Should -BeNullOrEmpty
        }
    }
}