package main
import (
"html"
"io"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"testing"
"time"
)
// formHxGetRe extracts the hx-get URL of a slot button pointing at /form.
var formHxGetRe = regexp.MustCompile(`hx-get="([^"]*/form[^"]*)"`)
// TestHandleGetSlots_OffsetSlotStartSurvivesURLParsing pins the bidbuddy.com.au
// (Australia/Perth) production failure: Cal.com returns slot starts in the
// event's own timezone with a numeric `+08:00` offset. That value is carried in
// the slot button's hx-get query string; a raw `+` in a query string decodes to
// a SPACE server-side, corrupting the timestamp ("2026-06-11T10:00:00 08:00").
// The corrupted value flows form → book → Cal.com, which rejects it with
// "start must be a valid ISO 8601 date string". The slot's machine value must
// therefore round-trip through URL query parsing as a valid RFC3339 timestamp.
func TestHandleGetSlots_OffsetSlotStartSurvivesURLParsing(t *testing.T) {
// 2026-06-11T10:00:00+08:00 (Perth) == 2026-06-11T02:00:00Z.
fakeCalcom(t, "Australia/Perth", func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, `{
"status":"success",
"data":{"2026-06-11":[{"start":"2026-06-11T10:00:00+08:00"}]}
}`)
})
h, _ := newKeyedHandler(t)
req := httptest.NewRequest(http.MethodGet,
"/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1", nil)
rec := httptest.NewRecorder()
h.HandleGetSlots(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String())
}
m := formHxGetRe.FindStringSubmatch(rec.Body.String())
if m == nil {
t.Fatalf("no slot button hx-get to /form found in body: %q", rec.Body.String())
}
// The browser reads the attribute via the DOM, which unescapes & → &.
u, err := url.Parse(html.UnescapeString(m[1]))
if err != nil {
t.Fatalf("parse slot form URL %q: %v", m[1], err)
}
// u.Query() decodes exactly as net/http would on the wire — this is where a
// raw `+` becomes a space.
got := u.Query().Get("start")
parsed, err := time.Parse(time.RFC3339, got)
if err != nil {
t.Fatalf("slot start %q is not a valid ISO 8601 string after URL parsing (Cal.com would reject it): %v", got, err)
}
if want := time.Date(2026, 6, 11, 2, 0, 0, 0, time.UTC); !parsed.Equal(want) {
t.Errorf("slot start instant: got %s, want %s", parsed.UTC(), want)
}
}