feat: grade open answers through a grader registry in the background
- graders registry collected at boot from every HasGraders plugin, manual first; a duplicate or reserved code fails boot - grading job on queue quizzes_grading with three attempts; a stale or reviewed answer is never touched; the last failure marks it failed - learning answers enqueue at once, tests at close; a grade recalculates a finished attempt and fires AnswerGradedEvent after commit
This commit is contained in:
74
classes/grading_test.go
Normal file
74
classes/grading_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user