From 405065df46c5ff405e5d189821464d5efef2ae68 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 3 Aug 2026 02:55:56 +0200 Subject: [PATCH 1/2] Expand test coverage for Sodium public commands - Add BeforeAll block to fail fast with a helpful message when the Sodium module is not loaded and no source manifest exists. - Cover parameter validation: null/empty Message, PublicKey, and PrivateKey. - Add Unicode/special-character round-trip and sealed-box non-determinism tests. - Add wrong-length base64 key rejection and mismatched public/private key tests. - Extend key-pair tests to verify output shape and randomness. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/Sodium.Tests.ps1 | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/tests/Sodium.Tests.ps1 b/tests/Sodium.Tests.ps1 index 404c4b8..98f95bc 100644 --- a/tests/Sodium.Tests.ps1 +++ b/tests/Sodium.Tests.ps1 @@ -1,6 +1,19 @@ #Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' } Describe 'Sodium' { + BeforeAll { + $script:ModuleName = 'Sodium' + if (-not (Get-Module -Name $script:ModuleName -ErrorAction SilentlyContinue)) { + $srcRoot = Split-Path -Path $PSScriptRoot -Parent + $manifestPath = Join-Path -Path $srcRoot -ChildPath 'src\Sodium.psd1' + if (Test-Path -Path $manifestPath) { + Import-Module -Name $manifestPath -Force -ErrorAction Stop + } else { + throw "Module '$script:ModuleName' is not loaded and no source manifest was found at '$manifestPath'. Build the module first." + } + } + } + Context 'SealedBox - Encryption and Decryption' { It 'Encrypts and decrypts a message correctly using valid keys' { $keyPair = New-SodiumKeyPair @@ -49,6 +62,71 @@ Describe 'Sodium' { Should -Throw 'Invalid sealed box. Expected at least 48 bytes but got 16.' } + It 'Rejects an empty message because the parameter is mandatory' { + $keyPair = New-SodiumKeyPair + + { ConvertTo-SodiumSealedBox -Message '' -PublicKey $keyPair.PublicKey } | Should -Throw + } + + It 'Encrypts and decrypts a message containing Unicode and special characters' { + $keyPair = New-SodiumKeyPair + $message = 'Hello 🌍! Æøå 日本語 ' + + $encryptedMessage = ConvertTo-SodiumSealedBox -Message $message -PublicKey $keyPair.PublicKey + $decryptedString = ConvertFrom-SodiumSealedBox -SealedBox $encryptedMessage -PublicKey $keyPair.PublicKey -PrivateKey $keyPair.PrivateKey + + $decryptedString | Should -BeExactly $message + } + + It 'Produces a different sealed box each time the same message is encrypted' { + $keyPair = New-SodiumKeyPair + $message = 'Deterministic input should not yield deterministic output' + + $encrypted1 = ConvertTo-SodiumSealedBox -Message $message -PublicKey $keyPair.PublicKey + $encrypted2 = ConvertTo-SodiumSealedBox -Message $message -PublicKey $keyPair.PublicKey + + $encrypted1 | Should -Not -Be $encrypted2 + } + + It 'Fails decryption when the public key does not match the private key' { + $keyPair1 = New-SodiumKeyPair + $keyPair2 = New-SodiumKeyPair + $message = 'Mismatched public key test' + + $encryptedMessage = ConvertTo-SodiumSealedBox -Message $message -PublicKey $keyPair1.PublicKey + + { ConvertFrom-SodiumSealedBox -SealedBox $encryptedMessage -PublicKey $keyPair2.PublicKey -PrivateKey $keyPair1.PrivateKey } | + Should -Throw 'Decryption failed.' + } + + It 'Rejects encryption with a public key that is valid base64 but the wrong length' { + $message = 'Wrong length public key' + $shortPublicKey = [Convert]::ToBase64String([byte[]]::new(16)) + + { ConvertTo-SodiumSealedBox -Message $message -PublicKey $shortPublicKey } | + Should -Throw 'Invalid public key. Expected 32 bytes but got 16.' + } + + It 'Rejects decryption with a private key that is valid base64 but the wrong length' { + $keyPair = New-SodiumKeyPair + $message = 'Wrong length private key' + $encryptedMessage = ConvertTo-SodiumSealedBox -Message $message -PublicKey $keyPair.PublicKey + $shortPrivateKey = [Convert]::ToBase64String([byte[]]::new(16)) + + { ConvertFrom-SodiumSealedBox -SealedBox $encryptedMessage -PrivateKey $shortPrivateKey } | + Should -Throw 'Invalid private key. Expected 32 bytes but got 16.' + } + + It 'Requires a non-null message parameter' { + $keyPair = New-SodiumKeyPair + + { ConvertTo-SodiumSealedBox -Message $null -PublicKey $keyPair.PublicKey } | Should -Throw + } + + It 'Requires a non-empty public key parameter' { + { ConvertTo-SodiumSealedBox -Message 'test' -PublicKey '' } | Should -Throw + } + It 'Encrypts a message correctly when using pipeline input on ConvertTo-SodiumSealedBox' { $keyPair = New-SodiumKeyPair $publicKey = $keyPair.PublicKey @@ -103,6 +181,13 @@ Describe 'Sodium' { $result = $encryptedMessage | ConvertFrom-SodiumSealedBox -PrivateKey $kp.PrivateKey $result | Should -Be $message } + + It 'Requires a non-empty private key when no public key is given' { + $kp = New-SodiumKeyPair + $encryptedMessage = ConvertTo-SodiumSealedBox -Message 'test' -PublicKey $kp.PublicKey + + { ConvertFrom-SodiumSealedBox -SealedBox $encryptedMessage -PrivateKey '' } | Should -Throw + } } Context 'Key Pair Generation' { @@ -150,6 +235,24 @@ Describe 'Sodium' { $keyPair1.PublicKey | Should -Be $keyPair2.PublicKey $keyPair1.PrivateKey | Should -Be $keyPair2.PrivateKey } + + It 'Returns a PSCustomObject with PublicKey and PrivateKey properties' { + $keyPair = New-SodiumKeyPair + + $keyPair | Should -BeOfType [PSCustomObject] + $keyPair.PublicKey | Should -Not -BeNullOrEmpty + $keyPair.PrivateKey | Should -Not -BeNullOrEmpty + ($keyPair | Get-Member -MemberType NoteProperty).Name | Should -Contain 'PublicKey' + ($keyPair | Get-Member -MemberType NoteProperty).Name | Should -Contain 'PrivateKey' + } + + It 'Generates different random key pairs when no seed is provided' { + $keyPair1 = New-SodiumKeyPair + $keyPair2 = New-SodiumKeyPair + + $keyPair1.PublicKey | Should -Not -Be $keyPair2.PublicKey + $keyPair1.PrivateKey | Should -Not -Be $keyPair2.PrivateKey + } } Context 'Public Key Derivation' { @@ -183,6 +286,11 @@ Describe 'Sodium' { { Get-SodiumPublicKey -PrivateKey $shortPrivateKey } | Should -Throw 'Invalid private key. Expected 32 bytes but got 16.' } + + It 'Get-SodiumPublicKey - Requires a non-empty private key' { + { Get-SodiumPublicKey -PrivateKey '' } | Should -Throw + } + } Context 'Runtime diagnostics' { From 58d9732c24f4c1f8e92aa70fe57988ff8ae657ca Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 3 Aug 2026 03:39:48 +0200 Subject: [PATCH 2/2] Bump Process-PSModule workflow to v6.1.16 Updates the reusable workflow pin from v6.1.13 to v6.1.16 to pick up the latest fixes and improvements. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Process-PSModule.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Process-PSModule.yml b/.github/workflows/Process-PSModule.yml index a895338..82efc09 100644 --- a/.github/workflows/Process-PSModule.yml +++ b/.github/workflows/Process-PSModule.yml @@ -27,5 +27,5 @@ permissions: jobs: Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@fb1bdb8fefd243292f779d2a856a38db6fe6daf4 # v6.1.13 + uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@1653be8d36607d9535f600278c44789979477813 # v6.1.16 secrets: inherit