- Service.Start resumes an open test attempt, closes expired ones at their deadline and refuses a start past max_attempts under an advisory lock; StartAttempt stays as a wrapper - answers to a test are accepted until the deadline plus golem15.quizzes.test.grace_seconds; one_way answers are final - a test covers the whole quiz and keeps a stable per-attempt order - 409 bodies carry a code; list and attempt rows carry attempts_left, question_types and seconds_left; import accepts the test keys
85 lines
4.4 KiB
Go
85 lines
4.4 KiB
Go
package classes_test
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/sm-quizzes-plugin/classes"
|
|
)
|
|
|
|
func TestParseImportSample(t *testing.T) {
|
|
raw, err := os.ReadFile("../testdata/quiz.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
doc, err := classes.ParseImport(raw)
|
|
if err != nil {
|
|
t.Fatalf("ParseImport: %v", err)
|
|
}
|
|
if doc.Slug != "bezpieczenstwo-pojecia" {
|
|
t.Errorf("slug = %q, want it built from the title", doc.Slug)
|
|
}
|
|
if len(doc.Questions) != 3 || *doc.RoundSize != 5 || *doc.PassingScore != 60 {
|
|
t.Errorf("doc = %+v", doc)
|
|
}
|
|
}
|
|
|
|
func TestParseImportRejects(t *testing.T) {
|
|
cases := map[string]string{
|
|
`not json`: "invalid JSON",
|
|
`{"title":"A","questions":[{"text":"Q","type":"self_check"}],"extra":1}`: "unknown field",
|
|
`{"questions":[{"text":"Q","type":"self_check"}]}`: "title is required",
|
|
`{"title":"A","questions":[]}`: "questions must not be empty",
|
|
`{"title":"A","passing_score":101,"questions":[{"text":"Q","type":"self_check"}]}`: "passing_score",
|
|
`{"title":"A","round_size":0,"questions":[{"text":"Q","type":"self_check"}]}`: "round_size",
|
|
`{"title":"A","time_limit_seconds":0,"questions":[{"text":"Q","type":"self_check"}]}`: "time_limit_seconds",
|
|
`{"title":"A","questions":[{"text":"","type":"self_check"}]}`: "questions[0].text",
|
|
`{"title":"A","questions":[{"text":"Q","type":"essay"}]}`: "questions[0].type",
|
|
`{"title":"A","questions":[{"text":"Q","type":"self_check","points":-1}]}`: "points",
|
|
`{"title":"A","questions":[{"text":"Q","type":"self_check","answers":[{"text":"x"}]}]}`: "self_check question has no answers",
|
|
`{"title":"A","questions":[{"text":"Q","type":"single","answers":[{"text":"x","is_correct":true}]}]}`: "single question",
|
|
`{"title":"A","questions":[{"text":"Q","type":"single","answers":[{"text":"x"},{"text":"y"}]}]}`: "single question",
|
|
`{"title":"A","questions":[{"text":"Q","type":"multiple","answers":[{"text":"x"},{"text":"y"}]}]}`: "multiple question",
|
|
`{"title":"A","questions":[{"text":"Q","type":"multiple","answers":[{"text":"","is_correct":true},{"text":"y"}]}]}`: "answers[0].text",
|
|
`{"title":"!!!","questions":[{"text":"Q","type":"self_check"}]}`: "slug is empty",
|
|
}
|
|
for raw, want := range cases {
|
|
_, err := classes.ParseImport([]byte(raw))
|
|
if err == nil || !strings.Contains(err.Error(), want) {
|
|
t.Errorf("ParseImport(%s) = %v, want an error with %q", raw, err, want)
|
|
}
|
|
}
|
|
doc, err := classes.ParseImport([]byte(`{"title":"Ignored","slug":"Mój Slug","questions":[{"text":"Q","type":"self_check"}]}`))
|
|
if err != nil || doc.Slug != "moj-slug" {
|
|
t.Errorf("explicit slug = %v %v, want moj-slug", doc, err)
|
|
}
|
|
}
|
|
|
|
func TestParseImportTestMode(t *testing.T) {
|
|
doc, err := classes.ParseImport([]byte(`{"title":"T","mode":"test","navigation":"one_way","max_attempts":2,"questions":[{"text":"Q","type":"self_check"}]}`))
|
|
if err != nil {
|
|
t.Fatalf("ParseImport: %v", err)
|
|
}
|
|
if doc.Mode == nil || *doc.Mode != "test" || doc.Navigation == nil || *doc.Navigation != "one_way" || doc.MaxAttempts == nil || *doc.MaxAttempts != 2 {
|
|
t.Errorf("doc = %+v", doc)
|
|
}
|
|
for raw, want := range map[string]string{
|
|
`{"title":"T","mode":"exam","questions":[{"text":"Q","type":"self_check"}]}`: "mode must be one of learning, test",
|
|
`{"title":"T","navigation":"back","questions":[{"text":"Q","type":"self_check"}]}`: "navigation must be one of free, one_way",
|
|
`{"title":"T","max_attempts":0,"questions":[{"text":"Q","type":"self_check"}]}`: "max_attempts must be at least 1",
|
|
} {
|
|
if _, err := classes.ParseImport([]byte(raw)); err == nil || !strings.Contains(err.Error(), want) {
|
|
t.Errorf("ParseImport(%s) = %v, want %q", raw, err, want)
|
|
}
|
|
}
|
|
raw, err := os.ReadFile("../testdata/quiz.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
doc, err = classes.ParseImport(raw)
|
|
if err != nil || doc.Mode != nil || doc.Navigation != nil || doc.MaxAttempts != nil {
|
|
t.Errorf("sample = %+v %v, want no test-mode keys", doc, err)
|
|
}
|
|
}
|