package quizzes_test import ( "slices" "strings" "testing" "git.golem15.com/golem15/sm-quizzes-plugin/models" "git.golem15.com/golem15/summercms/modules/bouncer" "git.golem15.com/golem15/summercms/modules/cabana" "git.golem15.com/golem15/summercms/modules/lagoon" "git.golem15.com/golem15/summercms/modules/pact" "gorm.io/gorm" "gorm.io/gorm/utils/tests" ) func TestAdminCompiles(t *testing.T) { app, plugins := activate(t, nil, nil) routes, err := cabana.Activate(app, plugins) if err != nil { t.Fatalf("cabana.Activate: %v", err) } if routes == nil { t.Fatal("no admin routes") } p := quizzesPlugin(t, plugins) fsys := p.(pact.AdminAssets).AdminFS() var ids []string for _, ctl := range p.(pact.HasAdminControllers).AdminControllers() { ids = append(ids, ctl.ID()) if _, err := cabana.CompileList(p.ID(), ctl, fsys); err != nil { t.Errorf("%s list: %v", ctl.ID(), err) } _, formErr := cabana.CompileForm(p.ID(), ctl, fsys) if ctl.ID() == "golem15.quizzes.attempts" { if formErr == nil { t.Error("attempts has a form; it must stay read-only") } } else if formErr != nil { t.Errorf("%s form: %v", ctl.ID(), formErr) } } slices.Sort(ids) want := []string{"golem15.quizzes.answers", "golem15.quizzes.attempts", "golem15.quizzes.questions", "golem15.quizzes.quizzes", "golem15.quizzes.reviews"} if !slices.Equal(ids, want) { t.Errorf("controllers = %v, want %v", ids, want) } editor := &bouncer.Principal{ID: 1, Backend: true, PermissionGrants: map[string]bool{"golem15.quizzes.access_quizzes": true}} for _, ctl := range p.(pact.HasAdminControllers).AdminControllers() { perms := ctl.(pact.AdminPermissioned).RequiredPermissions() allowed := cabana.Allows(editor, perms) if wantAllowed := ctl.ID() != "golem15.quizzes.attempts"; allowed != wantAllowed { t.Errorf("%s allowed for a quiz editor = %v, want %v", ctl.ID(), allowed, wantAllowed) } } nav := p.(pact.HasNavigation).Navigation() if len(nav) != 1 || nav[0].Label != "golem15.quizzes::lang.plugin.name" || len(nav[0].SideMenu) != 5 { t.Errorf("navigation = %+v", nav) } else { var codes []string for _, item := range nav[0].SideMenu { codes = append(codes, item.Code) } if !slices.Equal(codes, []string{"quizzes", "questions", "answers", "reviews", "attempts"}) { t.Errorf("side menu = %v, want reviews after answers", codes) } if review := nav[0].SideMenu[3]; review.Controller != "golem15.quizzes.reviews" || !slices.Equal(review.Permissions, []string{"golem15.quizzes.access_quizzes"}) { t.Errorf("reviews entry = %+v", review) } } } func TestQuestionFilterScopes(t *testing.T) { q := models.Question{} if got := q.FilterOptions(models.ScopeByType); len(got) != 4 || got[3].Value != models.TypeOpen { t.Errorf("type options = %v", got) } if got := q.FilterScope("unknown", nil, "x"); got != nil { t.Errorf("FilterScope on a nil db = %v", got) } } func TestReviewFilterScopes(t *testing.T) { r := models.Review{} if got := r.FilterScopes(); !slices.Equal(got, []string{models.ScopeByQuiz, models.ScopeByStatus, models.ScopeByStudent}) { t.Errorf("FilterScopes = %v", got) } opts := r.FilterOptions(models.ScopeByStatus) var values []string for _, o := range opts { values = append(values, o.Value) if o.Label != "golem15.quizzes::lang.reviews.status_"+o.Value { t.Errorf("status %s label = %q", o.Value, o.Label) } } if !slices.Equal(values, []string{"pending", "graded", "failed", "reviewed"}) { t.Errorf("status options = %v", values) } if got := r.FilterScope("unknown", nil, "x"); got != nil { t.Errorf("FilterScope on a nil db = %v", got) } db, err := gorm.Open(tests.DummyDialector{}, &gorm.Config{DryRun: true}) if err != nil { t.Fatalf("dry-run db: %v", err) } sqlOf := func(scope string, value any) string { var rows []models.Review stmt := r.FilterScope(scope, db.Session(&gorm.Session{DryRun: true}).Model(&models.Review{}), value).Find(&rows).Statement return stmt.SQL.String() } if got := sqlOf(models.ScopeByStatus, []any{"graded"}); !strings.Contains(got, "golem15_quizzes_attempt_answers.grading_status IN") { t.Errorf("status SQL = %s", got) } if got := sqlOf(models.ScopeByStatus, []any{"bogus"}); !strings.Contains(got, "1 = 0") { t.Errorf("unknown status SQL = %s, want no rows", got) } if got := sqlOf(models.ScopeByQuiz, []any{"3"}); !strings.Contains(got, "question_id IN (SELECT id FROM golem15_quizzes_questions WHERE quiz_id IN") { t.Errorf("quiz SQL = %s", got) } if got := sqlOf(models.ScopeByStudent, []any{"7"}); !strings.Contains(got, "attempt_id IN (SELECT id FROM golem15_quizzes_attempts WHERE user_id IN") { t.Errorf("student SQL = %s", got) } if got := sqlOf(models.ScopeByQuiz, nil); strings.Contains(got, "question_id IN") { t.Errorf("empty value SQL = %s, want no filter", got) } if name, err := lagoon.MorphType(nil, &models.Review{}); err != nil || name == "golem15_quizzes_attempt_answers" { t.Errorf("Review morph type = %q, %v; it must differ from AttemptAnswer's table", name, err) } }