fix: Cover Publish-PSBuildModule and fail the build on a failed publish - #204
Merged
Conversation
Publish-PSBuildModule was one of two public functions with no coverage of its own, and the worse of the pair: it is the function that pushes a module to a package repository, and its measured coverage against the built module was 0 of 21 instructions -- literally zero, not the artifact of background-job instrumentation that explains the other 0% readings. The only defect ever found in it, the blank validation message from an undefined PathDoesNotExist string (#187), was found by auditing en-US/Messages.psd1 rather than by exercising the function. Nobody had looked at it directly. Looking at it directly found #203. Publish-Module reports a failed publish as a non-terminating error, so at the default preference Publish-PSBuildModule returned normally after a publish that did not happen and the Publish task in both psakeFile.ps1 and IB.tasks.ps1 reported success. On the release workflow that is a green build with no artifact in the gallery. Publish-Module is now called with -ErrorAction Stop, forwarded through the same $PSBoundParameters loop that already forwards NuGetApiKey and Credential so an explicit -ErrorAction from the caller still wins. Reverting the one-line default turns four of the new tests red, including the one that publishes to a repository that was never registered. The tests cover the command surface, both -Path validation branches asserted on message text rather than on the bare fact of a throw, what each authentication mode forwards to Publish-Module, and an end-to-end publish to a temporary file-based PSRepository. The end-to-end case needs no network -- the same publish was measured behind an unreachable proxy -- and the repository is unregistered in AfterAll so the machine's repository list is left as it was found. Coverage is now 22 of 22 instructions. Every mock is declared with -ModuleName 'PowerShellBuild'. That was verified rather than assumed: stripping -ModuleName from a copy of the file turns eleven tests red because the calls reach the real PowerShellGet instead. Closes #103 Closes #203 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE
instructions/repository-specific.instructions.md keeps a table of every test file and what it covers. #188 corrected that table when it listed 5 of 15 files; adding a test file without adding a row puts it straight back out of date. Caught by the code review of this pull request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE
…ildmodule-tests # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tests/Publish-PSBuildModule.tests.ps1— 31 tests, taking the function from 0 of 21 instructions covered to 22 of 22.Publish-PSBuildModulereported success for a publish that never happened.CHANGELOG.mdentry, for the fix only. The tests are not user-facing and get none.Closes #103. Closes #203.
The defect
Publish-Modulereports a failed publish as a non-terminating error — an unregistered repository, a rejected API key, a repository the credential cannot authenticate to.Publish-PSBuildModulecalled it at the default$ErrorActionPreference, so the error went to the error stream and the command returned normally.Publish-PSBuildModulereturned normally after it, and thePublishtask in bothpsakeFile.ps1andIB.tasks.ps1reported success.Measured against the built module on
main:…and then it returns. Nothing throws. On
.github/workflows/publish.yaml, and on any consumer's release workflow, that is a green build with no artifact in the gallery.The fix is
-ErrorAction Stopon thePublish-Modulecall, added to the same$PSBoundParametersforwarding loop that already carriesNuGetApiKeyandCredential, so an explicit-ErrorActionfrom the caller still wins. Evidence it is load-bearing: reverting that one default and rebuilding turns four of the new tests red —Fails when the repository is not registered,Asks Publish-Module to stop on error,Forwards an explicit ErrorAction instead of the default, and the negative control.I filed it as its own issue rather than folding it silently into a test PR, but fixed it here because the change is one line and the test that catches it belongs in this file.
What is covered
ApiKeyalias,Credentialtyped asPSCredential, and the single-parameter-set shapeValidateScriptbranches, asserted on message text-Version, and the verbose messagePSRepositoryOn the parameter sets
The issue and the function's own
[CmdletBinding(DefaultParameterSetName = 'ApiKey')]both imply anApiKeyset and aCredentialset. There are none. No parameter carries aParameterSetName, soApiKeyis the only set and it holds all five parameters — which is what makes the third documented example, passing an API key and a credential, legal. I did not change this: the shipped tasks add each credential independently, so a consumer with both settings populated sends both, and splitting the sets would break them.Puts every parameter in a single parameter setpins the shape so a later split has to be deliberate. What the issue calls two parameter sets is covered as two authentication modes.On message text, not
Should -Throw$LocalizedData.Missing -f $valuereturns an empty string rather than throwing, so aValidateScriptreading a keyMessages.psd1never defined fails validation with no text at all — and a bareShould -Throwis perfectly happy with that. It is exactly how #187 survived until the string table was audited by hand. Both validation tests read the message:*Path does not exist*plus the path itself, and*The Path argument must be a folder*.Was the local repository case feasible? Yes.
It works, it needs no network, and it is in the PR.
Register-PSRepositoryagainst a$TestDrivedirectory,Publish-PSBuildModuleto it, then assert the.nupkglands andFind-Moduleresolves it from that repository. Measured:HTTP_PROXY/HTTPS_PROXYpointed at127.0.0.1:9). It succeeded, and faster — 3.0s versus 10.4s — which is itself the evidence that the 10.4s run had been reaching out and the 3.0s one could not.dotnetCLI when it is present, andnuget.exewas never downloaded —%LOCALAPPDATA%\…\PowerShellGet\NuGet.exedid not exist before the probes and does not exist after them.Machine state: the repository name is unique per run (
PSBuildTestRepository<8 hex>), andAfterAllunregisters it.AfterAllruns even when theBeforeAllpublish throws, so a failed publish still leaves the registered-repository list as it was found. Verified after every probe:Get-PSRepositoryreportsPSGalleryand nothing else. Nothing was installed and no module state was changed.The one concession to portability is a discovery-time guard: the context skips if
Register-PSRepository,Publish-Module, and a packaging tool (dotnetornuget) are not all resolvable. Every supported host and every CI runner has them, so this runs rather than skips — it guards against a machine that cannot package at all, not an expected outcome. The suite reports 3 skipped both before and after this PR, so nothing here is silently skipping.How I verified the mocks actually intercept
Two ways, because this repository has been bitten before —
tests/Get-PSBuildCertificate.tests.ps1carriedMockwithout-ModuleNamelong enough for a real defect to survive it.Negative controlcontext runs the same call with nothing mocked. It reaches the realPublish-Module, which cannot resolve the repository, and throws. If a mock in the forwarding context were declared without-ModuleName, its call would fall through to that same real command.-ModuleName 'PowerShellBuild'from everyMockandShould -Invoke, and ran it. Eleven tests went red withUnable to find repository 'InternalRepository'raised fromPSModule.psm1insidePublish-PSBuildModule. The mocks intercept; without-ModuleNamethey do not, and the file says so loudly rather than passing.Other observations, not changed here
-Versionis mandatory but never reachesPublish-Module. It is used only in the verbose message; the version that actually gets published is the one in the manifest under-Path.Does not forward the versionpins that, so a later change that starts forwarding it is a deliberate one.-Pathis typed[System.IO.FileInfo]for what validation insists must be a directory. It works, because only the string form is ever used, but[System.IO.DirectoryInfo]or[string]would say what is meant. Not changed — it is a public parameter type and a cosmetic break is not worth it before 1.0.0.### Fixedrather than adding a row to the Unreleased preamble's breaks table, since that table's rows each have a matching entry in the migration guide and this is a fix rather than a break. Happy to promote it if you would rather consumers meet it there.Test Plan
pwsh -NoProfile -c "./build.ps1 -Task Test"— 534 passed, 0 failed, 3 skipped (baseline onmain: 503 / 0 / 3; +31 from this file, same 3 skips)Publish-PSBuildModule: 22 of 22 instructions, up from 0 of 21ErrorActiondefault fails 4 of the new tests-ModuleNamefails 11Get-PSRepositoryunchanged after every runCHANGELOG.mdstays CRLF throughoutBreaking Changes
None to the API. One behavioral change worth calling out: a publish that has been failing silently will now fail the build. That is the point of #203, but it means a consumer whose publish was quietly broken finds out at this release.
Note on concurrency
Another agent is working #98 and #201 in
tests/build.tests.ps1,tests/TestModule/,PowerShellBuild/Public/Build-PSBuildModule.ps1, andPowerShellBuild/en-US/Messages.psd1. This branch touches none of them — the two strings the validation tests assert on,PathDoesNotExistandPathArgumentMustBeAFolder, already ship, soMessages.psd1needed no edit.🤖 Generated with Claude Code
https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE