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 }