Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions pkg/listen/proxy/forward_path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package proxy

import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/hookdeck/hookdeck-cli/pkg/websocket"
)

func TestProcessAttemptJoinsForwardingURLPathAndEventPath(t *testing.T) {
tests := []struct {
name string
forwardTo string
eventPath string
expectPath string
}{
{"forwarding URL with a trailing slash", "/base/", "/webhook", "/base/webhook"},
{"forwarding URL without a trailing slash", "/base", "/webhook", "/base/webhook"},
{"forwarding URL is the root", "/", "/webhook", "/webhook"},
{"forwarding URL has no path", "", "/webhook", "/webhook"},
{"event path keeps its own trailing slash", "/base/", "/webhook/", "/base/webhook/"},
{"event path is empty", "/base/", "", "/base/"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
received := make(chan string, 1)
local := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received <- r.URL.Path
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(local.Close)

target, err := url.Parse(local.URL + tt.forwardTo)
require.NoError(t, err)
p := New(&Config{URL: target, NoHealthcheck: true}, nil, newRecordingRenderer())

p.processAttempt(websocket.IncomingMessage{Attempt: &websocket.Attempt{
Body: websocket.AttemptBody{
Path: tt.eventPath,
EventID: "evt_test",
AttemptId: "evt_test",
Request: websocket.AttemptRequest{Method: http.MethodPost, DataString: "{}", Headers: json.RawMessage(`{}`)},
},
}})

select {
case got := <-received:
assert.Equal(t, tt.expectPath, got)
case <-time.After(5 * time.Second):
t.Fatal("local server never received the request")
}
})
}
}
13 changes: 12 additions & 1 deletion pkg/listen/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"os"
"os/signal"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
Expand Down Expand Up @@ -379,6 +380,16 @@ func (p *Proxy) createSession(ctx context.Context) (hookdeck.Session, error) {
return session, err
}

// joinForwardPath appends the event's path to the forwarding URL's path with a
// single "/" between them. A trailing "/" on the forwarding URL is dropped only
// when the event path starts with one, and the event path itself is left as is.
func joinForwardPath(base, eventPath string) string {
if strings.HasPrefix(eventPath, "/") {
base = strings.TrimSuffix(base, "/")
}
return base + eventPath
}

func (p *Proxy) processAttempt(msg websocket.IncomingMessage) {
if msg.Attempt == nil {
p.cfg.Log.Debug("WebSocket specified for Events received unexpected event")
Expand All @@ -392,7 +403,7 @@ func (p *Proxy) processAttempt(msg websocket.IncomingMessage) {
"prefix": "proxy.Proxy.processAttempt",
}).Debugf("Processing webhook event")

url := p.cfg.URL.Scheme + "://" + p.cfg.URL.Host + p.cfg.URL.Path + webhookEvent.Body.Path
url := p.cfg.URL.Scheme + "://" + p.cfg.URL.Host + joinForwardPath(p.cfg.URL.Path, webhookEvent.Body.Path)

// Create request with context for timeout control
timeout := webhookEvent.Body.Request.Timeout
Expand Down