lib/rules/version_tracking/major_branch_missing/major_branch_missing.Tests.ps1

#############################################################################
# Tests for Rule: major_branch_missing
#############################################################################

BeforeAll {
    . "$PSScriptRoot/../../../StateModel.ps1"
    . "$PSScriptRoot/../../../ValidationRules.ps1"
    . "$PSScriptRoot/../../../RemediationActions.ps1"
    . "$PSScriptRoot/major_branch_missing.ps1"
}

Describe "major_branch_missing" {
    Context "Condition - AppliesWhen floating-versions-use is branches" {
        It "should return missing major when patch exists but major branch doesn't" {
            $state = [RepositoryState]::new()
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "abc123", "tag")
            # No v1 branch
            $state.IgnoreVersions = @()
            
            $config = @{ 'floating-versions-use' = 'branches' }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            $result.Count | Should -Be 1
            $result[0].Major | Should -Be 1
        }
        
        It "should not return major when major branch exists" {
            $state = [RepositoryState]::new()
            $state.Branches += [VersionRef]::new("v1", "refs/heads/v1", "abc123", "branch")
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "abc123", "tag")
            $state.IgnoreVersions = @()
            
            $config = @{ 'floating-versions-use' = 'branches' }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            $result.Count | Should -Be 0
        }
        
        It "should return empty when floating-versions-use is 'tags'" {
            $state = [RepositoryState]::new()
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "abc123", "tag")
            # No v1 branch
            $state.IgnoreVersions = @()
            
            $config = @{ 'floating-versions-use' = 'tags' }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            $result.Count | Should -Be 0
        }
        
        It "should return empty when no patches exist" {
            $state = [RepositoryState]::new()
            # No patch versions at all
            $state.IgnoreVersions = @()
            
            $config = @{ 'floating-versions-use' = 'branches' }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            $result.Count | Should -Be 0
        }
        
        It "should return multiple missing majors" {
            $state = [RepositoryState]::new()
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "abc123", "tag")
            $state.Tags += [VersionRef]::new("v2.0.0", "refs/tags/v2.0.0", "def456", "tag")
            # No v1 or v2 branches
            $state.IgnoreVersions = @()
            
            $config = @{ 'floating-versions-use' = 'branches' }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            $result.Count | Should -Be 2
        }
        
        It "should skip ignored patch versions" {
            $state = [RepositoryState]::new()
            $ignored = [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "abc123", "tag")
            $ignored.IsIgnored = $true
            $state.Tags += $ignored
            $state.IgnoreVersions = @("v1.0.0")
            
            $config = @{ 'floating-versions-use' = 'branches' }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            $result.Count | Should -Be 0
        }
        
        It "should find patches from both tags and branches" {
            $state = [RepositoryState]::new()
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "abc123", "tag")
            $state.Branches += [VersionRef]::new("v2.0.0", "refs/heads/v2.0.0", "def456", "branch")
            # No v1 or v2 major branches
            $state.IgnoreVersions = @()
            
            $config = @{ 'floating-versions-use' = 'branches' }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            $result.Count | Should -Be 2
        }
        
        It "should NOT return missing major when only prerelease patches exist and ignore-preview-releases is true" {
            # This test demonstrates the bug where Condition returns a missing major branch
            # even though the only patches are prereleases and should be ignored.
            
            $state = [RepositoryState]::new()
            # v2.0.0 exists but is a prerelease
            $state.Tags += [VersionRef]::new("v2.0.0", "refs/tags/v2.0.0", "preview123", "tag")
            $state.IgnoreVersions = @()
            
            # Mark v2.0.0 as prerelease via ReleaseInfo
            $prereleaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 1
                draft = $false
                prerelease = $true
                html_url = "https://github.com/test/test/releases/tag/v2.0.0"
                target_commitish = "preview123"
                immutable = $false
            }
            $state.Releases += [ReleaseInfo]::new($prereleaseData)
            
            $config = @{ 
                'floating-versions-use' = 'branches'
                'ignore-preview-releases' = $true
            }
            $result = & $Rule_MajorBranchMissing.Condition $state $config
            
            # Should NOT return missing major when only prerelease patches exist
            # because prereleases are filtered out when ignore-preview-releases is true
            $result.Count | Should -Be 0
        }
    }

    Context "CreateIssue" {
        It "should create issue with correct details" {
            $state = [RepositoryState]::new()
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "abc123", "tag")
            $state.IgnoreVersions = @()
            
            $item = [PSCustomObject]@{ Major = 1 }
            $config = @{ 'ignore-preview-releases' = $true }
            
            $issue = & $Rule_MajorBranchMissing.CreateIssue $item $state $config
            
            $issue.Type | Should -Be "missing_major_version"
            $issue.Severity | Should -Be "error"
            $issue.Version | Should -Be "v1"
            $issue.ExpectedSha | Should -Be "abc123"
            $issue.RemediationAction | Should -Not -BeNullOrEmpty
            $issue.RemediationAction.GetType().Name | Should -Be "CreateBranchAction"
        }
        
        It "should configure CreateBranchAction with highest patch SHA" {
            $state = [RepositoryState]::new()
            $state.Tags += [VersionRef]::new("v2.0.0", "refs/tags/v2.0.0", "old123", "tag")
            $state.Tags += [VersionRef]::new("v2.1.0", "refs/tags/v2.1.0", "new456", "tag")
            $state.IgnoreVersions = @()
            
            $item = [PSCustomObject]@{ Major = 2 }
            $config = @{ 'ignore-preview-releases' = $true }
            
            $issue = & $Rule_MajorBranchMissing.CreateIssue $item $state $config
            
            $issue.RemediationAction.BranchName | Should -Be "v2"
            $issue.RemediationAction.Sha | Should -Be "new456"
        }
    }
    
    Context "Prerelease Filtering" {
        It "should use non-prerelease SHA in CreateIssue when ignore-preview-releases is true" {
            $state = [RepositoryState]::new()
            # v1.0.0 is stable
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "stable123", "tag")
            # v1.1.0 is prerelease (higher version but should be excluded)
            $state.Tags += [VersionRef]::new("v1.1.0", "refs/tags/v1.1.0", "prerel456", "tag")
            $state.IgnoreVersions = @()
            
            # Mark v1.1.0 as prerelease via ReleaseInfo
            $prereleaseData = [PSCustomObject]@{
                tag_name = "v1.1.0"
                id = 2
                draft = $false
                prerelease = $true
                html_url = "https://github.com/test/test/releases/tag/v1.1.0"
                target_commitish = "prerel456"
                immutable = $false
            }
            $state.Releases += [ReleaseInfo]::new($prereleaseData)
            
            $item = [PSCustomObject]@{ Major = 1 }
            $config = @{ 'ignore-preview-releases' = $true }
            
            $issue = & $Rule_MajorBranchMissing.CreateIssue $item $state $config
            
            # Should use SHA from v1.0.0 (stable), not v1.1.0 (prerelease)
            $issue.ExpectedSha | Should -Be "stable123"
        }
        
        It "should use prerelease SHA in CreateIssue when ignore-preview-releases is false" {
            $state = [RepositoryState]::new()
            # v1.0.0 is stable
            $state.Tags += [VersionRef]::new("v1.0.0", "refs/tags/v1.0.0", "stable123", "tag")
            # v1.1.0 is prerelease (should be included)
            $state.Tags += [VersionRef]::new("v1.1.0", "refs/tags/v1.1.0", "prerel456", "tag")
            $state.IgnoreVersions = @()
            
            # Mark v1.1.0 as prerelease via ReleaseInfo
            $prereleaseData = [PSCustomObject]@{
                tag_name = "v1.1.0"
                id = 2
                draft = $false
                prerelease = $true
                html_url = "https://github.com/test/test/releases/tag/v1.1.0"
                target_commitish = "prerel456"
                immutable = $false
            }
            $state.Releases += [ReleaseInfo]::new($prereleaseData)
            
            $item = [PSCustomObject]@{ Major = 1 }
            $config = @{ 'ignore-preview-releases' = $false }
            
            $issue = & $Rule_MajorBranchMissing.CreateIssue $item $state $config
            
            # Should use SHA from v1.1.0 (prerelease included)
            $issue.ExpectedSha | Should -Be "prerel456"
        }
    }
}