@@ -290,6 +290,82 @@ Describe 'Publish-PSModule source-bound preflight' {
290290 $failure | Should -Not - BeNullOrEmpty
291291 }
292292
293+ It ' rejects a <Shape> publication claim before all writes' - ForEach @ (
294+ @ { Shape = ' top-level array' }
295+ @ { Shape = ' array-valued field' }
296+ ) {
297+ $claim = $script :state.ExpectedClaim | ConvertTo-Json - Compress | ConvertFrom-Json
298+ if ($Shape -ceq ' array-valued field' ) {
299+ $claim.PullRequest = @ (0 )
300+ }
301+ $json = $claim | ConvertTo-Json - Compress
302+ if ($Shape -ceq ' top-level array' ) { $json = " [$json ]" }
303+ Set-PSModuleFixtureClaim - State $script :state - Claim $script :state.ExpectedClaim
304+ $encoded = [Convert ]::ToBase64String([Text.Encoding ]::UTF8.GetBytes($json ))
305+ $script :state.TagObject.message = " <!-- PSModule.Publication:v1:$encoded -->"
306+
307+ { & $script :publishScriptPath 1> $null 6> $null } | Should - Throw ' *publication*'
308+
309+ $script :state.GalleryWrites | Should - Be 0
310+ $script :state.GitHubWrites | Should - Be 0
311+ }
312+
313+ It ' rejects a <Shape> release footer before all writes' - ForEach @ (
314+ @ { Shape = ' top-level array' }
315+ @ { Shape = ' array-valued field' }
316+ ) {
317+ $script :state.ExpectedClaim.PullRequest = 42
318+ $env: PSMODULE_PUBLISH_PSMODULE_INPUT_PullRequest = ' {"Number":42}'
319+ Set-PSModuleFixtureClaim - State $script :state - Claim $script :state.ExpectedClaim
320+ $owner = [pscustomobject ]@ {
321+ Repository = ' PSModule/TestModule' ; PullRequest = 42 ; CommitSha = $env: GITHUB_SHA
322+ }
323+ if ($Shape -ceq ' array-valued field' ) { $owner.PullRequest = @ (42 ) }
324+ $json = $owner | ConvertTo-Json - Compress
325+ if ($Shape -ceq ' top-level array' ) { $json = " [$json ]" }
326+ $encoded = [Convert ]::ToBase64String([Text.Encoding ]::UTF8.GetBytes($json ))
327+ $script :state.Release = [pscustomobject ]@ {
328+ tag_name = ' v1.2.4' ; html_url = ' https://github.com/PSModule/TestModule/releases/tag/v1.2.4'
329+ prerelease = $false ; draft = $false ; body = " <!-- PSModule.PrereleaseOwner:v1:$encoded -->" ; assets = @ ()
330+ }
331+
332+ { & $script :publishScriptPath 1> $null 6> $null } | Should - Throw ' *footer*'
333+
334+ $script :state.GalleryWrites | Should - Be 0
335+ $script :state.GitHubWrites | Should - Be 0
336+ }
337+
338+ It ' rejects a ZIP payload inconsistent with the tested file-tree claim before all writes' {
339+ Mock - CommandName Get-FileHash - ParameterFilter { $LiteralPath -like ' *.zip' } - MockWith {
340+ $archive = [IO.Compression.ZipFile ]::Open($LiteralPath , [IO.Compression.ZipArchiveMode ]::Update)
341+ try {
342+ $archive.GetEntry (' TestModule/TestModule.psm1' ).Delete()
343+ $writer = [IO.StreamWriter ]::new($archive.CreateEntry (' TestModule/TestModule.psm1' ).Open())
344+ try { $writer.Write (' # Different ZIP payload bytes.' ) } finally { $writer.Dispose () }
345+ } finally {
346+ $archive.Dispose ()
347+ }
348+ $stream = [IO.File ]::OpenRead($LiteralPath )
349+ $sha256 = [Security.Cryptography.SHA256 ]::Create()
350+ try {
351+ [pscustomobject ]@ {
352+ Hash = [BitConverter ]::ToString($sha256.ComputeHash ($stream )).Replace(' -' , ' ' )
353+ }
354+ } finally {
355+ $sha256.Dispose ()
356+ $stream.Dispose ()
357+ }
358+ }
359+
360+ $failure = $null
361+ try { & $script :publishScriptPath 1> $null 6> $null } catch { $failure = $_ }
362+
363+ Should - Invoke - CommandName Get-FileHash - Times 1 - Exactly - ParameterFilter { $LiteralPath -like ' *.zip' }
364+ $script :state.GalleryWrites | Should - Be 0
365+ $script :state.GitHubWrites | Should - Be 0
366+ $failure.Exception.Message | Should -Match ' archive'
367+ }
368+
293369 It ' rejects changed Gallery package bytes despite matching numeric version and source claim' {
294370 Set-PSModuleFixtureClaim - State $script :state - Claim $script :state.ExpectedClaim
295371 $script :state.Package = [pscustomobject ]@ { Name = ' TestModule' ; Version = ' 1.2.4' }
@@ -450,6 +526,7 @@ Describe 'Publish-PSModule source-bound preflight' {
450526 @ { Json = ' {}' }
451527 @ { Json = ' {"Number":0}' }
452528 @ { Json = ' {"Number":"unknown"}' }
529+ @ { Json = ' {"Number":[42]}' }
453530 @ { Json = ' 42' }
454531 @ { Json = ' []' }
455532 @ { Json = ' [null]' }
0 commit comments