Files
sm-quizzes-plugin/controllers/api/manage_test.go
Jakub Zych 9688b4d552 feat: carry open questions and the grader through import and the quiz tools
- import document, manage routes and MCP question tools take rubric and
  max_length; open questions refuse answers, max_length is 1..20000 and
  both apply to open questions only
- quizzes take a grader; the manage routes refuse a code the registry
  does not know, the import accepts any well-formed code
- quiz stats report unreviewed open answers and their average points
- import_quiz warns that replace deletes the grades and reviews
2026-11-09 18:36:20 +01:00

89 lines
4.5 KiB
Go

package api
import (
"net/http"
"strings"
"testing"
"git.golem15.com/golem15/summercms/modules/backpack"
)
// TestManageHandlersWithoutDatabase: every manage handler answers 503
// when the application has no database and the input is valid.
func TestManageHandlersWithoutDatabase(t *testing.T) {
h := New(backpack.New(nil))
for name, tc := range map[string]struct {
fn http.HandlerFunc
method string
target string
body string
}{
"list": {h.ManageListQuizzes, "GET", "/", ""},
"show": {h.ManageShowQuiz, "GET", "/", ""},
"create": {h.ManageCreateQuiz, "POST", "/", `{"title":"Pojęcia"}`},
"update": {h.ManageUpdateQuiz, "PATCH", "/", `{"max_attempts":0}`},
"delete": {h.ManageDeleteQuiz, "DELETE", "/?confirm=x", ""},
"publish": {h.ManagePublish, "POST", "/", ""},
"unpublish": {h.ManageUnpublish, "POST", "/", ""},
"stats": {h.ManageQuizStats, "GET", "/", ""},
"create question": {h.ManageCreateQuestion, "POST", "/", `{"text":"Q","type":"self_check"}`},
"update question": {h.ManageUpdateQuestion, "PATCH", "/", `{"text":"Q"}`},
"delete question": {h.ManageDeleteQuestion, "DELETE", "/", ""},
"reorder": {h.ManageReorderQuestions, "PUT", "/", `{"question_ids":[2,1]}`},
} {
code, _ := call(t, tc.fn, tc.method, tc.target, tc.body, 7, map[string]string{"slug": "x", "id": "3"})
if code != http.StatusServiceUnavailable {
t.Errorf("%s without a database = %d, want 503", name, code)
}
}
}
// TestManageWritesRefuseBadInputBeforeDatabase: invalid input is a 422
// even without a database, so it is checked before any read.
func TestManageWritesRefuseBadInputBeforeDatabase(t *testing.T) {
h := New(backpack.New(nil))
for name, tc := range map[string]struct {
fn http.HandlerFunc
method string
target string
body string
want string
}{
"create invalid JSON": {h.ManageCreateQuiz, "POST", "/", `{"title":`, "invalid JSON"},
"create empty body": {h.ManageCreateQuiz, "POST", "/", "", "invalid JSON"},
"create is_published": {h.ManageCreateQuiz, "POST", "/", `{"title":"A","is_published":true}`, "is_published"},
"create missing title": {h.ManageCreateQuiz, "POST", "/", `{"slug":"a"}`, "title is required"},
"create mode exam": {h.ManageCreateQuiz, "POST", "/", `{"title":"A","mode":"exam"}`, "mode must be one of"},
"update slug": {h.ManageUpdateQuiz, "PATCH", "/", `{"slug":"b"}`, "slug"},
"update is_published": {h.ManageUpdateQuiz, "PATCH", "/", `{"is_published":true}`, "is_published"},
"update passing 101": {h.ManageUpdateQuiz, "PATCH", "/", `{"passing_score":101}`, "passing_score"},
"create unknown grader": {h.ManageCreateQuiz, "POST", "/", `{"title":"A","grader":"zzz"}`, "grader must be one of manual"},
"create bad grader": {h.ManageCreateQuiz, "POST", "/", `{"title":"A","grader":"Bad Grader"}`, "grader must be lowercase"},
"update unknown grader": {h.ManageUpdateQuiz, "PATCH", "/", `{"grader":"zzz"}`, "grader must be one of manual"},
"question open answers": {h.ManageCreateQuestion, "POST", "/", `{"text":"Q","type":"open","answers":[{"text":"a"}]}`, "an open question has no answers"},
"question rubric single": {h.ManageCreateQuestion, "POST", "/", `{"text":"Q","type":"single","rubric":"R","answers":[{"text":"a","is_correct":true},{"text":"b"}]}`, "rubric applies to open questions only"},
"delete without confirm": {h.ManageDeleteQuiz, "DELETE", "/", "", "confirm must repeat the quiz slug"},
"delete wrong confirm": {h.ManageDeleteQuiz, "DELETE", "/?confirm=y", "", "confirm must repeat the quiz slug"},
} {
code, out := call(t, tc.fn, tc.method, tc.target, tc.body, 7, map[string]string{"slug": "x", "id": "3"})
msg, _ := out["message"].(string)
if code != http.StatusUnprocessableEntity || !strings.Contains(msg, tc.want) {
t.Errorf("%s = %d %v, want 422 with %q", name, code, out, tc.want)
}
}
}
// TestManageQuestionIDs: a question id that is not a positive integer is
// the 404 of a missing question, before the database.
func TestManageQuestionIDs(t *testing.T) {
h := New(backpack.New(nil))
for _, id := range []string{"", "0", "abc", "-1"} {
for name, fn := range map[string]http.HandlerFunc{"update": h.ManageUpdateQuestion, "delete": h.ManageDeleteQuestion} {
code, out := call(t, fn, "PATCH", "/", `{}`, 7, map[string]string{"slug": "x", "id": id})
if code != http.StatusNotFound || out["message"] != "Question not found." {
t.Errorf("%s id %q = %d %v, want 404 Question not found.", name, id, code, out)
}
}
}
}