Files
sm-quizzes-plugin/graders_boot_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

66 lines
2.3 KiB
Go

package quizzes_test
import (
"context"
"errors"
"slices"
"testing"
"git.golem15.com/golem15/sm-quizzes-plugin/graders"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/party"
)
// fixtureGrader is the grader both fixture plugins publish, under the same
// code.
type fixtureGrader struct{}
func (fixtureGrader) Code() string { return "fixture" }
func (fixtureGrader) Label() string { return "Fixture" }
func (fixtureGrader) Grade(context.Context, graders.Input) (graders.Result, error) {
return graders.Result{}, nil
}
// graderPlugin is a test-only plugin that provides fixtureGrader. It is
// registered here and activated only by the tests that name it.
type graderPlugin struct{ id string }
func (p *graderPlugin) ID() string { return p.id }
func (p *graderPlugin) Requires() []string { return []string{"golem15.quizzes"} }
func (p *graderPlugin) Register(*backpack.App) error { return nil }
func (p *graderPlugin) Boot(*backpack.App) error { return nil }
func (p *graderPlugin) QuizGraders() []graders.Grader { return []graders.Grader{fixtureGrader{}} }
func init() {
party.Register(&graderPlugin{id: "acme.gradersone"})
party.Register(&graderPlugin{id: "acme.graderstwo"})
}
func TestGradersCollectedAtBoot(t *testing.T) {
app := backpack.New(testConfig(t))
if _, err := party.Activate(app, []string{"golem15.user", "golem15.quizzes", "acme.gradersone"}); err != nil {
t.Fatalf("Activate: %v", err)
}
reg, ok := app.Lookup[*graders.Registry]()
if !ok || reg == nil {
t.Fatal("no *graders.Registry published")
}
if got := reg.Codes(); !slices.Equal(got, []string{graders.Manual, "fixture"}) {
t.Errorf("Codes = %v, want [manual fixture]", got)
}
if id, _ := reg.PluginID("fixture"); id != "acme.gradersone" {
t.Errorf("PluginID(fixture) = %q", id)
}
plain, _ := activate(t, nil, nil)
if reg, ok := plain.Lookup[*graders.Registry](); !ok || !slices.Equal(reg.Codes(), []string{graders.Manual}) {
t.Errorf("without grading plugins the registry should hold manual only")
}
dup := backpack.New(testConfig(t))
_, err := party.Activate(dup, []string{"golem15.user", "golem15.quizzes", "acme.gradersone", "acme.graderstwo"})
if !errors.Is(err, graders.ErrDuplicateCode) {
t.Errorf("Activate with two fixture plugins = %v, want ErrDuplicateCode", err)
}
}