Enforce strict initial assessment set validation.

Require INITIAL_ASSESSMENT titles to follow the Level Test A1/A2/B1/B2 format and ensure passing_score is always present on create and update.

Made-with: Cursor
This commit is contained in:
Yared Yemane 2026-04-29 02:47:21 -07:00
parent 9027b65011
commit cdb0fa1bb3

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -550,6 +551,15 @@ type listQuestionSetsRes struct {
TotalCount int64 `json:"total_count"` TotalCount int64 `json:"total_count"`
} }
var initialAssessmentTitlePattern = regexp.MustCompile(`^Level Test (A1|A2|B1|B2) \([^)]+\)$`)
func validateInitialAssessmentTitle(title string) error {
if !initialAssessmentTitlePattern.MatchString(strings.TrimSpace(title)) {
return fmt.Errorf("title must match format: Level Test <A1|A2|B1|B2> (<description>)")
}
return nil
}
func isSequenceGatedPractice(set domain.QuestionSet) bool { func isSequenceGatedPractice(set domain.QuestionSet) bool {
if !strings.EqualFold(set.SetType, string(domain.QuestionSetTypePractice)) || set.OwnerType == nil { if !strings.EqualFold(set.SetType, string(domain.QuestionSetTypePractice)) || set.OwnerType == nil {
return false return false
@ -605,6 +615,20 @@ func (h *Handler) CreateQuestionSet(c *fiber.Ctx) error {
Error: err.Error(), Error: err.Error(),
}) })
} }
if strings.EqualFold(req.SetType, string(domain.QuestionSetTypeInitialAssessment)) {
if err := validateInitialAssessmentTitle(req.Title); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid initial assessment title",
Error: err.Error(),
})
}
if req.PassingScore == nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid initial assessment set",
Error: "passing_score is required for INITIAL_ASSESSMENT question sets",
})
}
}
input := domain.CreateQuestionSetInput{ input := domain.CreateQuestionSetInput{
Title: req.Title, Title: req.Title,
@ -883,6 +907,14 @@ func (h *Handler) UpdateQuestionSet(c *fiber.Ctx) error {
}) })
} }
existingSet, err := h.questionsSvc.GetQuestionSetByID(c.Context(), id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
Message: "Question set not found",
Error: err.Error(),
})
}
var req updateQuestionSetReq var req updateQuestionSetReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
@ -896,6 +928,30 @@ func (h *Handler) UpdateQuestionSet(c *fiber.Ctx) error {
title = *req.Title title = *req.Title
} }
if strings.EqualFold(existingSet.SetType, string(domain.QuestionSetTypeInitialAssessment)) {
effectiveTitle := existingSet.Title
if req.Title != nil {
effectiveTitle = *req.Title
}
if err := validateInitialAssessmentTitle(effectiveTitle); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid initial assessment title",
Error: err.Error(),
})
}
effectivePassingScore := existingSet.PassingScore
if req.PassingScore != nil {
effectivePassingScore = req.PassingScore
}
if effectivePassingScore == nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid initial assessment set",
Error: "passing_score is required for INITIAL_ASSESSMENT question sets",
})
}
}
input := domain.CreateQuestionSetInput{ input := domain.CreateQuestionSetInput{
Title: title, Title: title,
Description: req.Description, Description: req.Description,