Files
BoostAI/Backend/internal/questiongen/service_test.go
2026-05-25 17:05:06 +01:00

176 lines
4.3 KiB
Go

package questiongen
import (
"reflect"
"regexp"
"strconv"
"testing"
"boostai-backend/internal/sqlc"
)
func TestServiceGenerateDeterministicWithSeed(t *testing.T) {
t.Parallel()
service := NewService()
params := GenerateParams{
Topic: sqlc.QuestionTopicFractions,
Difficulty: sqlc.QuestionDifficultyMedium,
Count: 3,
Seed: 123456,
}
first, firstSeed, err := service.Generate(params)
if err != nil {
t.Fatalf("first generate returned error: %v", err)
}
second, secondSeed, err := service.Generate(params)
if err != nil {
t.Fatalf("second generate returned error: %v", err)
}
if firstSeed != params.Seed || secondSeed != params.Seed {
t.Fatalf("expected seed %d to be reused, got %d and %d", params.Seed, firstSeed, secondSeed)
}
if !reflect.DeepEqual(first, second) {
t.Fatalf("expected deterministic output for identical seed\nfirst: %#v\nsecond: %#v", first, second)
}
}
func TestServiceGenerateSupportsAllTopicsAndDifficulties(t *testing.T) {
t.Parallel()
service := NewService()
topics := []sqlc.QuestionTopic{
sqlc.QuestionTopicPlaceValue,
sqlc.QuestionTopicArithmetic,
sqlc.QuestionTopicNegativeNumbers,
sqlc.QuestionTopicBidmas,
sqlc.QuestionTopicFractions,
sqlc.QuestionTopicAlgebra,
sqlc.QuestionTopicGeometry,
sqlc.QuestionTopicData,
}
difficulties := []sqlc.QuestionDifficulty{
sqlc.QuestionDifficultyEasy,
sqlc.QuestionDifficultyMedium,
sqlc.QuestionDifficultyHard,
}
for _, topic := range topics {
topic := topic
for _, difficulty := range difficulties {
difficulty := difficulty
t.Run(string(topic)+"_"+string(difficulty), func(t *testing.T) {
t.Parallel()
items, usedSeed, err := service.Generate(GenerateParams{
Topic: topic,
Difficulty: difficulty,
Count: 2,
Seed: 99,
})
if err != nil {
t.Fatalf("generate returned error: %v", err)
}
if usedSeed != 99 {
t.Fatalf("expected used seed 99, got %d", usedSeed)
}
if len(items) != 2 {
t.Fatalf("expected 2 generated questions, got %d", len(items))
}
for i, item := range items {
if item.Title == "" {
t.Fatalf("item %d: title should not be empty", i)
}
if item.Prompt == "" {
t.Fatalf("item %d: prompt should not be empty", i)
}
if item.CorrectAnswer == "" {
t.Fatalf("item %d: correct answer should not be empty", i)
}
if len(item.WorkedSolution) == 0 {
t.Fatalf("item %d: worked solution should not be empty", i)
}
assertContainsTag(t, item.Tags, string(topic))
assertContainsTag(t, item.Tags, string(difficulty))
assertContainsTag(t, item.Tags, "rng_generated")
}
})
}
}
}
func TestFractionsEasyUsesSingleDigitPromptValues(t *testing.T) {
t.Parallel()
service := NewService()
items, _, err := service.Generate(GenerateParams{
Topic: sqlc.QuestionTopicFractions,
Difficulty: sqlc.QuestionDifficultyEasy,
Count: 20,
Seed: 20260522,
})
if err != nil {
t.Fatalf("generate returned error: %v", err)
}
for i, item := range items {
values := extractIntegers(item.Prompt)
if len(values) != 3 {
t.Fatalf("item %d: expected 3 integers in prompt, got %v from %q", i, values, item.Prompt)
}
for _, value := range values {
if value < 0 || value > 9 {
t.Fatalf("item %d: expected easy fraction prompt values to be single-digit, got %d in %q", i, value, item.Prompt)
}
}
assertContainsTag(t, item.Tags, "single_digit")
}
}
func TestServiceGenerateRejectsUnsupportedTopic(t *testing.T) {
t.Parallel()
service := NewService()
_, _, err := service.Generate(GenerateParams{
Topic: sqlc.QuestionTopic("unsupported_topic"),
Difficulty: sqlc.QuestionDifficultyEasy,
Count: 1,
Seed: 1,
})
if err == nil {
t.Fatal("expected unsupported topic to return an error")
}
}
func assertContainsTag(t *testing.T, tags []string, want string) {
t.Helper()
for _, tag := range tags {
if tag == want {
return
}
}
t.Fatalf("expected tags %v to contain %q", tags, want)
}
var integerPattern = regexp.MustCompile(`-?\d+`)
func extractIntegers(input string) []int {
matches := integerPattern.FindAllString(input, -1)
values := make([]int, 0, len(matches))
for _, match := range matches {
value, err := strconv.Atoi(match)
if err != nil {
continue
}
values = append(values, value)
}
return values
}