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:
2026-03-25 21:01:23 -07:00
parent 7f9e7f433f
commit 7749c035ae
8 changed files with 2101 additions and 3 deletions

View 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)
}
}