Add comprehensive ACME test suite (60 tests, 2100 lines)
Test coverage for the entire ACME server implementation: - helpers_test.go: memBarrier, key generation, JWS/EAB signing, test fixtures - nonce_test.go: issue/consume lifecycle, reuse rejection, concurrency - jws_test.go: JWS parsing/verification (ES256, ES384, RS256), JWK parsing, RFC 7638 thumbprints, EAB HMAC verification, key authorization - eab_test.go: EAB credential CRUD, account/order listing - validate_test.go: HTTP-01 challenge validation with httptest servers, authorization/order state machine transitions - handlers_test.go: full ACME protocol flow via chi router — directory, nonce, account creation with EAB, order creation, authorization retrieval, challenge triggering, finalize (order-not-ready), cert retrieval/revocation, CSR identifier validation One production change: extract dnsResolver variable in validate.go for DNS-01 test injection (no behavior change). All 60 tests pass with -race. Full project vet and test clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
internal/acme/nonce_test.go
Normal file
88
internal/acme/nonce_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNonceIssueAndConsume(t *testing.T) {
|
||||
store := NewNonceStore()
|
||||
nonce, err := store.Issue()
|
||||
if err != nil {
|
||||
t.Fatalf("Issue() error: %v", err)
|
||||
}
|
||||
if err := store.Consume(nonce); err != nil {
|
||||
t.Fatalf("Consume() error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonceRejectUnknown(t *testing.T) {
|
||||
store := NewNonceStore()
|
||||
err := store.Consume("never-issued-nonce")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error consuming unknown nonce, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonceRejectReuse(t *testing.T) {
|
||||
store := NewNonceStore()
|
||||
nonce, err := store.Issue()
|
||||
if err != nil {
|
||||
t.Fatalf("Issue() error: %v", err)
|
||||
}
|
||||
if err := store.Consume(nonce); err != nil {
|
||||
t.Fatalf("first Consume() error: %v", err)
|
||||
}
|
||||
err = store.Consume(nonce)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error on second Consume(), got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonceFormat(t *testing.T) {
|
||||
store := NewNonceStore()
|
||||
nonce, err := store.Issue()
|
||||
if err != nil {
|
||||
t.Fatalf("Issue() error: %v", err)
|
||||
}
|
||||
// 16 bytes base64url-encoded without padding = 22 characters.
|
||||
if len(nonce) != 22 {
|
||||
t.Fatalf("expected nonce length 22, got %d", len(nonce))
|
||||
}
|
||||
// Verify it is valid base64url (no padding).
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("nonce is not valid base64url: %v", err)
|
||||
}
|
||||
if len(decoded) != 16 {
|
||||
t.Fatalf("expected 16 decoded bytes, got %d", len(decoded))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonceConcurrentAccess(t *testing.T) {
|
||||
store := NewNonceStore()
|
||||
const goroutines = 50
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
errs := make(chan error, goroutines)
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
nonce, err := store.Issue()
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
if err := store.Consume(nonce); err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
for err := range errs {
|
||||
t.Fatalf("concurrent nonce operation failed: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user