| package scheduler |
|
|
| import ( |
| "encoding/json" |
| "io" |
| "net/http" |
| "net/http/httptest" |
| "sync/atomic" |
| "testing" |
| "time" |
| ) |
|
|
| func TestSendWebhookSuccess(t *testing.T) { |
| var received atomic.Bool |
| var gotBody []byte |
| var gotHeaders http.Header |
|
|
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| received.Store(true) |
| gotHeaders = r.Header.Clone() |
| var err error |
| gotBody, err = io.ReadAll(r.Body) |
| if err != nil { |
| t.Errorf("read body: %v", err) |
| } |
| w.WriteHeader(200) |
| })) |
| defer srv.Close() |
|
|
| task := &Task{ |
| ID: "tsk_webhook_1", |
| AgentID: "a1", |
| Action: "click", |
| State: StateDone, |
| } |
|
|
| sendWebhook(srv.URL, task) |
|
|
| if !received.Load() { |
| t.Fatal("webhook was never received") |
| } |
|
|
| |
| if ct := gotHeaders.Get("Content-Type"); ct != "application/json" { |
| t.Errorf("expected application/json, got %s", ct) |
| } |
| if ev := gotHeaders.Get("X-PinchTab-Event"); ev != "task.completed" { |
| t.Errorf("expected task.completed event, got %s", ev) |
| } |
| if tid := gotHeaders.Get("X-PinchTab-Task-ID"); tid != "tsk_webhook_1" { |
| t.Errorf("expected task ID header, got %s", tid) |
| } |
|
|
| |
| var snap Task |
| if err := json.Unmarshal(gotBody, &snap); err != nil { |
| t.Fatalf("unmarshal body: %v", err) |
| } |
| if snap.ID != "tsk_webhook_1" { |
| t.Errorf("expected task ID in body, got %s", snap.ID) |
| } |
| } |
|
|
| func TestSendWebhookEmptyURL(t *testing.T) { |
| |
| sendWebhook("", &Task{ID: "tsk_empty"}) |
| } |
|
|
| func TestSendWebhookUnsupportedScheme(t *testing.T) { |
| |
| sendWebhook("file:///etc/passwd", &Task{ID: "tsk_ssrf"}) |
| |
| sendWebhook("ftp://malicious.host/data", &Task{ID: "tsk_ssrf2"}) |
| } |
|
|
| func TestSendWebhookInvalidURL(t *testing.T) { |
| sendWebhook("://bad-url", &Task{ID: "tsk_bad_url"}) |
| } |
|
|
| func TestSendWebhookServerError(t *testing.T) { |
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.WriteHeader(500) |
| })) |
| defer srv.Close() |
|
|
| |
| sendWebhook(srv.URL, &Task{ID: "tsk_500", State: StateFailed}) |
| } |
|
|
| func TestSendWebhookTimeout(t *testing.T) { |
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| time.Sleep(100 * time.Millisecond) |
| w.WriteHeader(200) |
| })) |
| defer srv.Close() |
|
|
| |
| origClient := webhookClient |
| webhookClient = &http.Client{Timeout: 10 * time.Millisecond} |
| defer func() { webhookClient = origClient }() |
|
|
| |
| sendWebhook(srv.URL, &Task{ID: "tsk_timeout"}) |
| } |
|
|
| func TestWebhookFiredOnFinishTask(t *testing.T) { |
| var received atomic.Bool |
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| received.Store(true) |
| w.WriteHeader(200) |
| })) |
| defer srv.Close() |
|
|
| s, executor := newTestScheduler(t) |
| defer executor.Close() |
|
|
| task := &Task{ |
| ID: "tsk_cb_1", |
| AgentID: "a1", |
| Action: "click", |
| State: StateDone, |
| CallbackURL: srv.URL, |
| } |
| s.live["tsk_cb_1"] = task |
|
|
| s.finishTask(task) |
|
|
| |
| time.Sleep(200 * time.Millisecond) |
|
|
| if !received.Load() { |
| t.Error("webhook should have been fired from finishTask") |
| } |
| } |
|
|
| func TestWebhookNotFiredWithoutCallback(t *testing.T) { |
| var received atomic.Bool |
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| received.Store(true) |
| w.WriteHeader(200) |
| })) |
| defer srv.Close() |
|
|
| s, executor := newTestScheduler(t) |
| defer executor.Close() |
|
|
| task := &Task{ |
| ID: "tsk_no_cb", |
| AgentID: "a1", |
| State: StateDone, |
| } |
| s.live["tsk_no_cb"] = task |
|
|
| s.finishTask(task) |
| time.Sleep(100 * time.Millisecond) |
|
|
| if received.Load() { |
| t.Error("webhook should not fire when no callbackUrl") |
| } |
| } |
|
|