package main import ( "encoding/json" "net/http" "net/http/httptest" "net/url" "strings" "testing" ) func TestVerifyCapcha(t *testing.T) { t.Run("valid token", func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { t.Errorf("method = %s, want POST", r.Method) } if r.URL.Path != "/113476d322/siteverify" { t.Errorf("path = %s, want /113476d322/siteverify", r.URL.Path) } if ct := r.Header.Get("Content-Type"); ct != "application/json" { t.Errorf("content type = %s, want application/json", ct) } var payload map[string]string if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { t.Fatalf("decoding payload: %v", err) } if payload["secret"] != "sk-test" { t.Errorf("secret = %q, want sk-test", payload["secret"]) } if payload["response"] != "token-123" { t.Errorf("response = %q, want token-123", payload["response"]) } w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":true}`)) })) defer srv.Close() s := newTestServer() s.config.CapchaInstance = srv.URL s.config.CapchaSiteKey = "113476d322" s.config.CapchaKeySecret = "sk-test" if err := s.verifyCapcha(t.Context(), "token-123"); err != nil { t.Fatalf("verifyCapcha returned error: %v", err) } }) t.Run("rejected token", func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"invalid token"}`)) })) defer srv.Close() s := newTestServer() s.config.CapchaInstance = srv.URL s.config.CapchaKeySecret = "sk-test" err := s.verifyCapcha(t.Context(), "bad-token") if err == nil { t.Fatal("verifyCapcha succeeded, want error") } if !strings.Contains(err.Error(), "invalid token") { t.Errorf("error = %q, want it to contain the service error", err) } }) t.Run("missing token", func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Error("captcha service should not be called without a token") })) defer srv.Close() s := newTestServer() s.config.CapchaInstance = srv.URL s.config.CapchaKeySecret = "sk-test" if err := s.verifyCapcha(t.Context(), ""); err == nil { t.Fatal("verifyCapcha succeeded, want error") } }) t.Run("missing secret", func(t *testing.T) { s := newTestServer() s.config.CapchaKeySecret = "" if err := s.verifyCapcha(t.Context(), "token-123"); err == nil { t.Fatal("verifyCapcha succeeded, want error") } }) t.Run("service error", func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "boom", http.StatusInternalServerError) })) defer srv.Close() s := newTestServer() s.config.CapchaInstance = srv.URL s.config.CapchaKeySecret = "sk-test" if err := s.verifyCapcha(t.Context(), "token-123"); err == nil { t.Fatal("verifyCapcha succeeded, want error") } }) } func TestContactFormRequiresCapToken(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"success":false,"error":"invalid token"}`)) })) defer srv.Close() s := newTestServer() s.config.CapchaInstance = srv.URL s.config.CapchaKeySecret = "sk-test" s.config.TelegramBotToken = "" s.config.TelegramChatID = "" // Forged submission without a cap-token must be rejected. body := url.Values{ "name": {"Test"}, "email": {"test@example.com"}, "message": {"Hallo"}, } req := httptest.NewRequest(http.MethodPost, "/contact", strings.NewReader(body.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("X-Forwarded-For", "203.0.113.10") rec := httptest.NewRecorder() s.contactHandler(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } respBody := rec.Body.String() if strings.Contains(respBody, "Bedankt voor uw bericht") { t.Fatal("submission without a cap-token was accepted") } if !strings.Contains(respBody, "mens-verificatie is mislukt") { t.Fatalf("expected verification error message, got: %s", respBody) } } func newTestServer() *Server { s := NewServer() // Keep tests independent from any ambient Telegram configuration. s.config.TelegramBotToken = "" s.config.TelegramChatID = "" return s }