Skip to content

Commit b974e21

Browse files
test(coverage/nats): drive queueprovider/nats to 100% (was 80.4%) (#29)
Adds targeted coverage for every error branch in queueprovider/nats: * builder() — bad operator seed, opKP.PublicKey() fail, all defaults applied, UseTLS scheme flip * SetResolverPusher — nil-arg resets to no-op (previously could nil-deref) * IssueTenantCredentials — empty ResourceToken validation, SystemAccount rejection, legacy_open short-circuit when operator seed unset, pusher-error propagation, every nkeys.* / jwt.* post-create error branch (createAccount/PublicKey/Seed, createUser/PublicKey/Seed, sign account JWT, sign user JWT, FormatUserConfig) * RevokeTenantCredentials — empty keyID no-op, legacy_open no-op, cache-miss no-op, encode-revocation failure, push-failure propagation * RevokeWithSeed — empty seed no-op, legacy_open no-op, FromSeed failure, derived public key failure, encode failure, push-failure propagation * lookupCachedByPub — non-matching entry continue-scanning branch * connectionURL — useTLS=true tls:// scheme * Name — "nats" identifier * Capabilities — full shape (PerTenantAccounts/SubjectScopedAuth/StreamIso=true, BasicAuth=false) * canonicalSubject — auto-derive when Subject empty Source refactor: 4 package-level test seams (createAccountKP, createUserKP, formatUserCreds, parseOperatorKP) so unit tests can inject failing implementations for branches that the real nkeys/jwt libraries never trip in practice. In production these seams alias the upstream constructors 1:1 — zero behavior change. Coverage: 80.4% → 100.0% (all 14 functions in nats.go). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 50b0d98 commit b974e21

3 files changed

Lines changed: 952 additions & 6 deletions

File tree

queueprovider/nats/export_test.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package nats
22

3-
import "sync"
3+
import (
4+
"sync"
5+
"time"
6+
7+
"github.com/nats-io/nkeys"
8+
)
49

510
// PurgeAccountCacheForTest empties the in-memory accountCache. Used by tests
611
// to simulate the post-restart scenario where the cache is empty and the only
@@ -9,3 +14,47 @@ import "sync"
914
func (p *Provider) PurgeAccountCacheForTest() {
1015
p.accountCache = sync.Map{}
1116
}
17+
18+
// PoisonAccountCacheForTest stuffs a fake cachedAccount under the supplied
19+
// resource token. Used to exercise the lookupCachedByPub "no-match-keep-
20+
// scanning" branch with multiple entries in the cache.
21+
func (p *Provider) PoisonAccountCacheForTest(token string) {
22+
p.accountCache.Store(token, cachedAccount{
23+
accountKP: nil,
24+
accountPub: "AOTHER_PUB_KEY_THAT_NEVER_MATCHES",
25+
accountJWT: "",
26+
createdAt: time.Now(),
27+
})
28+
}
29+
30+
// SetCreateAccountKPForTest swaps the package-level account-NKey constructor.
31+
// Returns a restore func. Lets tests drive the error branches that
32+
// nkeys.CreateAccount otherwise never trips.
33+
func SetCreateAccountKPForTest(fn func() (nkeys.KeyPair, error)) (restore func()) {
34+
orig := createAccountKP
35+
createAccountKP = fn
36+
return func() { createAccountKP = orig }
37+
}
38+
39+
// SetCreateUserKPForTest is the user-NKey-constructor counterpart.
40+
func SetCreateUserKPForTest(fn func() (nkeys.KeyPair, error)) (restore func()) {
41+
orig := createUserKP
42+
createUserKP = fn
43+
return func() { createUserKP = orig }
44+
}
45+
46+
// SetFormatUserCredsForTest swaps the jwt.FormatUserConfig hook.
47+
func SetFormatUserCredsForTest(fn func(string, []byte) ([]byte, error)) (restore func()) {
48+
orig := formatUserCreds
49+
formatUserCreds = fn
50+
return func() { formatUserCreds = orig }
51+
}
52+
53+
// SetParseOperatorKPForTest swaps the nkeys.FromSeed hook used by both
54+
// builder() (to parse the operator seed) and RevokeWithSeed (to parse the
55+
// per-tenant account seed).
56+
func SetParseOperatorKPForTest(fn func([]byte) (nkeys.KeyPair, error)) (restore func()) {
57+
orig := parseOperatorKP
58+
parseOperatorKP = fn
59+
return func() { parseOperatorKP = orig }
60+
}

queueprovider/nats/nats.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ func init() {
6161
queueprovider.Register("nats", builder)
6262
}
6363

64+
// Test seams. These are package-level vars so unit tests can substitute
65+
// failing implementations and exercise the error branches that the real
66+
// nkeys/jwt libraries practically never hit. In production they alias the
67+
// upstream constructors 1:1.
68+
var (
69+
createAccountKP = nkeys.CreateAccount
70+
createUserKP = nkeys.CreateUser
71+
formatUserCreds = jwt.FormatUserConfig
72+
parseOperatorKP = nkeys.FromSeed
73+
)
74+
6475
// builder is the Factory entry point. Returns ErrAuthFailure-flavored errors
6576
// when the operator seed is unparseable, so the caller can degrade gracefully
6677
// during the pre-cutover window.
@@ -96,7 +107,7 @@ func builder(cfg queueprovider.Config) (queueprovider.QueueCredentialProvider, e
96107
// understands operator mode first, populate the secret + flip
97108
// nats.yaml later.
98109
if cfg.NATSOperatorSeed != "" {
99-
opKP, err := nkeys.FromSeed([]byte(cfg.NATSOperatorSeed))
110+
opKP, err := parseOperatorKP([]byte(cfg.NATSOperatorSeed))
100111
if err != nil {
101112
return nil, fmt.Errorf("%w: parse operator seed: %v", queueprovider.ErrAuthFailure, err)
102113
}
@@ -220,7 +231,7 @@ func (p *Provider) IssueTenantCredentials(ctx context.Context, in queueprovider.
220231
}
221232

222233
// 1. Mint account NKey pair.
223-
accountKP, err := nkeys.CreateAccount()
234+
accountKP, err := createAccountKP()
224235
if err != nil {
225236
return nil, fmt.Errorf("queueprovider.nats: create account NKey: %w", err)
226237
}
@@ -272,7 +283,7 @@ func (p *Provider) IssueTenantCredentials(ctx context.Context, in queueprovider.
272283
})
273284

274285
// 4. Mint user NKey pair + sign user JWT with the account seed.
275-
userKP, err := nkeys.CreateUser()
286+
userKP, err := createUserKP()
276287
if err != nil {
277288
return nil, fmt.Errorf("queueprovider.nats: create user NKey: %w", err)
278289
}
@@ -316,7 +327,7 @@ func (p *Provider) IssueTenantCredentials(ctx context.Context, in queueprovider.
316327
return nil, fmt.Errorf("queueprovider.nats: sign user JWT: %w", err)
317328
}
318329

319-
credsFile, err := jwt.FormatUserConfig(userJWT, userSeed)
330+
credsFile, err := formatUserCreds(userJWT, userSeed)
320331
if err != nil {
321332
return nil, fmt.Errorf("queueprovider.nats: format .creds blob: %w", err)
322333
}
@@ -397,7 +408,7 @@ func (p *Provider) RevokeWithSeed(ctx context.Context, accountSeed string) error
397408
if !p.operatorReady || accountSeed == "" {
398409
return nil
399410
}
400-
kp, err := nkeys.FromSeed([]byte(accountSeed))
411+
kp, err := parseOperatorKP([]byte(accountSeed))
401412
if err != nil {
402413
return fmt.Errorf("queueprovider.nats: parse account seed: %w", err)
403414
}

0 commit comments

Comments
 (0)