tests/PowerCraft.Release.Tests.ps1

BeforeAll {
    $ModulePath = Join-Path $PSScriptRoot '..' 'PowerCraft.Release.psd1'
    Import-Module $ModulePath -Force
}

Describe 'Get-SemanticVersion (via module internals)' {
    # Access private function through module scope
    BeforeAll {
        $module = Get-Module PowerCraft.Release
        $getSemVer = & $module { Get-Command Get-SemanticVersion }
    }

    It 'Parses simple version' {
        $result = & $module { Get-SemanticVersion -Version '1.2.3' }
        $result.Major | Should -Be 1
        $result.Minor | Should -Be 2
        $result.Patch | Should -Be 3
        $result.PreRelease | Should -BeNullOrEmpty
        $result.HasVPrefix | Should -BeFalse
    }

    It 'Parses version with v prefix' {
        $result = & $module { Get-SemanticVersion -Version 'v2.0.1' }
        $result.Major | Should -Be 2
        $result.Minor | Should -Be 0
        $result.Patch | Should -Be 1
        $result.HasVPrefix | Should -BeTrue
    }

    It 'Parses pre-release version' {
        $result = & $module { Get-SemanticVersion -Version 'v1.0.0-beta.1' }
        $result.Major | Should -Be 1
        $result.PreRelease | Should -Be 'beta.1'
    }

    It 'Parses version with build metadata' {
        $result = & $module { Get-SemanticVersion -Version '1.0.0+build.123' }
        $result.Build | Should -Be 'build.123'
    }

    It 'Throws on invalid version' {
        { & $module { Get-SemanticVersion -Version 'not-a-version' } } | Should -Throw
    }

    It 'Throws on partial version' {
        { & $module { Get-SemanticVersion -Version '1.2' } } | Should -Throw
    }
}

Describe 'Get-ProjectType (via module internals)' {
    BeforeAll {
        $module = Get-Module PowerCraft.Release
    }

    It 'Detects PowerShell module from .psd1' {
        $testDir = Join-Path $TestDrive 'ps-project'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        '@{ ModuleVersion = "1.0.0" }' | Set-Content (Join-Path $testDir 'Test.psd1')

        $result = & $module { param($p) Get-ProjectType -Path $p } $testDir
        $result.Type | Should -Be 'PowerShell'
        $result.FileName | Should -Be 'Test.psd1'
    }

    It 'Detects .NET project from .csproj' {
        $testDir = Join-Path $TestDrive 'dotnet-project'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        '<Project><PropertyGroup><Version>1.0.0</Version></PropertyGroup></Project>' |
            Set-Content (Join-Path $testDir 'App.csproj')

        $result = & $module { param($p) Get-ProjectType -Path $p } $testDir
        $result.Type | Should -Be 'DotNet'
    }

    It 'Detects Node.js from package.json' {
        $testDir = Join-Path $TestDrive 'node-project'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        '{"name":"test","version":"1.0.0"}' | Set-Content (Join-Path $testDir 'package.json')

        $result = & $module { param($p) Get-ProjectType -Path $p } $testDir
        $result.Type | Should -Be 'Node'
    }

    It 'Detects Python from pyproject.toml' {
        $testDir = Join-Path $TestDrive 'py-project'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        '[project]', 'version = "1.0.0"' | Set-Content (Join-Path $testDir 'pyproject.toml')

        $result = & $module { param($p) Get-ProjectType -Path $p } $testDir
        $result.Type | Should -Be 'Python'
    }

    It 'Returns Unknown when no project files found' {
        $testDir = Join-Path $TestDrive 'empty-project'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null

        $result = & $module { param($p) Get-ProjectType -Path $p } $testDir
        $result.Type | Should -Be 'Unknown'
    }

    It 'Respects priority: .psd1 over .csproj' {
        $testDir = Join-Path $TestDrive 'mixed-project'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        '@{ ModuleVersion = "1.0.0" }' | Set-Content (Join-Path $testDir 'Test.psd1')
        '<Project></Project>' | Set-Content (Join-Path $testDir 'App.csproj')

        $result = & $module { param($p) Get-ProjectType -Path $p } $testDir
        $result.Type | Should -Be 'PowerShell'
    }
}

Describe 'Get-PCProjectVersion' {
    It 'Reads version from PowerShell manifest' {
        $testDir = Join-Path $TestDrive 'ps-version'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        @"
@{
    ModuleVersion = '2.5.1'
    RootModule = 'Test.psm1'
}
"@
 | Set-Content (Join-Path $testDir 'Test.psd1')

        $result = Get-PCProjectVersion -Path $testDir
        $result.Version | Should -Be '2.5.1'
        $result.Parsed.Major | Should -Be 2
        $result.Parsed.Minor | Should -Be 5
        $result.Parsed.Patch | Should -Be 1
        $result.ProjectType.Type | Should -Be 'PowerShell'
    }

    It 'Reads version from .csproj' {
        $testDir = Join-Path $TestDrive 'dotnet-version'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        @"
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Version>3.1.4</Version>
  </PropertyGroup>
</Project>
"@
 | Set-Content (Join-Path $testDir 'App.csproj')

        $result = Get-PCProjectVersion -Path $testDir
        $result.Version | Should -Be '3.1.4'
        $result.Parsed.Major | Should -Be 3
    }

    It 'Reads version from package.json' {
        $testDir = Join-Path $TestDrive 'node-version'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        '{"name":"my-app","version":"0.9.2"}' | Set-Content (Join-Path $testDir 'package.json')

        $result = Get-PCProjectVersion -Path $testDir
        $result.Version | Should -Be '0.9.2'
    }

    It 'Reads version from pyproject.toml' {
        $testDir = Join-Path $TestDrive 'py-version'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        @"
[project]
name = "mypackage"
version = "1.4.0"
"@
 | Set-Content (Join-Path $testDir 'pyproject.toml')

        $result = Get-PCProjectVersion -Path $testDir
        $result.Version | Should -Be '1.4.0'
    }

    It 'Throws when no project type detected' {
        $testDir = Join-Path $TestDrive 'no-project'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null

        { Get-PCProjectVersion -Path $testDir } | Should -Throw '*No recognized project type*'
    }
}

Describe 'Set-PCProjectVersion' {
    It 'Updates version in PowerShell manifest' {
        $testDir = Join-Path $TestDrive 'ps-set'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        @"
@{
    ModuleVersion = '1.0.0'
    RootModule = 'Test.psm1'
}
"@
 | Set-Content (Join-Path $testDir 'Test.psd1')

        Set-PCProjectVersion -Version '1.1.0' -Path $testDir
        $result = Get-PCProjectVersion -Path $testDir
        $result.Version | Should -Be '1.1.0'
    }

    It 'Updates version in package.json' {
        $testDir = Join-Path $TestDrive 'node-set'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        '{"name":"app","version":"1.0.0"}' | Set-Content (Join-Path $testDir 'package.json')

        Set-PCProjectVersion -Version '2.0.0' -Path $testDir
        $pkg = Get-Content (Join-Path $testDir 'package.json') -Raw | ConvertFrom-Json
        $pkg.version | Should -Be '2.0.0'
    }

    It 'Updates version in pyproject.toml' {
        $testDir = Join-Path $TestDrive 'py-set'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        @"
[project]
name = "mypackage"
version = "1.0.0"
description = "A test"
"@
 | Set-Content (Join-Path $testDir 'pyproject.toml')

        Set-PCProjectVersion -Version '1.2.0' -Path $testDir
        $content = Get-Content (Join-Path $testDir 'pyproject.toml') -Raw
        $content | Should -Match 'version = "1.2.0"'
    }

    It 'Strips v prefix before writing' {
        $testDir = Join-Path $TestDrive 'ps-vprefix'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        "@{ ModuleVersion = '1.0.0'; RootModule = 'T.psm1' }" | Set-Content (Join-Path $testDir 'T.psd1')

        Set-PCProjectVersion -Version 'v3.0.0' -Path $testDir
        $result = Get-PCProjectVersion -Path $testDir
        $result.Version | Should -Be '3.0.0'
    }

    It 'Rejects invalid version format' {
        $testDir = Join-Path $TestDrive 'ps-invalid'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        "@{ ModuleVersion = '1.0.0'; RootModule = 'T.psm1' }" | Set-Content (Join-Path $testDir 'T.psd1')

        { Set-PCProjectVersion -Version 'bad' -Path $testDir } | Should -Throw
    }
}

Describe 'Get-PCNextVersion' {
    It 'Increments patch' {
        $testDir = Join-Path $TestDrive 'next-patch'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        "@{ ModuleVersion = '1.2.3'; RootModule = 'T.psm1' }" | Set-Content (Join-Path $testDir 'T.psd1')

        $result = Get-PCNextVersion -IncrementType Patch -Path $testDir
        $result | Should -Be '1.2.4'
    }

    It 'Increments minor and resets patch' {
        $testDir = Join-Path $TestDrive 'next-minor'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        "@{ ModuleVersion = '1.2.3'; RootModule = 'T.psm1' }" | Set-Content (Join-Path $testDir 'T.psd1')

        $result = Get-PCNextVersion -IncrementType Minor -Path $testDir
        $result | Should -Be '1.3.0'
    }

    It 'Increments major and resets minor+patch' {
        $testDir = Join-Path $TestDrive 'next-major'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        "@{ ModuleVersion = '1.2.3'; RootModule = 'T.psm1' }" | Set-Content (Join-Path $testDir 'T.psd1')

        $result = Get-PCNextVersion -IncrementType Major -Path $testDir
        $result | Should -Be '2.0.0'
    }

    It 'Works with explicit CurrentVersion parameter' {
        $result = Get-PCNextVersion -IncrementType Patch -CurrentVersion 'v5.0.9'
        $result | Should -Be '5.0.10'
    }

    It 'Works with version without v prefix' {
        $result = Get-PCNextVersion -IncrementType Minor -CurrentVersion '2.1.0'
        $result | Should -Be '2.2.0'
    }
}

Describe 'Test-PCReleaseReady' {
    It 'Fails when not in a git repository' {
        $testDir = Join-Path $TestDrive 'no-git'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        "@{ ModuleVersion = '1.0.0'; RootModule = 'T.psm1' }" | Set-Content (Join-Path $testDir 'T.psd1')

        $result = Test-PCReleaseReady -Path $testDir
        $result.Pass | Should -BeFalse
        $result.Errors | Should -Contain 'Not in a git repository'
    }

    Context 'In a git repository' {
        BeforeAll {
            $testDir = Join-Path $TestDrive 'git-ready'
            New-Item -ItemType Directory -Path $testDir -Force | Out-Null
            Push-Location $testDir
            git init --quiet
            git config user.email "test@test.com"
            git config user.name "Test"
            "@{ ModuleVersion = '1.0.0'; RootModule = 'T.psm1' }" | Set-Content (Join-Path $testDir 'T.psd1')
            git add -A
            git commit -m "init" --quiet
        }

        AfterAll {
            Pop-Location
        }

        It 'Detects uncommitted changes' {
            "change" | Set-Content (Join-Path $testDir 'dirty.txt')
            $result = Test-PCReleaseReady -Path $testDir
            $result.Pass | Should -BeFalse
            $result.Errors | Should -Contain 'Working directory has uncommitted changes'
            Remove-Item (Join-Path $testDir 'dirty.txt')
        }

        It 'Passes when clean on default branch' {
            # Rename branch to main
            git branch -m main 2>$null
            $result = Test-PCReleaseReady -Path $testDir
            # May warn about remote but should pass other checks
            $gitRepoCheck = $result.Checks | Where-Object { $_.Check -eq 'Git Repository' }
            $gitRepoCheck.Status | Should -Be 'PASS'
        }

        It 'Fails when on wrong branch' {
            git checkout -b feature/test --quiet
            $result = Test-PCReleaseReady -Path $testDir -RequireBranch @('main')
            $branchCheck = $result.Checks | Where-Object { $_.Check -eq 'Branch' }
            $branchCheck.Status | Should -Be 'FAIL'
            git checkout main --quiet
            git branch -D feature/test --quiet 2>$null
        }
    }
}

Describe 'Publish-PCRelease' {
    It 'DryRun shows plan without making changes' {
        $testDir = Join-Path $TestDrive 'release-dry'
        New-Item -ItemType Directory -Path $testDir -Force | Out-Null
        Push-Location $testDir
        try {
            git init --quiet
            git config user.email "test@test.com"
            git config user.name "Test"
            "@{ ModuleVersion = '1.0.0'; RootModule = 'T.psm1' }" | Set-Content 'T.psd1'
            git add -A
            git commit -m "init" --quiet
            git branch -m main

            $result = Publish-PCRelease -IncrementType Patch -DryRun -Path $testDir
            $result.Status | Should -Be 'DryRun'
            $result.Version | Should -Be '1.0.1'
            $result.Tag | Should -Be 'v1.0.1'

            # Version should NOT have changed
            $current = Get-PCProjectVersion -Path $testDir
            $current.Version | Should -Be '1.0.0'
        }
        finally {
            Pop-Location
        }
    }
}