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>
89 lines
1.9 KiB
Go
89 lines
1.9 KiB
Go
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)
|
|
}
|
|
}
|