Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 14:45 UTC
admin: Fix tests locally, properly isolate storage (#7486)
* admin: Fix tests locally, properly isolate storage * Fix flaky pki_test * Drop testdata dir logic * Safer temp dir * Test handlers without a full server
Francis Lavoie committed
Feb 17, 2026 at 15:14 UTC
3adcafd4c1120f071e1c1b1407566780144e7fc9
3 files changed
+60
-12
admin.go
+18
-1
@@ -47,6 +47,12 @@ import (
47
"go.uber.org/zap/zapcore"
48
)
49
50
+// testCertMagicStorageOverride is a package-level test hook. Tests may set
51
+// this variable to provide a temporary certmagic.Storage so that cert
52
+// management in tests does not hit the real default storage on disk.
53
+// This must NOT be set in production code.
54
+var testCertMagicStorageOverride certmagic.Storage
55
+
56
func init() {
57
// The hard-coded default `DefaultAdminListen` can be overridden
58
// by setting the `CADDY_ADMIN` environment variable.
@@ -633,8 +639,19 @@ func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool)
639
// certmagic config, although it'll be mostly useless for remote management
640
ident = new(IdentityConfig)
641
}
642
+ // Choose storage: prefer the package-level test override when present,
643
+ // otherwise use the configured DefaultStorage. Tests may set an override
644
+ // to divert storage into a temporary location. Otherwise, in production
645
+ // we use the DefaultStorage since we don't want to act as part of a
646
+ // cluster; this storage is for the server's local identity only.
647
+ var storage certmagic.Storage
648
+ if testCertMagicStorageOverride != nil {
649
+ storage = testCertMagicStorageOverride
650
+ } else {
651
+ storage = DefaultStorage
652
+ }
653
template := certmagic.Config{
637
- Storage: DefaultStorage, // do not act as part of a cluster (this is for the server's local identity)
654
+ Storage: storage,
655
Logger: logger,
656
Issuers: ident.issuers,
657
}
admin_test.go
+40
-9
@@ -22,9 +22,11 @@ import (
22
"maps"
23
"net/http"
24
"net/http/httptest"
25
+ "os"
26
"reflect"
27
"sync"
28
"testing"
29
+ "time"
30
31
"github.com/caddyserver/certmagic"
32
"github.com/prometheus/client_golang/prometheus"
@@ -275,13 +277,12 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
277
},
278
}
279
278
- err := replaceLocalAdminServer(cfg, Context{})
280
+ // Build the admin handler directly (no listener active)
281
+ addr, err := ParseNetworkAddress("localhost:2019")
282
if err != nil {
280
- t.Fatalf("setting up admin server: %v", err)
283
+ t.Fatalf("Failed to parse address: %v", err)
284
}
282
- defer func() {
283
- stopAdminServer(localAdminServer)
284
- }()
285
+ handler := cfg.Admin.newAdminHandler(addr, false, Context{})
286
287
tests := []struct {
288
name string
@@ -314,7 +315,7 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
315
req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil)
316
rr := httptest.NewRecorder()
317
317
- localAdminServer.Handler.ServeHTTP(rr, req)
318
+ handler.ServeHTTP(rr, req)
319
320
if rr.Code != test.expectedStatus {
321
t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code)
@@ -799,8 +800,24 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
800
...
801
-----END PRIVATE KEY-----`)
802
802
- testStorage := certmagic.FileStorage{Path: t.TempDir()}
803
- err := testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
803
+ tmpDir, err := os.MkdirTemp("", "TestManageIdentity-")
804
+ if err != nil {
805
+ t.Fatal(err)
806
+ }
807
+ testStorage := certmagic.FileStorage{Path: tmpDir}
808
+ // Clean up the temp dir after the test finishes. Ensure any background
809
+ // certificate maintenance is stopped first to avoid RemoveAll races.
810
+ t.Cleanup(func() {
811
+ if identityCertCache != nil {
812
+ identityCertCache.Stop()
813
+ identityCertCache = nil
814
+ }
815
+ // Give goroutines a moment to exit and release file handles.
816
+ time.Sleep(50 * time.Millisecond)
817
+ _ = os.RemoveAll(tmpDir)
818
+ })
819
+
820
+ err = testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
821
if err != nil {
822
t.Fatal(err)
823
}
@@ -862,7 +879,7 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
879
},
880
},
881
},
865
- storage: &certmagic.FileStorage{Path: "testdata"},
882
+ storage: &testStorage,
883
},
884
checkState: func(t *testing.T, cfg *Config) {
885
if len(cfg.Admin.Identity.issuers) != 1 {
@@ -900,6 +917,13 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
917
identityCertCache.Stop()
918
identityCertCache = nil
919
}
920
+ // Ensure any cache started by manageIdentity is stopped at the end
921
+ defer func() {
922
+ if identityCertCache != nil {
923
+ identityCertCache.Stop()
924
+ identityCertCache = nil
925
+ }
926
+ }()
927
928
ctx := Context{
929
Context: context.Background(),
@@ -907,6 +931,13 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
931
moduleInstances: make(map[string][]Module),
932
}
933
934
+ // If this test provided a FileStorage, set the package-level
935
+ // testCertMagicStorageOverride so certmagicConfig will use it.
936
+ if test.cfg != nil && test.cfg.storage != nil {
937
+ testCertMagicStorageOverride = test.cfg.storage
938
+ defer func() { testCertMagicStorageOverride = nil }()
939
+ }
940
+
941
err := manageIdentity(ctx, test.cfg)
942
943
if test.wantErr {
caddytest/integration/pki_test.go
+2
-2
@@ -53,7 +53,7 @@ func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
53
}
54
}
55
}
56
- `, "json", "certificate lifetime (168h0m0s) should be less than intermediate certificate lifetime (168h0m0s)")
56
+ `, "json", "should be less than intermediate certificate lifetime")
57
}
58
59
func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
@@ -103,5 +103,5 @@ func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
103
}
104
}
105
}
106
- `, "json", "intermediate certificate lifetime must be less than root certificate lifetime (86400h0m0s)")
106
+ `, "json", "intermediate certificate lifetime must be less than actual root certificate lifetime")
107
}