- CanModerateMessage: group conversations only, chat members only - parseEmbedRefs, embedKey, escapeLike - /chat/embeds and /chat/embeds/resolve: 401, 422 before the database, 503 without one
162 lines
5.6 KiB
Go
162 lines
5.6 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func embedHandlers() *Handlers {
|
|
return &Handlers{DB: func() *gorm.DB { return nil }, ChatGroup: "studenci", ChatModeratorGroup: "moderatorzy"}
|
|
}
|
|
|
|
func serveEmbed(t *testing.T, fn http.HandlerFunc, method, target, body string, userID uint) (int, map[string]any) {
|
|
t.Helper()
|
|
req := httptest.NewRequest(method, target, strings.NewReader(body))
|
|
if userID != 0 {
|
|
req = req.WithContext(bouncer.WithUser(req.Context(), &bouncer.Principal{ID: userID}))
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
fn(rec, req)
|
|
var out map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
|
t.Fatalf("%s %s: body is not JSON: %v\n%s", method, target, err, rec.Body.String())
|
|
}
|
|
return rec.Code, out
|
|
}
|
|
|
|
// TestChatEmbedRefs: the resolver accepts deadline, class and reading refs
|
|
// with a positive id, collapses duplicates and takes at most 50 distinct
|
|
// refs; materials and quizzes are not embed types (D-14).
|
|
func TestChatEmbedRefs(t *testing.T) {
|
|
ok := []embedRef{{"deadline", 1}, {"class", 2}, {"reading", 3}, {"deadline", 1}}
|
|
got, errs := parseEmbedRefs(ok)
|
|
if len(errs) != 0 {
|
|
t.Fatalf("valid refs refused: %v", errs)
|
|
}
|
|
if want := []embedRef{{"deadline", 1}, {"class", 2}, {"reading", 3}}; fmt.Sprint(got) != fmt.Sprint(want) {
|
|
t.Errorf("refs = %v, want %v (duplicates collapse, order kept)", got, want)
|
|
}
|
|
for name, refs := range map[string][]embedRef{
|
|
"material": {{"material", 1}},
|
|
"quiz": {{"quiz", 1}},
|
|
"empty": {{"", 1}},
|
|
"id 0": {{"deadline", 0}},
|
|
"51 refs": manyRefs(51),
|
|
} {
|
|
if _, errs := parseEmbedRefs(refs); len(errs) == 0 {
|
|
t.Errorf("%s: accepted, want a validation error", name)
|
|
}
|
|
}
|
|
if _, errs := parseEmbedRefs(manyRefs(50)); len(errs) != 0 {
|
|
t.Errorf("50 refs refused: %v", errs)
|
|
}
|
|
dup := append(manyRefs(50), manyRefs(50)...)
|
|
if got, errs := parseEmbedRefs(dup); len(errs) != 0 || len(got) != 50 {
|
|
t.Errorf("50 distinct refs sent twice = %d refs, %v; want 50 and no error", len(got), errs)
|
|
}
|
|
if embedKey("deadline", 12) != "deadline:12" || embedKey("class", 3) != "class:3" {
|
|
t.Errorf("embedKey = %q / %q", embedKey("deadline", 12), embedKey("class", 3))
|
|
}
|
|
if embedSearchLimit != 20 {
|
|
t.Errorf("embedSearchLimit = %d, want 20", embedSearchLimit)
|
|
}
|
|
}
|
|
|
|
func manyRefs(n int) []embedRef {
|
|
out := make([]embedRef, n)
|
|
for i := range out {
|
|
out[i] = embedRef{Type: "reading", ID: uint(i + 1)}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// TestEscapeLike: %, _ and the backslash escape character are matched
|
|
// literally in an ILIKE pattern.
|
|
func TestEscapeLike(t *testing.T) {
|
|
for in, want := range map[string]string{
|
|
`50%_a\b`: `50\%\_a\\b`,
|
|
"plain": "plain",
|
|
"": "",
|
|
"Wstęp_100": `Wstęp\_100`,
|
|
} {
|
|
if got := escapeLike(in); got != want {
|
|
t.Errorf("escapeLike(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChatEmbedsUnauthenticated(t *testing.T) {
|
|
code, body := serveEmbed(t, embedHandlers().ChatEmbeds, http.MethodGet, "/?type=deadline", "", 0)
|
|
if code != http.StatusUnauthorized || body["error"] != true {
|
|
t.Errorf("GET /chat/embeds without a principal = %d %v, want 401", code, body)
|
|
}
|
|
}
|
|
|
|
func TestResolveChatEmbedsUnauthenticated(t *testing.T) {
|
|
code, body := serveEmbed(t, embedHandlers().ResolveChatEmbeds, http.MethodPost, "/", `{"refs":[{"type":"deadline","id":1}]}`, 0)
|
|
if code != http.StatusUnauthorized || body["error"] != true {
|
|
t.Errorf("POST /chat/embeds/resolve without a principal = %d %v, want 401", code, body)
|
|
}
|
|
}
|
|
|
|
// TestChatEmbedsInvalid: the search validates type and q before the
|
|
// database (422 without one).
|
|
func TestChatEmbedsInvalid(t *testing.T) {
|
|
h := embedHandlers()
|
|
for target, field := range map[string]string{
|
|
"/": "type",
|
|
"/?type=material": "type",
|
|
"/?type=deadline&q=" + strings.Repeat("ą", 101): "q",
|
|
} {
|
|
code, body := serveEmbed(t, h.ChatEmbeds, http.MethodGet, target, "", 7)
|
|
if code != http.StatusUnprocessableEntity {
|
|
t.Errorf("%s = %d %v, want 422", target, code, body)
|
|
continue
|
|
}
|
|
if errs, _ := body["errors"].(map[string]any); errs[field] == nil {
|
|
t.Errorf("%s: errors %v have no %s", target, body["errors"], field)
|
|
}
|
|
}
|
|
code, _ := serveEmbed(t, h.ChatEmbeds, http.MethodGet, "/?type=reading&q="+strings.Repeat("ą", 100), "", 7)
|
|
if code != http.StatusServiceUnavailable {
|
|
t.Errorf("100-rune q = %d, want to pass validation (503 without a database)", code)
|
|
}
|
|
}
|
|
|
|
// TestResolveChatEmbedsInvalid: an unsupported type, id 0, too many refs,
|
|
// an unknown field or a malformed body is 422 before the database.
|
|
func TestResolveChatEmbedsInvalid(t *testing.T) {
|
|
h := embedHandlers()
|
|
many, _ := json.Marshal(map[string]any{"refs": manyRefs(51)})
|
|
for name, body := range map[string]string{
|
|
"material": `{"refs":[{"type":"material","id":1}]}`,
|
|
"id 0": `{"refs":[{"type":"deadline","id":0}]}`,
|
|
"51 refs": string(many),
|
|
"unknown field": `{"refs":[],"extra":1}`,
|
|
"not an object": `[1]`,
|
|
"malformed": `{`,
|
|
"empty body": ``,
|
|
} {
|
|
code, out := serveEmbed(t, h.ResolveChatEmbeds, http.MethodPost, "/", body, 7)
|
|
if code != http.StatusUnprocessableEntity {
|
|
t.Errorf("%s = %d %v, want 422", name, code, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestResolveChatEmbedsNoDatabase: valid refs pass validation and need the
|
|
// database (503 without one).
|
|
func TestResolveChatEmbedsNoDatabase(t *testing.T) {
|
|
code, body := serveEmbed(t, embedHandlers().ResolveChatEmbeds, http.MethodPost, "/", `{"refs":[{"type":"deadline","id":1},{"type":"class","id":2},{"type":"reading","id":3}]}`, 7)
|
|
if code != http.StatusServiceUnavailable {
|
|
t.Errorf("valid refs without a database = %d %v, want 503", code, body)
|
|
}
|
|
}
|