Files
MealQuest/internal/chat/webhook_test.go
T
2026-10-09 18:04:05 -04:00

102 lines
5.3 KiB
Go

package chat
import (
"strings"
"testing"
)
const (
testKey = "AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI"
testToken = "q0Lz3kV9Yw8pXcRtN2bHsJdFgA7uEmKiOlP1nZxQ%3D"
testSpace = "AAQA1b2C3d4"
testHook = "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=" + testKey + "&token=" + testToken
)
func TestParseWebhookAccepts(t *testing.T) {
t.Parallel()
for _, raw := range []string{
testHook,
"https://chat.googleapis.com/v1/spaces/AAAA_x-y/messages?key=" + testKey + "&token=" + testToken,
} {
hook, err := ParseWebhook(raw)
if err != nil {
t.Errorf("ParseWebhook(%q) = %v, want accepted", raw, err)
continue
}
if hook.String() != raw {
t.Errorf("String() = %q, want %q", hook.String(), raw)
}
}
}
func TestParseWebhookRejects(t *testing.T) {
t.Parallel()
q := "?key=" + testKey + "&token=" + testToken
for name, raw := range map[string]string{
"empty": "",
"http": "http://chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q,
"upper-case scheme": "HTTPS://chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q,
"lookalike suffix": "https://chat.googleapis.com.evil.example/v1/spaces/" + testSpace + "/messages" + q,
"lookalike prefix": "https://evilchat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q,
"other google host": "https://www.googleapis.com/v1/spaces/" + testSpace + "/messages" + q,
"upper-case host": "https://CHAT.googleapis.com/v1/spaces/" + testSpace + "/messages" + q,
"port": "https://chat.googleapis.com:443/v1/spaces/" + testSpace + "/messages" + q,
"user info": "https://user@chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q,
"trailing dot host": "https://chat.googleapis.com./v1/spaces/" + testSpace + "/messages" + q,
"no space": "https://chat.googleapis.com/v1/spaces//messages" + q,
"extra path": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages/x" + q,
"other version": "https://chat.googleapis.com/v2/spaces/" + testSpace + "/messages" + q,
"members path": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/members" + q,
"escaped path": "https://chat.googleapis.com/v1/spaces/AA%2FQA/messages" + q,
"dot segment": "https://chat.googleapis.com/v1/spaces/../messages" + q,
"no query": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages",
"no key": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?token=" + testToken,
"no token": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=" + testKey,
"empty token": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=" + testKey + "&token=",
"short key": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=abc&token=" + testToken,
"token first": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?token=" + testToken + "&key=" + testKey,
"empty segment": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=" + testKey + "&&token=" + testToken,
"leading ampersand": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?&" + q[1:],
"trailing ampersand": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q + "&",
"second question": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q + "?x",
"multi-byte token": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=" + testKey + "&token=" + strings.Repeat("%F0%9F%98%80", 4),
"multi-byte key": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=" + strings.Repeat("%F0%9F%98%80", 4) + "&token=" + testToken,
"plus in token": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=" + testKey + "&token=" + testToken + "+",
"repeated key": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q + "&key=" + testKey,
"extra parameter": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q + "&threadKey=x",
"bad escape in query": "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages" + q + "%zz",
"fragment": testHook + "#frag",
"space": testHook + " ",
"newline": testHook + "\n",
"too long": testHook + strings.Repeat("A", MaxWebhookURLLength),
"not a URL": "chat webhook please",
} {
_, err := ParseWebhook(raw)
if err == nil {
t.Errorf("%s: ParseWebhook(%q) accepted", name, raw)
continue
}
for _, secret := range []string{testKey, testToken, testSpace} {
if strings.Contains(err.Error(), secret) {
t.Errorf("%s: error %q quotes the URL", name, err)
}
}
}
}
func TestWebhookMasked(t *testing.T) {
t.Parallel()
hook, err := ParseWebhook(testHook)
if err != nil {
t.Fatal(err)
}
// The tail is of the token as it decodes: "%3D" is "=".
want := "https://chat.googleapis.com/v1/spaces/" + testSpace + "/messages?key=…&token=…ZxQ="
if got := hook.Masked(); got != want {
t.Errorf("Masked() = %q, want %q", got, want)
}
if m := hook.Masked(); strings.Contains(m, testKey) || strings.Contains(m, testToken) || strings.Contains(m, testToken[:8]) {
t.Errorf("Masked() = %q holds the key or token", m)
}
}