Skip to content

Vault and Azure: the backend-said-no branches are untested (52% and 46% branch coverage) #275

Description

@kirill-abblix

Measurement

Line coverage flatters these two packages; branch coverage does not. From the merged solution run (3556 tests, all green):

package lines branches
Abblix.Oidc.Server.Vault 91.9% 51.9% (80/154)
Abblix.Oidc.Server.Azure 78.5% 45.9% (45/98)

Every other production project sits between 71% and 89% on branches. These two are the outliers, and they are the two that talk to an external custodian over the network.

A line is counted as covered when it runs once. A line like if (properties.Enabled != true) therefore reads as covered while only the true side has ever been taken. That is the whole gap here: the happy path runs, the answers-we-do-not-like do not.

Reproduce with dotnet test Abblix.Oidc.slnx -c Debug --coverage --coverage-output-format cobertura, then read condition-coverage per line rather than the line hits.

Where the untaken branches are

Almost all of them sit in the two classes that speak to the backend:

  • Vault/TransitCustodian.cs - 62 of the package's 74 missed branches
  • Azure/KeyVaultClient.cs - 48 of the package's 53

The rest is a handful in ApiResponse.cs, KeyValueStore.cs, Transport.cs, BlobKeyRingStore.cs and ServiceCollectionExtensions.cs.

They fall into two kinds, and the second kind is the one that matters.

Algorithm mapping switches, one arm walked

  • TransitCustodian.cs:120 and :135 - the algorithm and hash-algorithm switches
  • TransitCustodian.cs:254 - the RSA key-family check
  • KeyVaultClient.cs:120 and :165 - MapSignatureAlgorithm and MapEncryptionAlgorithm

Cheap to close with a theory over the algorithm names. No backend needed. Worth doing because a wrong arm here means signing with an algorithm the caller did not ask for, and the mistake is invisible until a verifier elsewhere rejects the result.

Guards on an answer we do not like

  • KeyVaultClient.cs:197 - if (properties.Enabled != true). Disabling a key in Key Vault is how an operator revokes a compromised key. The branch that honours that has never been taken. If it is wrong, revocation silently does not revoke, and nothing in the suite would say so.
  • KeyVaultClient.cs:261 - if (separator <= 0 || separator == keyId.Length - 1), the malformed key-identifier guard.
  • KeyVaultClient.cs:103 - the credential-configuration check.
  • The remaining untaken branches in ApiResponse.cs, Transport.cs and KeyValueStore.cs are the backend-said-no paths.

Proposal

  • Cover the mapping switches with theories. Mechanical, no infrastructure, closes the bulk of the count.
  • Cover the guards with a stub custodian that answers badly: a disabled key, a malformed identifier, a rejected credential, a transport failure. These are the branches an operator's real actions reach, so a test here defends behaviour rather than a number.
  • Verify the disabled-key path against a live backend. There is an OpenBao container in the loop already and Azurite for the Azure side; a unit test with a stub proves the branch is wired, a live one proves the backend actually reports what the branch expects. Both, not either.
  • Exclude generated sources from the measurement - obj/**/LoggerMessage.g.cs contributes untaken branches in both packages and nothing a human can act on. Same measurement noise as Mvc adapter coverage: opt-in endpoints are never walked end to end #274.

Why this is not "raise the number"

Both packages exist to keep a private key inside an HSM or KMS. The interesting behaviour of such a client is not what it does when the custodian cooperates - it is what it does when the custodian refuses, returns a disabled key, or cannot be reached. That is precisely the half that is currently untested.

Acceptance

  • Branch coverage in both packages in line with the rest of the solution.
  • The disabled-key path covered by a unit test and confirmed against a live backend.
  • Every algorithm arm of every mapping switch exercised.
  • Generated sources no longer counted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions