Skip to content

fix: Cover Publish-PSBuildModule and fail the build on a failed publish - #204

Merged
tablackburn merged 3 commits into
mainfrom
test/103-publish-psbuildmodule-tests
Aug 28, 2026
Merged

fix: Cover Publish-PSBuildModule and fail the build on a failed publish#204
tablackburn merged 3 commits into
mainfrom
test/103-publish-psbuildmodule-tests

Conversation

@tablackburn

Copy link
Copy Markdown
Contributor

Summary

  • Adds tests/Publish-PSBuildModule.tests.ps1 — 31 tests, taking the function from 0 of 21 instructions covered to 22 of 22.
  • Fixes Publish-PSBuildModule reports success when the publish fails #203: Publish-PSBuildModule reported success for a publish that never happened.
  • One CHANGELOG.md entry, for the fix only. The tests are not user-facing and get none.

Closes #103. Closes #203.

The defect

Publish-Module reports a failed publish as a non-terminating error — an unregistered repository, a rejected API key, a repository the credential cannot authenticate to. Publish-PSBuildModule called it at the default $ErrorActionPreference, so the error went to the error stream and the command returned normally. Publish-PSBuildModule returned normally after it, and the Publish task in both psakeFile.ps1 and IB.tasks.ps1 reported success.

Measured against the built module on main:

Get-PackageSource: Unable to find repository 'PSBuildNoSuchRepository'.

…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 Stop on the Publish-Module call, added to the same $PSBoundParameters forwarding loop that already carries NuGetApiKey and Credential, so an explicit -ErrorAction from 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

Context What it pins
Command surface Export, help synopsis and example, the three mandatory parameters, the two optional ones, the ApiKey alias, Credential typed as PSCredential, and the single-parameter-set shape
Path validation Both ValidateScript branches, asserted on message text
Negative control That the mocks below actually intercept
Failure reporting The #203 regression, against the real command and against the forwarded value
Forwarding Path, repository, API key, alias-bound API key, credential, both together, neither, the un-forwarded -Version, and the verbose message
End-to-end A real publish to a temporary file-based PSRepository

On the parameter sets

The issue and the function's own [CmdletBinding(DefaultParameterSetName = 'ApiKey')] both imply an ApiKey set and a Credential set. There are none. No parameter carries a ParameterSetName, so ApiKey is 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 set pins 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 $value returns an empty string rather than throwing, so a ValidateScript reading a key Messages.psd1 never defined fails validation with no text at all — and a bare Should -Throw is 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-PSRepository against a $TestDrive directory, Publish-PSBuildModule to it, then assert the .nupkg lands and Find-Module resolves it from that repository. Measured:

  • No network required. The same publish was run behind an unreachable proxy (HTTP_PROXY/HTTPS_PROXY pointed at 127.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.
  • Windows PowerShell 5.1 works too, verified separately (3.4s).
  • No NuGet bootstrapping was triggered. PowerShellGet 2.2.5 packs through the dotnet CLI when it is present, and nuget.exe was never downloaded — %LOCALAPPDATA%\…\PowerShellGet\NuGet.exe did not exist before the probes and does not exist after them.

Machine state: the repository name is unique per run (PSBuildTestRepository<8 hex>), and AfterAll unregisters it. AfterAll runs even when the BeforeAll publish throws, so a failed publish still leaves the registered-repository list as it was found. Verified after every probe: Get-PSRepository reports PSGallery and 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 (dotnet or nuget) 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.ps1 carried Mock without -ModuleName long enough for a real defect to survive it.

  1. In the suite. A Negative control context runs the same call with nothing mocked. It reaches the real Publish-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.
  2. Out of band. I copied the file, stripped -ModuleName 'PowerShellBuild' from every Mock and Should -Invoke, and ran it. Eleven tests went red with Unable to find repository 'InternalRepository' raised from PSModule.psm1 inside Publish-PSBuildModule. The mocks intercept; without -ModuleName they do not, and the file says so loudly rather than passing.

Other observations, not changed here

  • -Version is mandatory but never reaches Publish-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 version pins that, so a later change that starts forwarding it is a deliberate one.
  • -Path is 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.
  • I left the Publish-PSBuildModule reports success when the publish fails #203 fix in ### Fixed rather 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 on main: 503 / 0 / 3; +31 from this file, same 3 skips)
  • Coverage for Publish-PSBuildModule: 22 of 22 instructions, up from 0 of 21
  • Red-before-green: reverting the ErrorAction default fails 4 of the new tests
  • Mocks proven to intercept: stripping -ModuleName fails 11
  • End-to-end publish verified offline and on Windows PowerShell 5.1
  • Get-PSRepository unchanged after every run
  • CHANGELOG.md stays CRLF throughout

Breaking 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, and PowerShellBuild/en-US/Messages.psd1. This branch touches none of them — the two strings the validation tests assert on, PathDoesNotExist and PathArgumentMustBeAFolder, already ship, so Messages.psd1 needed no edit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U1Jhu7fgTRJq7LK5MuKteE

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
Copilot AI lite review requested due to automatic review settings August 28, 2026 15:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

Test Results

    4 files  ±  0    817 suites  +32   2m 47s ⏱️ -7s
  539 tests + 31    536 ✅ + 31   3 💤 ±0  0 ❌ ±0 
2 139 runs  +124  2 067 ✅ +124  72 💤 ±0  0 ❌ ±0 

Results for commit 84095f5. ± Comparison against base commit b3bbe9e.

♻️ This comment has been updated with latest results.

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
@tablackburn tablackburn changed the title test: Cover Publish-PSBuildModule and fail the build on a failed publish fix: Cover Publish-PSBuildModule and fail the build on a failed publish Aug 28, 2026
@tablackburn
tablackburn merged commit 139e9be into main Aug 28, 2026
7 checks passed
@tablackburn
tablackburn deleted the test/103-publish-psbuildmodule-tests branch August 28, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publish-PSBuildModule reports success when the publish fails Tests: Publish-PSBuildModule

2 participants