Files
sm-quizzes-plugin/graders/registry_test.go
Jakub Zych 80ea79ee3e 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
2026-11-09 17:51:55 +01:00

80 lines
2.5 KiB
Go

package graders_test
import (
"context"
"errors"
"slices"
"strings"
"testing"
"git.golem15.com/golem15/sm-quizzes-plugin/graders"
)
type fake struct{ code, label string }
func (f fake) Code() string { return f.code }
func (f fake) Label() string { return f.label }
func (f fake) Grade(context.Context, graders.Input) (graders.Result, error) {
return graders.Result{}, nil
}
func TestGradersRegistry(t *testing.T) {
reg := graders.NewRegistry()
if got := reg.Codes(); !slices.Equal(got, []string{graders.Manual}) {
t.Fatalf("empty Codes = %v, want [manual]", got)
}
if err := reg.Add("acme.one", fake{"x", "X grader"}); err != nil {
t.Fatal(err)
}
if got := reg.Codes(); !slices.Equal(got, []string{graders.Manual, "x"}) {
t.Errorf("Codes = %v, want [manual x]", got)
}
if g, ok := reg.Get("x"); !ok || g.Code() != "x" {
t.Errorf("Get(x) = %v, %v", g, ok)
}
for _, code := range []string{graders.Manual, "zzz", ""} {
if _, ok := reg.Get(code); ok {
t.Errorf("Get(%q) = true, want false", code)
}
}
if id, ok := reg.PluginID("x"); !ok || id != "acme.one" {
t.Errorf("PluginID(x) = %q, %v", id, ok)
}
err := reg.Add("acme.two", fake{"x", "Other"})
if !errors.Is(err, graders.ErrDuplicateCode) || !strings.Contains(err.Error(), "acme.one") || !strings.Contains(err.Error(), "acme.two") {
t.Errorf("duplicate Add = %v, want ErrDuplicateCode naming both plugins", err)
}
if err := reg.Add("acme.two", fake{graders.Manual, "M"}); !errors.Is(err, graders.ErrReservedCode) {
t.Errorf("manual Add = %v, want ErrReservedCode", err)
}
for _, g := range []graders.Grader{nil, fake{"", "E"}, fake{"Bad Code", "B"}, fake{"9lives", "N"}, fake{strings.Repeat("a", 65), "L"}} {
if err := reg.Add("acme.two", g); err == nil {
t.Errorf("Add(%v) = nil, want an error", g)
}
}
if got := reg.Codes(); !slices.Equal(got, []string{graders.Manual, "x"}) {
t.Errorf("rejected Adds changed Codes: %v", got)
}
opts := reg.Options()
want := []graders.Option{{Code: graders.Manual, Label: graders.ManualLabel}, {Code: "x", Label: "X grader"}}
if !slices.Equal(opts, want) {
t.Errorf("Options = %v, want %v", opts, want)
}
var none *graders.Registry
if got := none.Codes(); !slices.Equal(got, []string{graders.Manual}) {
t.Errorf("nil Codes = %v", got)
}
if _, ok := none.Get("x"); ok {
t.Error("nil Get = true")
}
if got := none.Options(); len(got) != 1 || got[0].Code != graders.Manual {
t.Errorf("nil Options = %v", got)
}
if err := none.Add("acme.one", fake{"y", "Y"}); err == nil {
t.Error("nil Add = nil, want an error")
}
}