File size: 3,889 Bytes
6a7089a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | 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")
}
// Verify headers.
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)
}
// Verify body is a valid task snapshot.
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) {
// Should be a no-op, no panic.
sendWebhook("", &Task{ID: "tsk_empty"})
}
func TestSendWebhookUnsupportedScheme(t *testing.T) {
// file:// scheme should be rejected for SSRF protection.
sendWebhook("file:///etc/passwd", &Task{ID: "tsk_ssrf"})
// ftp:// should also be rejected.
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()
// Should not panic — just logs warning.
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()
// Use a short-timeout client for testing.
origClient := webhookClient
webhookClient = &http.Client{Timeout: 10 * time.Millisecond}
defer func() { webhookClient = origClient }()
// Should not panic — timeout is logged.
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)
// Give goroutine time to fire.
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")
}
}
|