oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1package calendar
2
3import (
4 "bytes"
5 "encoding/json"
6 "net/http/httptest"
7 "testing"
8 "time"
9)
10
11func TestNativeQuickPreviewAndSave(t *testing.T) {
12 f := newHomeRouterFixture(t)
13 call := func(path string, value any) *httptest.ResponseRecorder {
14 body, _ := json.Marshal(value)
15 r := httptest.NewRequest("POST", homeTestOrigin+path, bytes.NewReader(body))
16 r.Header.Set("Origin", homeTestOrigin)
17 r.Header.Set("Content-Type", "application/json")
18 for _, c := range f.cookies {
19 r.AddCookie(c)
20 }
21 w := httptest.NewRecorder()
22 f.app.mux.ServeHTTP(w, r)
23 return w
24 }
25 input := map[string]string{"text": "Lunch tomorrow at 1pm for an hour with bob@example.test", "zone": "Pacific/Kiritimati"}
26 if call("/api/native/quick/parse", input).Code == 200 {
27 t.Fatal("anonymous preview")
28 }
29 if call("/api/native/quick", nativeQuickDraft{}).Code == 200 {
30 t.Fatal("anonymous save")
31 }
32 f.local(t)
33 f.cookies = nativeClientCookies(t, f.app.mux, f.cookies)
34 var preview nativeQuickPreview
35 w := call("/api/native/quick/parse", input)
36 if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &preview) != nil {
37 t.Fatal("preview failed", w.Code)
38 }
39 loc, _ := Zone(input["zone"])
40 expected := ParseQuick(input["text"], time.Now().In(loc), input["zone"])
41 if preview.Draft.StartLocal != expected.StartLocal || preview.Draft.TZ != input["zone"] || preview.Draft.Title != expected.Title || len(preview.Draft.Attendees) != 1 {
42 t.Fatal("preview diverged from existing parser or requested zone")
43 }
44 p, err := f.app.store.EnsurePersonSetup(t.Context(), "alice@example.test")
45 if err != nil {
46 t.Fatal(err)
47 }
48 personal, _ := f.app.store.DefaultCalendarID(t.Context(), p.ID)
49 if preview.Draft.CalendarID != personal || preview.CalendarName == "" || preview.When == "" {
50 t.Fatal("preview missing personal calendar or civil time")
51 }
52 // Save concrete dates from the preview, not a later interpretation of tomorrow.
53 preview.Draft.StartLocal = "2026-10-25T09:00"
54 preview.Draft.EndLocal = "2026-10-25T10:00"
55 preview.Draft.Repeat = RepeatWeekly
56 w = call("/api/native/quick", preview.Draft)
57 var saved struct{ ID int64 }
58 if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &saved) != nil || saved.ID == 0 {
59 t.Fatal("save failed", w.Code)
60 }
61 ev, err := f.app.store.Event(t.Context(), p.ID, saved.ID)
62 if err != nil || ev.StartLocal != preview.Draft.StartLocal || ev.TZ != preview.Draft.TZ || ev.RRule != "FREQ=WEEKLY" {
63 t.Fatal("saved meaning changed")
64 }
65 attendees, err := f.app.store.AttendeesFor(t.Context(), saved.ID)
66 if err != nil || len(attendees) != 1 || attendees[0] != "bob@example.test" {
67 t.Fatal("attendees not recorded")
68 }
69 preview.Draft.CalendarID = 999999
70 if call("/api/native/quick", preview.Draft).Code == 200 {
71 t.Fatal("unauthorized calendar accepted")
72 }
73 preview.Draft.CalendarID = personal
74 preview.Draft.Repeat = "unsupported"
75 if call("/api/native/quick", preview.Draft).Code != 422 {
76 t.Fatal("unsupported repeat silently lost")
77 }
78 input["zone"] = "invalid/not-a-zone"
79 if call("/api/native/quick/parse", input).Code != 422 {
80 t.Fatal("invalid zone accepted")
81 }
82}
83
84func TestNativeViewingZoneUsesRequestedCivilDay(t *testing.T) {
85 f := newHomeRouterFixture(t)
86 f.local(t)
87 p, _ := f.app.store.EnsurePersonSetup(t.Context(), "alice@example.test")
88 cal, _ := f.app.store.DefaultCalendarID(t.Context(), p.ID)
89 _, err := f.app.store.CreateEvent(t.Context(), p.ID, EventInput{CalendarID: cal, Title: "Across midnight", StartLocal: "2026-10-24T12:00", EndLocal: "2026-10-24T13:00", TZ: "UTC", Kind: KindMeeting, Status: StatusConfirmed})
90 if err != nil {
91 t.Fatal(err)
92 }
93 for _, tc := range []struct {
94 zone string
95 count int
96 }{{"Pacific/Kiritimati", 1}, {"America/Los_Angeles", 0}} {
97 w := f.do("GET", "/api/native/events?from=2026-10-25&to=2026-10-26&zone="+tc.zone, "")
98 var response struct{ Events []nativeEvent }
99 if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &response) != nil || len(response.Events) != tc.count {
100 t.Fatalf("civil query wrong for %s", tc.zone)
101 }
102 }
103}
104
105func TestNativeQuickConfirmsSavedEventWithReminderWarning(t *testing.T) {
106 f := newHomeRouterFixture(t)
107 f.local(t)
108 p, _ := f.app.store.EnsurePersonSetup(t.Context(), "alice@example.test")
109 cal, _ := f.app.store.DefaultCalendarID(t.Context(), p.ID)
110 if _, err := f.app.db.Writer().Exec("CREATE TRIGGER native_quick_reminder_failure BEFORE INSERT ON reminders BEGIN SELECT RAISE(FAIL, 'fixture'); END"); err != nil {
111 t.Fatal(err)
112 }
113 draft := nativeQuickDraft{CalendarID: cal, Title: "Confirmed quick event", StartLocal: "2026-10-25T09:00", EndLocal: "2026-10-25T10:00", TZ: "Europe/Dublin"}
114 body, _ := json.Marshal(draft)
115 r := httptest.NewRequest("POST", homeTestOrigin+"/api/native/quick", bytes.NewReader(body))
116 r.Header.Set("Origin", homeTestOrigin)
117 for _, c := range f.cookies {
118 r.AddCookie(c)
119 }
120 w := httptest.NewRecorder()
121 f.app.mux.ServeHTTP(w, r)
122 var result struct {
123 ID int64
124 Warning string
125 }
126 if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &result) != nil || result.ID == 0 || result.Warning == "" {
127 t.Fatal("saved quick event reported as failure", w.Code)
128 }
129 event, err := f.app.store.Event(t.Context(), p.ID, result.ID)
130 if err != nil || event.Title != draft.Title {
131 t.Fatal("confirmed event not persisted")
132 }
133}
134