package questions import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "boostai-backend/internal/http/respond" "boostai-backend/internal/questiongen" "boostai-backend/internal/sqlc" "github.com/gofiber/fiber/v2" ) func TestGenerateQuestionsReturnsGeneratorUnavailable(t *testing.T) { t.Parallel() handler := NewHandler(nil, nil) status, body := performGenerateRequest(t, handler, map[string]any{ "topic": "fractions", "difficulty": "easy", "count": 1, }, true) if status != fiber.StatusServiceUnavailable { t.Fatalf("expected status %d, got %d", fiber.StatusServiceUnavailable, status) } if body.Error != "generator_unavailable" { t.Fatalf("expected generator_unavailable error, got %#v", body) } } func TestGenerateQuestionsRequiresTeacherAuthentication(t *testing.T) { t.Parallel() handler := NewHandler(nil, questiongen.NewService()) status, body := performGenerateRequest(t, handler, map[string]any{ "topic": "fractions", "difficulty": "easy", "count": 1, }, false) if status != fiber.StatusUnauthorized { t.Fatalf("expected status %d, got %d", fiber.StatusUnauthorized, status) } if body.Error != "unauthorized" { t.Fatalf("expected unauthorized error, got %#v", body) } } func TestGenerateQuestionsRejectsZeroCount(t *testing.T) { t.Parallel() handler := NewHandler(nil, questiongen.NewService()) status, body := performGenerateRequest(t, handler, map[string]any{ "topic": "fractions", "difficulty": "easy", "count": 0, }, true) if status != fiber.StatusBadRequest { t.Fatalf("expected status %d, got %d", fiber.StatusBadRequest, status) } if body.Message != "count must be between 1 and 25" { t.Fatalf("expected count validation message, got %#v", body) } } func TestGenerateQuestionsRejectsInvalidStatus(t *testing.T) { t.Parallel() handler := NewHandler(nil, questiongen.NewService()) status, body := performGenerateRequest(t, handler, map[string]any{ "topic": "fractions", "difficulty": "easy", "count": 1, "status": "invalid", }, true) if status != fiber.StatusBadRequest { t.Fatalf("expected status %d, got %d", fiber.StatusBadRequest, status) } if body.Message != "status must be draft, published, or archived" { t.Fatalf("expected invalid status message, got %#v", body) } } func TestGenerateQuestionsRejectsInvalidTopic(t *testing.T) { t.Parallel() handler := NewHandler(nil, questiongen.NewService()) status, body := performGenerateRequest(t, handler, map[string]any{ "topic": "not_a_topic", "difficulty": "easy", "count": 1, }, true) if status != fiber.StatusBadRequest { t.Fatalf("expected status %d, got %d", fiber.StatusBadRequest, status) } if body.Error != "invalid_request" { t.Fatalf("expected invalid_request error, got %#v", body) } } func performGenerateRequest(t *testing.T, handler *Handler, payload map[string]any, authenticated bool) (int, respond.ErrorBody) { t.Helper() app := fiber.New() app.Post("/questions/generate", func(c *fiber.Ctx) error { if authenticated { c.Locals("auth.user_id", int64(42)) c.Locals("auth.role", sqlc.UserRoleTeacher) } return handler.GenerateQuestions(c) }) bodyBytes, err := json.Marshal(payload) if err != nil { t.Fatalf("marshal payload: %v", err) } req := httptest.NewRequest(http.MethodPost, "/questions/generate", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") resp, err := app.Test(req) if err != nil { t.Fatalf("app.Test returned error: %v", err) } defer resp.Body.Close() var errorBody respond.ErrorBody if err := json.NewDecoder(resp.Body).Decode(&errorBody); err != nil { t.Fatalf("decode error response: %v", err) } return resp.StatusCode, errorBody }