CI / ci (push) Successful in 4m27s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
190 lines
6.3 KiB
Go
190 lines
6.3 KiB
Go
package gcal_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/gcal"
|
|
)
|
|
|
|
// chosen is a harness connected with dinners chosen as the calendar to
|
|
// push to.
|
|
func chosen(t *testing.T) *harness {
|
|
t.Helper()
|
|
h := connected(t)
|
|
if _, err := h.svc.SelectCalendar(context.Background(), dinners.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return h
|
|
}
|
|
|
|
// noRecord is a PushEvents record that stores nothing.
|
|
func noRecord() error { return nil }
|
|
|
|
// event is a valid event with id.
|
|
func event(id string) gcal.Event {
|
|
start := time.Date(2026, 10, 12, 21, 30, 0, 0, time.UTC)
|
|
return gcal.Event{ID: id, Summary: "Dinner: Beef Chili", Description: "Beef Chili",
|
|
Start: start, End: start.Add(time.Hour), TimeZone: "America/New_York"}
|
|
}
|
|
|
|
func TestPushEventsInsertsThenUpdates(t *testing.T) {
|
|
t.Parallel()
|
|
h := chosen(t)
|
|
if err := h.svc.PushEvents(context.Background(), []gcal.Event{event("abcde0"), event("abcde1")}, noRecord); err != nil {
|
|
t.Fatalf("PushEvents: %v", err)
|
|
}
|
|
renamed := event("abcde0")
|
|
renamed.Summary = "Dinner: Pot Roast"
|
|
if err := h.svc.PushEvents(context.Background(), []gcal.Event{renamed}, noRecord); err != nil {
|
|
t.Fatalf("PushEvents again: %v", err)
|
|
}
|
|
if inserts, updates := h.google.EventWrites(); inserts != 2 || updates != 1 {
|
|
t.Errorf("%d inserts and %d updates, want 2 and 1", inserts, updates)
|
|
}
|
|
events := h.google.Events(dinners.ID)
|
|
if len(events) != 2 || events["abcde0"].Summary != "Dinner: Pot Roast" {
|
|
t.Errorf("events = %+v", events)
|
|
}
|
|
if got := events["abcde1"].Start.DateTime; got != "2026-10-12T21:30:00Z" {
|
|
t.Errorf("start = %s, want the instant as given", got)
|
|
}
|
|
}
|
|
|
|
func TestPushEventsRefusesInvalidEvents(t *testing.T) {
|
|
t.Parallel()
|
|
h := chosen(t)
|
|
before := h.google.TokenCalls()
|
|
for name, edit := range map[string]func(*gcal.Event){
|
|
"uppercase id": func(e *gcal.Event) { e.ID = "ABCDE" },
|
|
"id outside a-v": func(e *gcal.Event) { e.ID = "abcdw" },
|
|
"id too short": func(e *gcal.Event) { e.ID = "abcd" },
|
|
"id too long": func(e *gcal.Event) { e.ID = strings.Repeat("a", 1025) },
|
|
"no summary": func(e *gcal.Event) { e.Summary = "" },
|
|
"ends as it starts": func(e *gcal.Event) { e.End = e.Start },
|
|
"no time zone": func(e *gcal.Event) { e.TimeZone = "" },
|
|
} {
|
|
ev := event("abcde")
|
|
edit(&ev)
|
|
if err := h.svc.PushEvents(context.Background(), []gcal.Event{event("abcde0"), ev}, noRecord); err == nil {
|
|
t.Errorf("%s: PushEvents succeeded", name)
|
|
}
|
|
}
|
|
if err := h.svc.PushEvents(context.Background(), nil, noRecord); err == nil {
|
|
t.Error("PushEvents of no events succeeded")
|
|
}
|
|
if err := h.svc.PushEvents(context.Background(), []gcal.Event{event(strings.Repeat("v", 1024))}, noRecord); err != nil {
|
|
t.Errorf("PushEvents of a 1024-character id: %v", err)
|
|
}
|
|
if inserts, _ := h.google.EventWrites(); inserts != 1 {
|
|
t.Errorf("%d events inserted, want only the valid one", inserts)
|
|
}
|
|
if n := h.google.TokenCalls() - before; n != 1 {
|
|
t.Errorf("token endpoint called %d times, want once, for the valid push", n)
|
|
}
|
|
}
|
|
|
|
func TestPushEventsGrantNoLongerAccepted(t *testing.T) {
|
|
t.Parallel()
|
|
h := chosen(t)
|
|
h.google.FailEventWrite(1, http.StatusUnauthorized)
|
|
err := h.svc.PushEvents(context.Background(), []gcal.Event{event("abcde")}, func() error {
|
|
t.Error("record ran after a failed write")
|
|
return nil
|
|
})
|
|
if !errors.Is(err, gcal.ErrReconnect) {
|
|
t.Fatalf("PushEvents = %v, want ErrReconnect", err)
|
|
}
|
|
assertNoSecret(t, "error", err.Error())
|
|
if _, ok := h.row(t); !ok {
|
|
t.Error("a refused grant forgot the connection")
|
|
}
|
|
}
|
|
|
|
func TestPushEventsGoogleFailureIsRedacted(t *testing.T) {
|
|
t.Parallel()
|
|
for name, n := range map[string]int{"insert": 1, "update": 2} {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
h := chosen(t)
|
|
if name == "update" {
|
|
if err := h.svc.PushEvents(context.Background(), []gcal.Event{event("abcde")}, noRecord); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// The fake's failure body quotes the access token.
|
|
h.google.FailEventWrite(n, http.StatusBadRequest)
|
|
err := h.svc.PushEvents(context.Background(), []gcal.Event{event("abcde")}, noRecord)
|
|
var calendar *gcal.CalendarError
|
|
if !errors.As(err, &calendar) {
|
|
t.Fatalf("PushEvents = %v, want *CalendarError", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "event "+name) || !strings.Contains(err.Error(), "HTTP 400") {
|
|
t.Errorf("error = %q, want the failed %s and its status", err, name)
|
|
}
|
|
assertNoSecret(t, "error", err.Error())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPushEventsHoldsTheAccount holds an event write at Google while a
|
|
// disconnect starts: the disconnect waits for the push, so it never
|
|
// revokes the token the push is using.
|
|
func TestPushEventsHoldsTheAccount(t *testing.T) {
|
|
t.Parallel()
|
|
h := chosen(t)
|
|
arrived, release := h.google.HoldEvents()
|
|
defer release()
|
|
pushed := make(chan error, 1)
|
|
go func() { pushed <- h.svc.PushEvents(context.Background(), []gcal.Event{event("abcde")}, noRecord) }()
|
|
<-arrived
|
|
disconnected := make(chan error, 1)
|
|
go func() { disconnected <- h.svc.Disconnect(context.Background()) }()
|
|
select {
|
|
case err := <-disconnected:
|
|
t.Fatalf("Disconnect finished (%v) while a push was at Google", err)
|
|
case <-time.After(200 * time.Millisecond):
|
|
}
|
|
if n := len(h.google.Revoked()); n != 0 {
|
|
t.Fatalf("revoked %d tokens while a push was at Google", n)
|
|
}
|
|
release()
|
|
if err := <-pushed; err != nil {
|
|
t.Errorf("PushEvents: %v", err)
|
|
}
|
|
if err := <-disconnected; err != nil {
|
|
t.Errorf("Disconnect: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestPushEventsRecordsWhileTheCalendarIsChosen chooses another calendar
|
|
// while a push records: the choice waits for the record, so a push never
|
|
// records events on a calendar no longer chosen.
|
|
func TestPushEventsRecordsWhileTheCalendarIsChosen(t *testing.T) {
|
|
t.Parallel()
|
|
h := chosen(t)
|
|
selected := make(chan error, 1)
|
|
err := h.svc.PushEvents(context.Background(), []gcal.Event{event("abcde")}, func() error {
|
|
go func() {
|
|
_, err := h.svc.SelectCalendar(context.Background(), family.ID)
|
|
selected <- err
|
|
}()
|
|
select {
|
|
case err := <-selected:
|
|
t.Errorf("SelectCalendar finished (%v) while a push was recording", err)
|
|
case <-time.After(200 * time.Millisecond):
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PushEvents: %v", err)
|
|
}
|
|
if err := <-selected; err != nil {
|
|
t.Errorf("SelectCalendar: %v", err)
|
|
}
|
|
}
|