Tests/Publish-ChocolateyPackage.Tests.ps1
|
Import-Module "$PSScriptRoot/../ChocoForge.psd1" -Force Describe 'Publish-ChocolateyPackage' { InModuleScope 'ChocoForge' { BeforeAll { $script:tempDir = Join-Path $env:TEMP 'chocoforge-test-publish' if (Test-Path $script:tempDir) { Remove-Item -Recurse -Force $script:tempDir } New-Item -ItemType Directory -Path $script:tempDir | Out-Null $script:packagePath = Join-Path $script:tempDir 'mypackage.1.2.3.nupkg' Set-Content -Path $script:packagePath -Value 'not-a-real-nupkg' } AfterAll { if (Test-Path $script:tempDir) { Remove-Item -Recurse -Force $script:tempDir } } It 'Names the package when an ordinary (non-409) push fails' { Mock Invoke-Chocolatey { [PSCustomObject]@{ ExitCode = 1; StdOut = '500 Internal Server Error'; StdErr = '' } } { Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl 'https://nuget.pkg.github.com/owner/index.json' -ApiKey 'k' } | Should -Throw '*mypackage*' } It 'Includes the underlying choco output in the failure message' { Mock Invoke-Chocolatey { [PSCustomObject]@{ ExitCode = 1; StdOut = '500 Internal Server Error'; StdErr = '' } } { Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl 'https://nuget.pkg.github.com/owner/index.json' -ApiKey 'k' } | Should -Throw '*500 Internal Server Error*' } It 'Returns the package path on success' { Mock Invoke-Chocolatey { [PSCustomObject]@{ ExitCode = 0; StdOut = ''; StdErr = '' } } $result = Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl 'https://nuget.pkg.github.com/owner/index.json' -ApiKey 'k' $result | Should -Be $script:packagePath } It 'Rewrites the community repository URL to the push endpoint' { Mock Invoke-Chocolatey { [PSCustomObject]@{ ExitCode = 0; StdOut = ''; StdErr = '' } } $null = Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl 'https://community.chocolatey.org/api/v2' -ApiKey 'k' Should -Invoke Invoke-Chocolatey -Times 1 -Exactly -ParameterFilter { $Arguments -contains 'https://push.chocolatey.org' } } It 'Throws when the package file does not exist' { { Publish-ChocolateyPackage -Path (Join-Path $script:tempDir 'missing.nupkg') -SourceUrl 'https://x' } | Should -Throw '*not found*' } It 'Returns the path without pushing under -WhatIf' { Mock Invoke-Chocolatey { [PSCustomObject]@{ ExitCode = 0; StdOut = ''; StdErr = '' } } $result = Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl 'https://x' -WhatIf $result | Should -Be $script:packagePath Should -Invoke Invoke-Chocolatey -Times 0 -Exactly } } } Describe 'Publish-ChocolateyPackage (-Force after a 409 conflict)' { InModuleScope 'ChocoForge' { BeforeAll { $script:tempDir = Join-Path $env:TEMP 'chocoforge-test-publish-force' if (Test-Path $script:tempDir) { Remove-Item -Recurse -Force $script:tempDir } New-Item -ItemType Directory -Path $script:tempDir | Out-Null $script:packagePath = Join-Path $script:tempDir 'mypackage.1.2.3.nupkg' Set-Content -Path $script:packagePath -Value 'not-a-real-nupkg' $script:githubUrl = 'https://nuget.pkg.github.com/fdcastel/index.json' } AfterAll { if (Test-Path $script:tempDir) { Remove-Item -Recurse -Force $script:tempDir } } BeforeEach { # First push conflicts, the retry after deletion succeeds. $script:pushCount = 0 Mock Invoke-Chocolatey { $script:pushCount++ if ($script:pushCount -eq 1) { [PSCustomObject]@{ ExitCode = 1; StdOut = 'Response status code does not indicate success: 409 (Conflict).'; StdErr = '' } } else { [PSCustomObject]@{ ExitCode = 0; StdOut = ''; StdErr = '' } } } Mock Invoke-RestMethod { if ($Method -eq 'Delete') { return $null } @( [PSCustomObject]@{ id = 111; name = '1.2.2' }, [PSCustomObject]@{ id = 222; name = '1.2.3' } ) } } It 'Deletes the conflicting version and retries the push' { $result = Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl $script:githubUrl -ApiKey 'k' -Force $result | Should -Be $script:packagePath Should -Invoke Invoke-Chocolatey -Times 2 -Exactly # original push + retry Should -Invoke Invoke-RestMethod -Times 1 -Exactly -ParameterFilter { $Method -eq 'Delete' } } It 'Targets the version id matching the package file name' { # Version '1.2.3' is parsed from the file name, so id 222 must be the one deleted. $null = Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl $script:githubUrl -ApiKey 'k' -Force Should -Invoke Invoke-RestMethod -Times 1 -Exactly -ParameterFilter { $Method -eq 'Delete' -and $Uri -like '*/222' } } It 'Builds the versions URL from the package name and repository owner' { $null = Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl $script:githubUrl -ApiKey 'k' -Force Should -Invoke Invoke-RestMethod -ParameterFilter { $Uri -eq 'https://api.github.com/users/fdcastel/packages/nuget/mypackage/versions' } } It 'Throws when the conflicting version is not present in the feed' { Mock Invoke-RestMethod { @([PSCustomObject]@{ id = 111; name = '9.9.9' }) } { Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl $script:githubUrl -ApiKey 'k' -Force } | Should -Throw "*Version '1.2.3' not found for package 'mypackage'*" } It 'Names the package when the retry also fails' { Mock Invoke-Chocolatey { $script:pushCount++ if ($script:pushCount -eq 1) { [PSCustomObject]@{ ExitCode = 1; StdOut = '409 (Conflict)'; StdErr = '' } } else { [PSCustomObject]@{ ExitCode = 1; StdOut = 'still broken'; StdErr = '' } } } { Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl $script:githubUrl -ApiKey 'k' -Force } | Should -Throw '*mypackage after force*' } It 'Refuses to force-push to a non-GitHub source' { { Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl 'https://gitlab.com/api/v4/projects/1/packages/nuget/index.json' -ApiKey 'k' -Force } | Should -Throw '*Force push not supported*' } It 'Does not attempt a forced delete when -Force is absent' { { Publish-ChocolateyPackage -Path $script:packagePath -SourceUrl $script:githubUrl -ApiKey 'k' } | Should -Throw '*mypackage*' Should -Invoke Invoke-RestMethod -Times 0 -Exactly } } } |