WR-01: a deterministic server-side guard behind the grader's prompt. storeGrade drops feedback sharing a run of six words with the rubric (runs also in the question or model answer do not count) and notes it for the owner in grading_error.
106 lines
4.1 KiB
Go
106 lines
4.1 KiB
Go
package classes_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/sm-quizzes-plugin/classes"
|
|
"git.golem15.com/golem15/sm-quizzes-plugin/graders"
|
|
"git.golem15.com/golem15/sm-quizzes-plugin/models"
|
|
)
|
|
|
|
type stubGrader struct{ code string }
|
|
|
|
func (g stubGrader) Code() string { return g.code }
|
|
func (g stubGrader) Label() string { return g.code }
|
|
func (g stubGrader) Grade(context.Context, graders.Input) (graders.Result, error) {
|
|
return graders.Result{}, nil
|
|
}
|
|
|
|
func TestGraderFor(t *testing.T) {
|
|
reg := graders.NewRegistry()
|
|
if err := reg.Add("acme.x", stubGrader{"x"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if g, ok := classes.GraderFor(reg, &models.Quiz{Grader: "x"}); !ok || g.Code() != "x" {
|
|
t.Errorf("GraderFor(x) = %v, %v", g, ok)
|
|
}
|
|
for _, code := range []string{graders.Manual, "unknown", ""} {
|
|
if _, ok := classes.GraderFor(reg, &models.Quiz{Grader: code}); ok {
|
|
t.Errorf("GraderFor(%q) = true, want false", code)
|
|
}
|
|
}
|
|
if _, ok := classes.GraderFor(nil, &models.Quiz{Grader: "x"}); ok {
|
|
t.Error("GraderFor(nil registry) = true")
|
|
}
|
|
if _, ok := classes.GraderFor(reg, nil); ok {
|
|
t.Error("GraderFor(nil quiz) = true")
|
|
}
|
|
}
|
|
|
|
func TestNextGrading(t *testing.T) {
|
|
str := func(s string) *string { return &s }
|
|
boom := errors.New("boom")
|
|
cases := []struct {
|
|
name string
|
|
status *string
|
|
rowSeq int
|
|
jobSeq int
|
|
attempts int
|
|
err error
|
|
want classes.GradingStep
|
|
}{
|
|
{"nil status", nil, 1, 1, 1, nil, classes.GradingSkip},
|
|
{"graded", str(models.GradingGraded), 1, 1, 1, nil, classes.GradingSkip},
|
|
{"failed", str(models.GradingFailed), 1, 1, 1, nil, classes.GradingSkip},
|
|
{"reviewed", str(models.GradingReviewed), 1, 1, 1, nil, classes.GradingSkip},
|
|
{"stale seq", str(models.GradingPending), 2, 1, 1, nil, classes.GradingSkip},
|
|
{"stale seq with error", str(models.GradingPending), 2, 1, 3, boom, classes.GradingSkip},
|
|
{"store", str(models.GradingPending), 1, 1, 1, nil, classes.GradingStore},
|
|
{"retry 1", str(models.GradingPending), 1, 1, 1, boom, classes.GradingRetry},
|
|
{"retry 2", str(models.GradingPending), 1, 1, 2, boom, classes.GradingRetry},
|
|
{"fail at max", str(models.GradingPending), 1, 1, classes.GradeMaxAttempts, boom, classes.GradingFail},
|
|
{"fail past max", str(models.GradingPending), 1, 1, classes.GradeMaxAttempts + 1, boom, classes.GradingFail},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := classes.NextGrading(tc.status, tc.rowSeq, tc.jobSeq, tc.attempts, tc.err); got != tc.want {
|
|
t.Errorf("%s: NextGrading = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
if classes.GradeMaxAttempts != 3 {
|
|
t.Errorf("GradeMaxAttempts = %d, want 3", classes.GradeMaxAttempts)
|
|
}
|
|
}
|
|
|
|
func TestLeaksRubric(t *testing.T) {
|
|
rubric := "Pełne punkty: wymienia trzy przyczyny upadku Republiki Weimarskiej, w tym kryzys gospodarczy z 1929 roku."
|
|
question := "Wyjaśnij, dlaczego upadła Republika Weimarska."
|
|
model := "Republika upadła przez kryzys gospodarczy z 1929 roku i słabość instytucji."
|
|
cases := []struct {
|
|
name string
|
|
feedback string
|
|
want bool
|
|
}{
|
|
{"own words", "Dobra odpowiedź, ale zabrakło trzeciej przyczyny i dat.", false},
|
|
{"verbatim run", "Oczekiwano, że odpowiedź WYMIENIA trzy przyczyny upadku Republiki Weimarskiej.", true},
|
|
{"punctuation and case ignored", "pełne punkty - wymienia, trzy; przyczyny upadku republiki!", true},
|
|
{"run also in the model answer", "Brakuje wzmianki o tym, że kryzys gospodarczy z 1929 roku i słabość instytucji zaszkodziły.", false},
|
|
{"empty feedback", "", false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := classes.LeaksRubric(tc.feedback, rubric, question, model); got != tc.want {
|
|
t.Errorf("%s: LeaksRubric = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
if classes.LeaksRubric("Za mało szczegółów.", "", question) {
|
|
t.Error("an empty rubric leaks")
|
|
}
|
|
if classes.LeaksRubric("Wymień trzy przyczyny.", "trzy przyczyny", question) {
|
|
t.Error("a rubric under the minimum length leaks")
|
|
}
|
|
if !classes.LeaksRubric("Wymagano: podać daty, nazwiska i skutki. Zabrakło dat.", "Podać daty, nazwiska i skutki.") {
|
|
t.Error("a short rubric quoted whole does not leak")
|
|
}
|
|
}
|