Skip to content

Commit cebd5d9

Browse files
Neil ScharkSean-Der
authored andcommitted
Add Webhook package
Add `CallWebhook` with tests. Not called by WHIP/WHEP handlers yet
1 parent e17aa4b commit cebd5d9

2 files changed

Lines changed: 145 additions & 0 deletions

File tree

internal/webhook/webhook.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package webhook
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
)
10+
11+
type webhookPayload struct {
12+
Action string `json:"action"`
13+
IP string `json:"ip"`
14+
BearerToken string `json:"bearerToken"`
15+
QueryParams map[string]string `json:"queryParams"`
16+
UserAgent string `json:"userAgent"`
17+
}
18+
19+
type webhookResponse struct {
20+
StreamKey string `json:"streamKey"`
21+
}
22+
23+
func CallWebhook(url, action, bearerToken string, timeout int, r *http.Request) (string, error) {
24+
start := time.Now()
25+
26+
queryParams := make(map[string]string)
27+
for k, v := range r.URL.Query() {
28+
if len(v) > 0 {
29+
queryParams[k] = v[0]
30+
}
31+
}
32+
33+
jsonPayload, err := json.Marshal(webhookPayload{
34+
Action: action,
35+
IP: getIPAddress(r),
36+
BearerToken: bearerToken,
37+
QueryParams: queryParams,
38+
UserAgent: r.UserAgent(),
39+
})
40+
if err != nil {
41+
return "", fmt.Errorf("failed to marshal payload: %w", err)
42+
}
43+
44+
client := &http.Client{
45+
Timeout: time.Duration(timeout) * time.Millisecond,
46+
}
47+
48+
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
49+
if err != nil {
50+
return "", fmt.Errorf("failed to create request: %w", err)
51+
}
52+
req.Header.Set("Content-Type", "application/json")
53+
54+
resp, err := client.Do(req)
55+
if err != nil {
56+
return "", fmt.Errorf("webhook request failed after %v: %w", time.Since(start), err)
57+
}
58+
defer resp.Body.Close() //nolint
59+
60+
if resp.StatusCode != http.StatusOK {
61+
return "", fmt.Errorf("webhook returned non-200 Status: %v", resp.StatusCode)
62+
}
63+
64+
response := webhookResponse{}
65+
if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
66+
return "", fmt.Errorf("failed to decode response: %w", err)
67+
}
68+
69+
return response.StreamKey, nil
70+
}
71+
72+
func getIPAddress(r *http.Request) string {
73+
if r.Header.Get("X-Forwarded-For") != "" {
74+
return r.Header.Get("X-Forwarded-For")
75+
}
76+
return r.RemoteAddr
77+
}

internal/webhook/webhook_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package webhook
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
"time"
10+
)
11+
12+
func TestCallWebhook(t *testing.T) {
13+
// Setup a Mock HTTP Server
14+
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15+
switch r.URL.Path {
16+
case "/ok":
17+
w.WriteHeader(http.StatusOK)
18+
_ = json.NewEncoder(w).Encode(webhookResponse{StreamKey: "dummy_stream_key"})
19+
case "/timeout":
20+
time.Sleep(2 * time.Second)
21+
case "/error":
22+
w.WriteHeader(http.StatusInternalServerError)
23+
case "/badjson":
24+
w.WriteHeader(http.StatusOK)
25+
_, _ = w.Write([]byte("not a json"))
26+
default:
27+
w.WriteHeader(http.StatusNotFound)
28+
}
29+
}))
30+
defer mockServer.Close()
31+
32+
tests := []struct {
33+
name string
34+
url string
35+
timeout int
36+
expectedErr bool
37+
expectedKey string
38+
}{
39+
{"Success Case", "/ok", 1000, false, "dummy_stream_key"},
40+
{"Server Timeout", "/timeout", 1000, true, ""},
41+
{"Server Error", "/error", 1000, true, ""},
42+
{"Malformed JSON", "/badjson", 1000, true, ""},
43+
{"Not Found", "/notfound", 1000, true, ""},
44+
}
45+
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
req, _ := http.NewRequest("GET", "/", nil)
49+
req.RemoteAddr = "127.0.0.1"
50+
req.Header.Set("User-Agent", "test-agent")
51+
52+
// call the function with test layers
53+
result, err := CallWebhook(fmt.Sprintf("%s%s", mockServer.URL, tt.url), "action", "bearerToken", tt.timeout, req)
54+
55+
if tt.expectedErr && err == nil {
56+
t.Fatalf("expected an error but got none")
57+
}
58+
59+
if !tt.expectedErr && err != nil {
60+
t.Fatalf("did not expect an error but got %v", err)
61+
}
62+
63+
if result != tt.expectedKey {
64+
t.Fatalf("expected stream key %s but got %s", tt.expectedKey, result)
65+
}
66+
})
67+
}
68+
}

0 commit comments

Comments
 (0)