Six mocks in the Get-PSBuildCertificate tests never reach the module, and three tests fail on a machine that has a code-signing certificate
Avaliação
- Dificuldade
- 4/5
- Tempo estimado
- 3-5 dias
- Facilidade para iniciantes
- 68/100
- Tipo de issue
- Bug
- Clareza
- Claramente especificada
- Status de atividade
- Ativa
- Stack de tecnologia
- powershell
- Domínio
- security, testing-qa
Direção de pesquisa
Comece em tests/Get-PSBuildCertificate.tests.ps1 e compare os seis mocks do repositório com os mocks corretamente delimitados no contexto SkipValidation for store-backed sources. Execute o arquivo de testes completo e, em seguida, torne os testes do repositório determinísticos e faça com que exercitem o bloco de validação pós-carregamento para chaves privadas, expiração e Code Signing EKU. Está concluído quando os testes não dependerem mais do repositório de certificados local e as linhas de validação estiverem cobertas.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Six Mock Get-ChildItem statements in tests/Get-PSBuildCertificate.tests.ps1 are declared without -ModuleName 'PowerShellBuild', so they never reach the call inside the module. Three of the tests that rely on them pass only because the machine running them has no code-signing certificate installed.
What happens
Pester mocks are scoped to the session state they are declared in. A mock declared in a test file replaces the command for the test file; it does not replace the command for a function running inside an imported module. Get-PSBuildCertificate calls Get-ChildItem -Path $CertStoreLocation -CodeSigningCert from inside PowerShellBuild, so it gets the real cmdlet and the real certificate store.
The six unscoped mocks:
| Line | Test |
|---|---|
| 24 | Defaults to Auto mode when no CertificateSource is specified |
| 42 | Resolves to Store mode when SIGNCERTIFICATE environment variable is not set |
| 61 | Returns $null when no valid certificate is found |
| 67 | Filters out expired certificates |
| 76 | Filters out certificates without a private key |
| 100 | Returns $null when the specified thumbprint is not found |
Measured
Adding Should -Invoke Get-ChildItem -Times 0 -Exactly to a copy of Returns $null when no valid certificate is found — an assertion that the mock was never called — passes. A module-scoped spy added alongside it records exactly one call, which is the one the function actually makes:
[+] A: the unscoped mock is never invoked
[+] B: the real Get-ChildItem inside the module is what actually runs
Should -Invoke Get-ChildItem -Times 0 -Exactly (unscoped)
Should -Invoke -ModuleName PowerShellBuild Get-ChildItem -Times 1 -Exactly
Standing in a valid code-signing certificate for what the store would return, and keeping the test's own assertion verbatim:
[-] C: the same test fails when the store holds a code-signing certificate
Expected $null or empty, but got @{Subject=CN=Real Workstation Cert; ...}.
at $cert | Should -BeNullOrEmpty
Three tests fail that way on a machine with a code-signing certificate in Cert:\CurrentUser\My: Returns $null when no valid certificate is found, Filters out expired certificates, and Filters out certificates without a private key. All three assert $cert | Should -BeNullOrEmpty after asking the real store for a real certificate.
The other three unscoped mocks are harmless by luck rather than design. The two Auto-mode tests assert on the first verbose record, which is written before the store is touched, and the thumbprint test asks for 'NOTFOUND123', which no real certificate matches.
The same fix closes a coverage gap
Get-PSBuildCertificate's post-load validation block has never executed. It is the block that gates a certificate loaded from EnvVar or PfxFile on having a private key, not being expired, and carrying the Code Signing extended key usage:
if ($cert -and -not $SkipValidation -and ($resolvedSource -eq 'EnvVar' -or $resolvedSource -eq 'PfxFile')) {
if (-not $cert.HasPrivateKey) { throw ... }
if ($cert.NotAfter -le (Get-Date)) { throw ... }
$hasCodeSigningEku = $cert.EnhancedKeyUsageList | Where-Object { $_.ObjectId -eq $codeSigningOid }
if (-not $hasCodeSigningEku) { throw ... }
Every EnvVar and PfxFile test in the file feeds the function deliberately invalid data — [byte[]]@(1,2,3,4,5) base64-encoded, or an empty file with a .pfx extension — so construction of the X509Certificate2 throws before the block is reached. Nothing produces a certificate object that survives to line 228.
Code coverage over Get-PSBuildCertificate.ps1 with the whole file running:
26 tests passed, 0 failed
65 of 95 commands covered
Missed, lines 230-246: the entire private key, expiry, and Code Signing EKU block
This is the validation that decides whether a certificate a consumer supplied through a CI secret is usable. It has never run.
Why it matters
- The three store tests are latent CI-versus-workstation failures. They are green on the hosted runners and on any workstation without a signing certificate, and red on exactly the machine most likely to have one: a maintainer's, where signing is actually being worked on. That is the worst place for a test to first go red, because the natural reading is "my machine is broken", not "the test was never testing anything".
- The three store tests assert nothing about the module either way. With the mock inert,
Filters out expired certificatesandFilters out certificates without a private keyare the same test asReturns $null when no valid certificate is found: three identical assertions that the real store happens to be empty. Neither filter is exercised. - The EKU and expiry checks are the signing feature's safety net, and the failure mode if they regress is a build that signs with a certificate it should have refused, or throws on one it should have accepted.
The model to follow is already in the file
The SkipValidation for store-backed sources context added in #194 is written correctly:
Mock -ModuleName PowerShellBuild -CommandName Get-ChildItem -MockWith { $script:expiredCertificate }
Nine tests there control what the store returns, and they are the only tests in the file that actually exercise the selection logic. Whatever is done here should look like those.
Options
- Add
-ModuleName 'PowerShellBuild'to the six mocks and give the three store tests real fixtures. Cheapest change, and it makesFilters out expired certificatesandFilters out certificates without a private keydo what their names say by returning an expired and a keyless certificate respectively. It also makes them deterministic everywhere. - Cover the post-load validation block by generating a real certificate.
New-SelfSignedCertificate -Type CodeSigningCertproduces one with a private key and the Code Signing EKU, and it can be exported to a PFX under$TestDriveto drive thePfxFilesource, or base64-encoded into an environment variable to driveEnvVar. Windows-only, and it writes to the certificate store, so it needs a cleanup block. A certificate with-NotAfterin the past covers the expiry throw, and one created without the code-signing type covers the EKU throw. - Cover the validation block with a fake object instead. The block only reads
HasPrivateKey,NotAfter,Subject, andEnhancedKeyUsageList, so aPSCustomObjectwould exercise every branch — but only if the loading step can be intercepted, and it constructsX509Certificate2directly rather than through a mockable command. That would mean extracting the load behind a private function first. - Delete the three store tests that assert the machine has no certificate. Honest about what they currently test, and it removes the latent failure without pretending to add coverage. Worth considering only if (1) is not taken.
(1) and (2) together are the whole fix, and (1) alone is worth doing even if (2) is deferred, because it removes the machine dependency.
Related: #194 (the correctly scoped context), #197 (the platform guard on these same tests). Shares its mock-scoping defect and its remediation with #217, which has the same unscoped Mock problem in tests/Invoke-PSBuildModuleSigning.tests.ps1.
- Linguagem predominante
- PowerShell
- Estrelas
- 145
- Forks
- 26
- Métricas de merge de PRs
- Nenhum PR com merge em 30d
Preparar o ambiente
Inicia o contêiner de desenvolvimento do projeto no navegador, com a sua própria conta do GitHub.
- Sem Dockerfile nem arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de psake/PowerShellBuild
-
Nothing tests the Build-PSBuildUpdatableHelp branch that deletes a consumer's output directoryAbertabug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
psake/PowerShellBuild#218 · 1 comentário ·
-
Staging flattens a culture directory's .psd1 into the output rootTalvez já em andamento Um pull request vinculado a esta issue está aberto ou já foi mesclado. Abertabug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 74/100
psake/PowerShellBuild#211 · 1 comentário ·
-
CI: Install the built module from a local repository to verify install-time dependency behaviourTalvez já em andamento @tablackburn assumiu há 38 dias. Abertaenhancement github_actions
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 68/100
psake/PowerShellBuild#229 ·
-
Test-PSBuildPester unloads a module it never importedTalvez já em andamento Um pull request vinculado a esta issue está aberto ou já foi mesclado. Abertabug
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 55/100
psake/PowerShellBuild#222 ·
-
Build-PSBuildMarkdown unloads a module the caller had loadedTalvez já em andamento @tablackburn assumiu há 45 dias. Abertabug
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 68/100
psake/PowerShellBuild#221 ·
Todas as issues de psake/PowerShellBuild
Issues semelhantes
-
raised-by:verifier
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
medici-finance/assay#2718 ·
Mantenedores costumam responder em até 1 dia
-
Security issue submissionAberta
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 3 dias
-
Count attacks the IPS drops mid-request as blocked in the attack simulatorTalvez já em andamento @himanishpuri assumiu hoje. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 83/100
Ergane-Foundation/vajra#54 · 2 comentários ·
Mantenedores costumam responder em até 2 dias
-
github_actions
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 94/100
Mantenedores costumam responder em até 1 dia
-
bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 66/100
Mantenedores costumam responder em até 1 dia