appointments_test.go
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "io" |
| 5 | "net/http" |
| 6 | "net/url" |
| 7 | "regexp" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | |
| 12 | "forge.gpl-nas.de/physiobase/internal/config" |
| 13 | ) |
| 14 | |
| 15 | func TestAppointmentsAndCalendar(t *testing.T) { |
| 16 | ts, c := newServerCfg(t, func(cfg *config.Config) { |
| 17 | cfg.PublicURL = "https://physio.example.org" |
| 18 | cfg.Address = "Musterstraße 1, 50667 Köln" |
| 19 | }) |
| 20 | login(t, c, ts.URL) |
| 21 | post(c, ts.URL+"/patienten", url.Values{"name": {"Kai Kalender"}}) |
| 22 | post(c, ts.URL+"/behandler", url.Values{"name": {"Anna Schmidt"}}) |
| 23 | |
| 24 | // Serie: 3× wöchentlich, ab morgen |
| 25 | day := time.Now().AddDate(0, 0, 1).Format("2006-01-02") |
| 26 | r, _ := post(c, ts.URL+"/termine", url.Values{ |
| 27 | "patient": {"1"}, "date": {day}, "time": {"09:30"}, "duration": {"25"}, |
| 28 | "therapist": {"1"}, "title": {"Krankengymnastik"}, "note": {"Handtuch, mitbringen; bitte"}, "repeat": {"3"}, "every": {"7"}, "back": {"patient"}, |
| 29 | }) |
| 30 | if r.Header.Get("Location") != "/patient/1#termine" { |
| 31 | t.Fatalf("Termin anlegen: %d %s", r.StatusCode, r.Header.Get("Location")) |
| 32 | } |
| 33 | _, html := body(t, c, ts.URL+"/patient/1") |
| 34 | if strings.Count(html, "Krankengymnastik · Anna Schmidt") != 3 { |
| 35 | t.Fatalf("Serie nicht vollständig auf der Patientenseite") |
| 36 | } |
| 37 | m := regexp.MustCompile(`https://physio\.example\.org/kalender/([0-9a-f]{48})\.ics`).FindStringSubmatch(html) |
| 38 | if m == nil || !strings.Contains(html, "webcal://physio.example.org/kalender/") { |
| 39 | t.Fatal("Kalender-Link fehlt") |
| 40 | } |
| 41 | feed := "/kalender/" + m[1] + ".ics" |
| 42 | |
| 43 | // Feed ohne Login abrufbar, korrektes Format, keine Gesundheitsdaten/Namen |
| 44 | anon := &http.Client{} |
| 45 | resp, err := anon.Get(ts.URL + feed) |
| 46 | if err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | b, _ := io.ReadAll(resp.Body) |
| 50 | resp.Body.Close() |
| 51 | ics := string(b) |
| 52 | if resp.StatusCode != http.StatusOK || !strings.HasPrefix(resp.Header.Get("Content-Type"), "text/calendar") { |
| 53 | t.Fatalf("Feed: %d %s", resp.StatusCode, resp.Header.Get("Content-Type")) |
| 54 | } |
| 55 | for _, want := range []string{ |
| 56 | "BEGIN:VCALENDAR", "SUMMARY:Krankengymnastik – Test", "Behandlung bei: Anna Schmidt", |
| 57 | `Hinweis: Handtuch\, mitbringen\; bitte`, `LOCATION:Musterstraße 1\, 50667 Köln`, "STATUS:CONFIRMED", |
| 58 | } { |
| 59 | if !strings.Contains(strings.ReplaceAll(ics, "\r\n ", ""), want) { |
| 60 | t.Errorf("Feed ohne %q", want) |
| 61 | } |
| 62 | } |
| 63 | if strings.Count(ics, "BEGIN:VEVENT") != 3 || strings.Contains(ics, "Kai Kalender") { |
| 64 | t.Error("Feed: falsche Anzahl Termine oder Patientenname enthalten") |
| 65 | } |
| 66 | |
| 67 | // Absagen → STATUS:CANCELLED, SEQUENCE steigt |
| 68 | post(c, ts.URL+"/termin/1/absagen", nil) |
| 69 | resp, _ = anon.Get(ts.URL + feed) |
| 70 | b, _ = io.ReadAll(resp.Body) |
| 71 | resp.Body.Close() |
| 72 | if !strings.Contains(string(b), "STATUS:CANCELLED") || !strings.Contains(string(b), "SEQUENCE:1") { |
| 73 | t.Error("Absage nicht im Feed") |
| 74 | } |
| 75 | |
| 76 | // Falscher Token → 404; Link erneuern macht alten Link ungültig |
| 77 | if resp, _ := anon.Get(ts.URL + "/kalender/" + strings.Repeat("0", 48) + ".ics"); resp.StatusCode != http.StatusNotFound { |
| 78 | t.Errorf("falscher Token: %d", resp.StatusCode) |
| 79 | } |
| 80 | post(c, ts.URL+"/patient/1/kalender-link", nil) |
| 81 | if resp, _ := anon.Get(ts.URL + feed); resp.StatusCode != http.StatusNotFound { |
| 82 | t.Errorf("alter Link nach Erneuern noch gültig: %d", resp.StatusCode) |
| 83 | } |
| 84 | |
| 85 | // QR-Code, Druckliste, Übersicht |
| 86 | if code, svg := body(t, c, ts.URL+"/patient/1/kalender-qr.svg"); code != http.StatusOK || !strings.HasPrefix(svg, "<svg") { |
| 87 | t.Errorf("QR: %d", code) |
| 88 | } |
| 89 | if _, pr := body(t, c, ts.URL+"/patient/1/termine/druck"); !strings.Contains(pr, "Ihre Termine") || strings.Count(pr, "Anna Schmidt") < 3 { |
| 90 | t.Error("Druckliste unvollständig") |
| 91 | } |
| 92 | if _, ov := body(t, c, ts.URL+"/termine"); !strings.Contains(ov, "Kai Kalender") || !strings.Contains(ov, "abgesagt") { |
| 93 | t.Error("Übersicht unvollständig") |
| 94 | } |
| 95 | |
| 96 | // Behandlungszusammenfassung nennt den nächsten Termin |
| 97 | post(c, ts.URL+"/patient/1/verlauf", url.Values{"date": {time.Now().Format("2006-01-02")}, "goal": {"Mobilisation"}}) |
| 98 | r, _ = post(c, ts.URL+"/verlauf/1/zusammenfassung", nil) |
| 99 | if _, sum := body(t, c, ts.URL+r.Header.Get("Location")); !strings.Contains(sum, "um 09:30 Uhr bei Anna Schmidt") { |
| 100 | t.Error("nächster Termin fehlt in der Zusammenfassung") |
| 101 | } |
| 102 | } |
| 103 |