package api import ( "strings" "testing" ) const fullEditBody = `{"revision":3,"title":"Notatka","type":"notatka","body":"Tekst","source":null,"url":null,"meeting_id":null,"tags":["lektura"]}` // TestDecodeMaterialEdit checks the student edit body (D-09): every key is // required, revision, title, type and tags may not be null, and unknown keys // such as course_id or published_at are refused. func TestDecodeMaterialEdit(t *testing.T) { in, errs, err := decodeMaterialEdit([]byte(fullEditBody)) if err != nil { t.Fatal(err) } if len(errs) != 0 { t.Errorf("full body errors = %v, want none", errs) } if in.Revision == nil || *in.Revision != 3 || deref(in.Title) != "Notatka" || in.Tags == nil || len(*in.Tags) != 1 { t.Errorf("full body decoded to %+v", in) } for _, key := range materialEditKeys { raw := dropKey(t, fullEditBody, key) _, errs, err := decodeMaterialEdit([]byte(raw)) if err != nil { t.Errorf("without %s: decode error %v", key, err) continue } if _, ok := errs[key]; !ok || len(errs) != 1 { t.Errorf("without %s: errors = %v, want one on %s", key, errs, key) } } for _, key := range []string{"revision", "title", "type", "tags"} { raw := nullKey(t, fullEditBody, key) _, errs, err := decodeMaterialEdit([]byte(raw)) if err != nil { t.Errorf("null %s: decode error %v", key, err) continue } if _, ok := errs[key]; !ok || len(errs) != 1 { t.Errorf("null %s: errors = %v, want one on %s", key, errs, key) } } for _, key := range []string{"body", "source", "url", "meeting_id"} { _, errs, err := decodeMaterialEdit([]byte(nullKey(t, fullEditBody, key))) if err != nil || len(errs) != 0 { t.Errorf("null %s = %v %v, want no error", key, errs, err) } } for _, extra := range []string{`"course_id":1`, `"published_at":null`, `"author_user_id":2`, `"is_private":true`, `"share_user_ids":[]`} { raw := strings.TrimSuffix(fullEditBody, "}") + "," + extra + "}" if _, _, err := decodeMaterialEdit([]byte(raw)); err == nil { t.Errorf("body with %s decoded, want an unknown-field error", extra) } } if _, _, err := decodeMaterialEdit([]byte(`[1]`)); err == nil { t.Error("an array body decoded, want an error") } } // dropKey removes key and its value from the flat JSON object raw. func dropKey(t *testing.T, raw, key string) string { t.Helper() parts := splitTop(raw) out := make([]string, 0, len(parts)) for _, p := range parts { if !strings.HasPrefix(p, `"`+key+`":`) { out = append(out, p) } } if len(out) == len(parts) { t.Fatalf("key %s not in %s", key, raw) } return "{" + strings.Join(out, ",") + "}" } // nullKey sets key to null in the flat JSON object raw. func nullKey(t *testing.T, raw, key string) string { t.Helper() parts := splitTop(raw) found := false for i, p := range parts { if strings.HasPrefix(p, `"`+key+`":`) { parts[i] = `"` + key + `":null` found = true } } if !found { t.Fatalf("key %s not in %s", key, raw) } return "{" + strings.Join(parts, ",") + "}" } // splitTop splits a flat JSON object into its "key":value members, keeping // commas inside arrays. func splitTop(raw string) []string { inner := strings.TrimSuffix(strings.TrimPrefix(raw, "{"), "}") var parts []string depth, start := 0, 0 for i, c := range inner { switch c { case '[': depth++ case ']': depth-- case ',': if depth == 0 { parts = append(parts, inner[start:i]) start = i + 1 } } } return append(parts, inner[start:]) } // TestNormalizeBody checks the body normalisation of the edit route (A6): // LF line ends, no surrounding blank lines, one trailing newline, and an // empty or whitespace-only body as null. func TestNormalizeBody(t *testing.T) { s := func(v string) *string { return &v } cases := []struct { name string in *string want *string }{ {"nil", nil, nil}, {"empty", s(""), nil}, {"whitespace", s(" \n\t\r\n "), nil}, {"crlf", s("a\r\nb\r\n"), s("a\nb\n")}, {"cr", s("a\rb"), s("a\nb\n")}, {"blank lines around", s("\n\n# Tytuł\n\ntekst\n\n\n"), s("# Tytuł\n\ntekst\n")}, {"no newline", s("tekst"), s("tekst\n")}, {"already normal", s("tekst\n"), s("tekst\n")}, } for _, tc := range cases { got := normalizeBody(tc.in) switch { case tc.want == nil && got != nil: t.Errorf("%s: normalizeBody = %q, want nil", tc.name, *got) case tc.want != nil && (got == nil || *got != *tc.want): t.Errorf("%s: normalizeBody = %v, want %q", tc.name, got, *tc.want) } } }