aboutsummaryrefslogtreecommitdiff
path: root/weed/notification/webhook/http_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/notification/webhook/http_test.go')
-rw-r--r--weed/notification/webhook/http_test.go372
1 files changed, 372 insertions, 0 deletions
diff --git a/weed/notification/webhook/http_test.go b/weed/notification/webhook/http_test.go
index f7ef006ae..f64c01f2d 100644
--- a/weed/notification/webhook/http_test.go
+++ b/weed/notification/webhook/http_test.go
@@ -148,3 +148,375 @@ func TestHttpClientSendMessageNetworkError(t *testing.T) {
t.Error("Expected error for network failure")
}
}
+
+// TestHttpClientFollowsRedirectAsPost verifies that redirects are followed with POST method preserved
+func TestHttpClientFollowsRedirectAsPost(t *testing.T) {
+ redirectCalled := false
+ finalCalled := false
+ var finalMethod string
+ var finalBody map[string]interface{}
+
+ // Create final destination server
+ finalServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ finalCalled = true
+ finalMethod = r.Method
+ body, _ := io.ReadAll(r.Body)
+ json.Unmarshal(body, &finalBody)
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer finalServer.Close()
+
+ // Create redirect server
+ redirectServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ redirectCalled = true
+ // Return 301 redirect to final server
+ http.Redirect(w, r, finalServer.URL, http.StatusMovedPermanently)
+ }))
+ defer redirectServer.Close()
+
+ cfg := &config{
+ endpoint: redirectServer.URL,
+ authBearerToken: "test-token",
+ timeoutSeconds: 5,
+ }
+
+ client, err := newHTTPClient(cfg)
+ if err != nil {
+ t.Fatalf("Failed to create HTTP client: %v", err)
+ }
+
+ message := &filer_pb.EventNotification{
+ NewEntry: &filer_pb.Entry{
+ Name: "test.txt",
+ },
+ }
+
+ // Send message - should follow redirect and recreate POST request
+ err = client.sendMessage(newWebhookMessage("/test/path", message))
+ if err != nil {
+ t.Fatalf("Failed to send message: %v", err)
+ }
+
+ if !redirectCalled {
+ t.Error("Expected redirect server to be called")
+ }
+
+ if !finalCalled {
+ t.Error("Expected final server to be called after redirect")
+ }
+
+ if finalMethod != "POST" {
+ t.Errorf("Expected POST method at final destination, got %s", finalMethod)
+ }
+
+ if finalBody["key"] != "/test/path" {
+ t.Errorf("Expected key '/test/path' at final destination, got %v", finalBody["key"])
+ }
+
+ // Verify the final URL is cached
+ client.endpointMu.RLock()
+ cachedURL := client.finalURL
+ client.endpointMu.RUnlock()
+
+ if cachedURL != finalServer.URL {
+ t.Errorf("Expected cached URL %s, got %s", finalServer.URL, cachedURL)
+ }
+}
+
+// TestHttpClientUsesCachedRedirect verifies that subsequent requests use the cached redirect destination
+func TestHttpClientUsesCachedRedirect(t *testing.T) {
+ redirectCount := 0
+ finalCount := 0
+
+ // Create final destination server
+ finalServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ finalCount++
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer finalServer.Close()
+
+ // Create redirect server
+ redirectServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ redirectCount++
+ http.Redirect(w, r, finalServer.URL, http.StatusMovedPermanently)
+ }))
+ defer redirectServer.Close()
+
+ cfg := &config{
+ endpoint: redirectServer.URL,
+ authBearerToken: "test-token",
+ timeoutSeconds: 5,
+ }
+
+ client, err := newHTTPClient(cfg)
+ if err != nil {
+ t.Fatalf("Failed to create HTTP client: %v", err)
+ }
+
+ message := &filer_pb.EventNotification{
+ NewEntry: &filer_pb.Entry{
+ Name: "test.txt",
+ },
+ }
+
+ // First request - should hit redirect server
+ err = client.sendMessage(newWebhookMessage("/test/path1", message))
+ if err != nil {
+ t.Fatalf("Failed to send first message: %v", err)
+ }
+
+ if redirectCount != 1 {
+ t.Errorf("Expected 1 redirect call, got %d", redirectCount)
+ }
+ if finalCount != 1 {
+ t.Errorf("Expected 1 final call, got %d", finalCount)
+ }
+
+ // Second request - should use cached URL and skip redirect server
+ err = client.sendMessage(newWebhookMessage("/test/path2", message))
+ if err != nil {
+ t.Fatalf("Failed to send second message: %v", err)
+ }
+
+ if redirectCount != 1 {
+ t.Errorf("Expected redirect server to be called only once (cached), got %d calls", redirectCount)
+ }
+ if finalCount != 2 {
+ t.Errorf("Expected 2 final calls, got %d", finalCount)
+ }
+}
+
+// TestHttpClientPreservesPostMethod verifies POST method is preserved and not converted to GET
+func TestHttpClientPreservesPostMethod(t *testing.T) {
+ var receivedMethod string
+
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ receivedMethod = r.Method
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer server.Close()
+
+ cfg := &config{
+ endpoint: server.URL,
+ authBearerToken: "test-token",
+ timeoutSeconds: 5,
+ }
+
+ client, err := newHTTPClient(cfg)
+ if err != nil {
+ t.Fatalf("Failed to create HTTP client: %v", err)
+ }
+
+ message := &filer_pb.EventNotification{
+ NewEntry: &filer_pb.Entry{
+ Name: "test.txt",
+ },
+ }
+
+ err = client.sendMessage(newWebhookMessage("/test/path", message))
+ if err != nil {
+ t.Fatalf("Failed to send message: %v", err)
+ }
+
+ if receivedMethod != "POST" {
+ t.Errorf("Expected POST method, got %s", receivedMethod)
+ }
+}
+
+// TestHttpClientInvalidatesCacheOnError verifies that cache is invalidated when cached URL fails
+func TestHttpClientInvalidatesCacheOnError(t *testing.T) {
+ finalServerDown := false // Start with server UP
+ originalCallCount := 0
+ finalCallCount := 0
+
+ // Create final destination server that can be toggled
+ finalServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ finalCallCount++
+ if finalServerDown {
+ w.WriteHeader(http.StatusServiceUnavailable)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+ }))
+ defer finalServer.Close()
+
+ // Create redirect server
+ redirectServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ originalCallCount++
+ http.Redirect(w, r, finalServer.URL, http.StatusMovedPermanently)
+ }))
+ defer redirectServer.Close()
+
+ cfg := &config{
+ endpoint: redirectServer.URL,
+ authBearerToken: "test-token",
+ timeoutSeconds: 5,
+ }
+
+ client, err := newHTTPClient(cfg)
+ if err != nil {
+ t.Fatalf("Failed to create HTTP client: %v", err)
+ }
+
+ message := &filer_pb.EventNotification{
+ NewEntry: &filer_pb.Entry{
+ Name: "test.txt",
+ },
+ }
+
+ // First request - should follow redirect and cache the final URL
+ err = client.sendMessage(newWebhookMessage("/test/path1", message))
+ if err != nil {
+ t.Fatalf("Failed to send first message: %v", err)
+ }
+
+ if originalCallCount != 1 {
+ t.Errorf("Expected 1 original call, got %d", originalCallCount)
+ }
+ if finalCallCount != 1 {
+ t.Errorf("Expected 1 final call, got %d", finalCallCount)
+ }
+
+ // Verify cache was set
+ client.endpointMu.RLock()
+ cachedURL := client.finalURL
+ client.endpointMu.RUnlock()
+ if cachedURL != finalServer.URL {
+ t.Errorf("Expected cached URL %s, got %s", finalServer.URL, cachedURL)
+ }
+
+ // Second request with cached URL working - should use cache
+ err = client.sendMessage(newWebhookMessage("/test/path2", message))
+ if err != nil {
+ t.Fatalf("Failed to send second message: %v", err)
+ }
+
+ if originalCallCount != 1 {
+ t.Errorf("Expected still 1 original call (using cache), got %d", originalCallCount)
+ }
+ if finalCallCount != 2 {
+ t.Errorf("Expected 2 final calls, got %d", finalCallCount)
+ }
+
+ // Third request - bring final server DOWN, should invalidate cache and retry with original
+ // Flow: cached URL (fail, depth=0) -> clear cache -> retry original (depth=1) -> redirect -> final (fail, depth=2)
+ finalServerDown = true
+ err = client.sendMessage(newWebhookMessage("/test/path3", message))
+ if err == nil {
+ t.Error("Expected error when cached URL fails and retry also fails")
+ }
+
+ // originalCallCount: 1 (initial) + 1 (retry after cache invalidation) = 2
+ if originalCallCount != 2 {
+ t.Errorf("Expected 2 original calls, got %d", originalCallCount)
+ }
+ // finalCallCount: 2 (previous) + 1 (cached fail) + 1 (retry after redirect) = 4
+ if finalCallCount != 4 {
+ t.Errorf("Expected 4 final calls, got %d", finalCallCount)
+ }
+
+ // Verify final URL is still set (to the failed destination from the redirect)
+ client.endpointMu.RLock()
+ finalURLAfterError := client.finalURL
+ client.endpointMu.RUnlock()
+ if finalURLAfterError != finalServer.URL {
+ t.Errorf("Expected finalURL to be %s after error, got %s", finalServer.URL, finalURLAfterError)
+ }
+
+ // Fourth request - bring final server back UP
+ // Since cache still has the final URL, it should use it directly
+ finalServerDown = false
+ err = client.sendMessage(newWebhookMessage("/test/path4", message))
+ if err != nil {
+ t.Fatalf("Failed to send fourth message after recovery: %v", err)
+ }
+
+ // Should have used the cached URL directly (no new original call)
+ // originalCallCount: still 2
+ if originalCallCount != 2 {
+ t.Errorf("Expected 2 original calls (using cache), got %d", originalCallCount)
+ }
+ // finalCallCount: 4 + 1 = 5
+ if finalCallCount != 5 {
+ t.Errorf("Expected 5 final calls, got %d", finalCallCount)
+ }
+
+ // Verify cache was re-established
+ client.endpointMu.RLock()
+ reestablishedCache := client.finalURL
+ client.endpointMu.RUnlock()
+ if reestablishedCache != finalServer.URL {
+ t.Errorf("Expected cache to be re-established to %s, got %s", finalServer.URL, reestablishedCache)
+ }
+}
+
+// TestHttpClientInvalidatesCacheOnNetworkError verifies cache invalidation on network errors
+func TestHttpClientInvalidatesCacheOnNetworkError(t *testing.T) {
+ originalCallCount := 0
+ var finalServer *httptest.Server
+ // Create redirect server
+ redirectServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ originalCallCount++
+ if finalServer != nil {
+ http.Redirect(w, r, finalServer.URL, http.StatusMovedPermanently)
+ } else {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ }))
+ defer redirectServer.Close()
+
+ // Create final destination server
+ finalServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ }))
+
+ cfg := &config{
+ endpoint: redirectServer.URL,
+ authBearerToken: "test-token",
+ timeoutSeconds: 5,
+ }
+
+ client, err := newHTTPClient(cfg)
+ if err != nil {
+ t.Fatalf("Failed to create HTTP client: %v", err)
+ }
+
+ message := &filer_pb.EventNotification{
+ NewEntry: &filer_pb.Entry{
+ Name: "test.txt",
+ },
+ }
+
+ // First request - establish cache
+ err = client.sendMessage(newWebhookMessage("/test/path1", message))
+ if err != nil {
+ t.Fatalf("Failed to send first message: %v", err)
+ }
+
+ if originalCallCount != 1 {
+ t.Errorf("Expected 1 original call, got %d", originalCallCount)
+ }
+
+ // Close final server to simulate network error
+ cachedURL := finalServer.URL
+ finalServer.Close()
+ finalServer = nil
+
+ // Second request - cached URL is down, should invalidate and retry with original
+ err = client.sendMessage(newWebhookMessage("/test/path2", message))
+ if err == nil {
+ t.Error("Expected error when network fails")
+ }
+
+ if originalCallCount != 2 {
+ t.Errorf("Expected 2 original calls (retry after cache invalidation), got %d", originalCallCount)
+ }
+
+ // Verify cache was cleared
+ client.endpointMu.RLock()
+ clearedCache := client.finalURL
+ client.endpointMu.RUnlock()
+ if clearedCache == cachedURL {
+ t.Errorf("Expected cache to be invalidated, but still has %s", clearedCache)
+ }
+}