package handlers import ( "net/http" "net/http/httptest" "os" "path/filepath" "testing" "github.com/gin-gonic/gin" ) func init() { gin.SetMode(gin.TestMode) } func setupACME(t *testing.T) (*gin.Engine, string) { t.Helper() root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, ".well-known", "acme-challenge"), 0o755); err != nil { t.Fatal(err) } r := gin.New() NewACMEHandler(root).Register(r) return r, root } func TestACME_ServesExistingToken(t *testing.T) { r, root := setupACME(t) tokenContent := "abc123-some-key-authorisation" tokenPath := filepath.Join(root, ".well-known", "acme-challenge", "tok_42") if err := os.WriteFile(tokenPath, []byte(tokenContent), 0o644); err != nil { t.Fatal(err) } rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/.well-known/acme-challenge/tok_42", nil) r.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status: %d, body=%s", rec.Code, rec.Body.String()) } if rec.Body.String() != tokenContent { t.Errorf("body: %q", rec.Body.String()) } } func TestACME_MissingToken_Returns404(t *testing.T) { r, _ := setupACME(t) rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/.well-known/acme-challenge/notthere", nil) r.ServeHTTP(rec, req) if rec.Code != http.StatusNotFound { t.Errorf("status: %d", rec.Code) } } func TestACME_RejectsTraversal(t *testing.T) { // gin parses `:token` as one path segment, so `..` literally // can't reach the handler — but we still want the validToken // guard to reject any non-charset value defensively. cases := []string{ "a/b", "..", "a%2Fb", "with space", "semi;colon", } for _, c := range cases { if validToken(c) { t.Errorf("validToken(%q) should be false", c) } } } func TestACME_DirIsNotAFile(t *testing.T) { r, root := setupACME(t) if err := os.Mkdir(filepath.Join(root, ".well-known", "acme-challenge", "subdir"), 0o755); err != nil { t.Fatal(err) } rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/.well-known/acme-challenge/subdir", nil) r.ServeHTTP(rec, req) if rec.Code != http.StatusNotFound { t.Errorf("expected 404 for directory, got %d", rec.Code) } }