|
5 | 5 | "fmt" |
6 | 6 | "io" |
7 | 7 | "io/ioutil" |
| 8 | + "net/http" |
8 | 9 | "net/http/httptest" |
9 | 10 | "strings" |
10 | 11 | "testing" |
@@ -33,7 +34,7 @@ func TestPropFindSupportedCalendarComponent(t *testing.T) { |
33 | 34 | req.Body = io.NopCloser(strings.NewReader(propFindSupportedCalendarComponentRequest)) |
34 | 35 | req.Header.Set("Content-Type", "application/xml") |
35 | 36 | w := httptest.NewRecorder() |
36 | | - handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}} |
| 37 | + handler := Handler{Backend: &testBackend{calendars: []Calendar{*calendar}}} |
37 | 38 | handler.ServeHTTP(w, req) |
38 | 39 |
|
39 | 40 | res := w.Result() |
@@ -68,7 +69,7 @@ func TestPropFindRoot(t *testing.T) { |
68 | 69 | req.Header.Set("Content-Type", "application/xml") |
69 | 70 | w := httptest.NewRecorder() |
70 | 71 | calendar := &Calendar{} |
71 | | - handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}} |
| 72 | + handler := Handler{Backend: &testBackend{calendars: []Calendar{*calendar}}} |
72 | 73 | handler.ServeHTTP(w, req) |
73 | 74 |
|
74 | 75 | res := w.Result() |
@@ -118,7 +119,7 @@ func TestMultiCalendarBackend(t *testing.T) { |
118 | 119 | req := httptest.NewRequest("PROPFIND", "/user/calendars/", strings.NewReader(propFindUserPrincipal)) |
119 | 120 | req.Header.Set("Content-Type", "application/xml") |
120 | 121 | w := httptest.NewRecorder() |
121 | | - handler := Handler{Backend: testBackend{ |
| 122 | + handler := Handler{Backend: &testBackend{ |
122 | 123 | calendars: calendars, |
123 | 124 | objectMap: map[string][]CalendarObject{ |
124 | 125 | calendarB.Path: []CalendarObject{object}, |
@@ -177,13 +178,123 @@ func TestMultiCalendarBackend(t *testing.T) { |
177 | 178 | } |
178 | 179 | } |
179 | 180 |
|
| 181 | +const TestMkCalendarReq = ` |
| 182 | +<?xml version="1.0" encoding="UTF-8"?> |
| 183 | +<B:mkcalendar xmlns:B="urn:ietf:params:xml:ns:caldav"> |
| 184 | + <A:set xmlns:A="DAV:"> |
| 185 | + <A:prop> |
| 186 | + <B:calendar-timezone>BEGIN:VCALENDAR |
| 187 | +VERSION:2.0 |
| 188 | +PRODID:-//Apple Inc.//iPhone OS 18.1.1//EN |
| 189 | +CALSCALE:GREGORIAN |
| 190 | +BEGIN:VTIMEZONE |
| 191 | +TZID:Europe/Paris |
| 192 | +BEGIN:DAYLIGHT |
| 193 | +TZOFFSETFROM:+0100 |
| 194 | +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU |
| 195 | +DTSTART:19810329T020000 |
| 196 | +TZNAME:UTC+2 |
| 197 | +TZOFFSETTO:+0200 |
| 198 | +END:DAYLIGHT |
| 199 | +BEGIN:STANDARD |
| 200 | +TZOFFSETFROM:+0200 |
| 201 | +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU |
| 202 | +DTSTART:19961027T030000 |
| 203 | +TZNAME:UTC+1 |
| 204 | +TZOFFSETTO:+0100 |
| 205 | +END:STANDARD |
| 206 | +END:VTIMEZONE |
| 207 | +END:VCALENDAR |
| 208 | +</B:calendar-timezone> |
| 209 | + <D:calendar-order xmlns:D="http://apple.com/ns/ical/">2</D:calendar-order> |
| 210 | + <B:supported-calendar-component-set> |
| 211 | + <B:comp name="VEVENT"/> |
| 212 | + </B:supported-calendar-component-set> |
| 213 | + <D:calendar-color xmlns:D="http://apple.com/ns/ical/" symbolic-color="red">#FF2968</D:calendar-color> |
| 214 | + <A:displayname>test calendar</A:displayname> |
| 215 | + <B:calendar-free-busy-set> |
| 216 | + <NO/> |
| 217 | + </B:calendar-free-busy-set> |
| 218 | + </A:prop> |
| 219 | + </A:set> |
| 220 | +</B:mkcalendar> |
| 221 | +` |
| 222 | + |
| 223 | +const propFindTest2 = ` |
| 224 | +<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"> |
| 225 | + <d:prop> |
| 226 | + <d:resourcetype/> |
| 227 | + <c:supported-calendar-component-set/> |
| 228 | + <d:displayname/> |
| 229 | + <c:max-resource-size/> |
| 230 | + <c:calendar-description/> |
| 231 | + </d:prop> |
| 232 | +</d:propfind> |
| 233 | +` |
| 234 | + |
| 235 | +func TestMkCalendar(t *testing.T) { |
| 236 | + handler := Handler{Backend: &testBackend{ |
| 237 | + calendars: []Calendar{}, |
| 238 | + objectMap: map[string][]CalendarObject{}, |
| 239 | + }} |
| 240 | + |
| 241 | + req := httptest.NewRequest("MKCALENDAR", "/user/calendars/default/", strings.NewReader(TestMkCalendarReq)) |
| 242 | + req.Header.Set("Content-Type", "application/xml") |
| 243 | + w := httptest.NewRecorder() |
| 244 | + handler.ServeHTTP(w, req) |
| 245 | + |
| 246 | + res := w.Result() |
| 247 | + if e := res.Body.Close(); e != nil { |
| 248 | + t.Fatal(e) |
| 249 | + } else if loc := res.Header.Get("Location"); loc != "/user/calendars/default/" { |
| 250 | + t.Fatalf("unexpected location: %s", loc) |
| 251 | + } else if sc := res.StatusCode; sc != http.StatusCreated { |
| 252 | + t.Fatalf("unexpected status code: %d", sc) |
| 253 | + } |
| 254 | + |
| 255 | + req = httptest.NewRequest("PROPFIND", "/user/calendars/default/", strings.NewReader(propFindTest2)) |
| 256 | + req.Header.Set("Content-Type", "application/xml") |
| 257 | + req.Header.Set("Depth", "0") |
| 258 | + w = httptest.NewRecorder() |
| 259 | + handler.ServeHTTP(w, req) |
| 260 | + |
| 261 | + res = w.Result() |
| 262 | + defer res.Body.Close() |
| 263 | + data, err := io.ReadAll(res.Body) |
| 264 | + if err != nil { |
| 265 | + t.Fatal(err) |
| 266 | + } |
| 267 | + resp := string(data) |
| 268 | + if !strings.Contains(resp, fmt.Sprintf("<href>%s</href>", "/user/calendars/default/")) { |
| 269 | + t.Fatalf("want calendar href in response") |
| 270 | + } else if !strings.Contains(resp, "<resourcetype xmlns=\"DAV:\">") { |
| 271 | + t.Fatalf("want resource type in response") |
| 272 | + } else if !strings.Contains(resp, "<collection xmlns=\"DAV:\"></collection>") { |
| 273 | + t.Fatalf("want collection resource type in response") |
| 274 | + } else if !strings.Contains(resp, "<calendar xmlns=\"urn:ietf:params:xml:ns:caldav\"></calendar>") { |
| 275 | + t.Fatalf("want calendar resource type in response") |
| 276 | + } else if !strings.Contains(resp, "<displayname xmlns=\"DAV:\">test calendar</displayname>") { |
| 277 | + t.Fatalf("want display name in response") |
| 278 | + } else if !strings.Contains(resp, "<supported-calendar-component-set xmlns=\"urn:ietf:params:xml:ns:caldav\"><comp xmlns=\"urn:ietf:params:xml:ns:caldav\" name=\"VEVENT\"></comp></supported-calendar-component-set>") { |
| 279 | + t.Fatalf("want supported-calendar-component-set in response") |
| 280 | + } |
| 281 | +} |
| 282 | + |
| 283 | + |
180 | 284 | type testBackend struct { |
181 | 285 | calendars []Calendar |
182 | 286 | objectMap map[string][]CalendarObject |
183 | 287 | } |
184 | 288 |
|
185 | | -func (t testBackend) CreateCalendar(ctx context.Context, calendar *Calendar) error { |
186 | | - return nil |
| 289 | +func (t *testBackend) CreateCalendar(ctx context.Context, calendar *Calendar) error { |
| 290 | + if v, e := t.CalendarHomeSetPath(ctx); e != nil { |
| 291 | + return e |
| 292 | + } else if !strings.HasPrefix(calendar.Path, v) || len(calendar.Path) == len(v) { |
| 293 | + return fmt.Errorf("cannot create calendar at location %s", calendar.Path) |
| 294 | + } else { |
| 295 | + t.calendars = append(t.calendars, *calendar) |
| 296 | + return nil |
| 297 | + } |
187 | 298 | } |
188 | 299 |
|
189 | 300 | func (t testBackend) ListCalendars(ctx context.Context) ([]Calendar, error) { |
|
0 commit comments