package queryapi_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "time" queryapi "queryorchestration/api/queryAPI" "queryorchestration/internal/cognitoauth" "queryorchestration/internal/eula" "queryorchestration/internal/test" "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/oapi-codegen/nullable" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // ptrTime is a helper to create a pointer to a time.Time value. func ptrTime(t time.Time) *time.Time { return &t } // setupEulaTestContext creates an echo context with the specified path and method. func setupEulaTestContext(method, path string, body interface{}) (echo.Context, *httptest.ResponseRecorder) { e := echo.New() var req *http.Request if body != nil { jsonBody, _ := json.Marshal(body) req = httptest.NewRequest(method, path, bytes.NewReader(jsonBody)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) } else { req = httptest.NewRequest(method, path, nil) } rec := httptest.NewRecorder() ctx := e.NewContext(req, rec) return ctx, rec } // setupEulaTestContextWithUser creates an echo context with user info set. func setupEulaTestContextWithUser(method, path string, body interface{}, userSubject, userEmail string) (echo.Context, *httptest.ResponseRecorder) { ctx, rec := setupEulaTestContext(method, path, body) // Set up user info in context (simulating authenticated user) // Note: GetUserSubject expects map[string]interface{} with "sub" key // Note: GetUserInfo expects cognitoauth.UserInfo (not pointer) ctx.Set("user_claims", map[string]interface{}{ "sub": userSubject, "email": userEmail, }) ctx.Set("user_info", cognitoauth.UserInfo{ Email: userEmail, }) return ctx, rec } // createTestEulaVersion creates a test EULA version in the database. func createTestEulaVersion(t *testing.T, svc *eula.Service, version, title string, activate bool) *uuid.UUID { ctx := t.Context() input := &eula.CreateVersionInput{ Version: version, Title: title, Content: "# Test EULA\n\nThis is test content.", EffectiveDate: ptrTime(time.Now()), CreatedBy: "admin@test.com", } created, err := svc.CreateVersion(ctx, input) require.NoError(t, err) if activate { _, err = svc.ActivateVersion(ctx, created.ID, "admin@test.com") require.NoError(t, err) } return &created.ID } func TestGetCurrentEula(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) t.Run("returns current EULA version", func(t *testing.T) { // Create and activate a version versionStr := "1.0.0-test-" + uuid.New().String()[:8] createTestEulaVersion(t, svc.Eula, versionStr, "Test EULA", true) ctx, rec := setupEulaTestContext(http.MethodGet, "/eula", nil) err := cons.GetCurrentEula(ctx) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaPublicResponse err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, versionStr, response.Version) }) t.Run("returns 404 when no current version", func(t *testing.T) { // Create a new config without any EULA versions activated cfg2 := &ControllerConfig{} test.CreateDB(t, cfg2) initializeTestConfig(t, cfg2) svc2 := createControllerServices(cfg2) cons2 := queryapi.NewControllers(svc2, cfg2) ctx, rec := setupEulaTestContext(http.MethodGet, "/eula", nil) err := cons2.GetCurrentEula(ctx) require.NoError(t, err) assert.Equal(t, http.StatusNotFound, rec.Code) }) } func TestGetEulaStatus(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) // Create and activate a version versionStr := "status-test-" + uuid.New().String()[:8] versionID := createTestEulaVersion(t, svc.Eula, versionStr, "Status Test EULA", true) t.Run("returns status for unagreed user", func(t *testing.T) { userSubject := "user-" + uuid.New().String()[:8] ctx, rec := setupEulaTestContextWithUser(http.MethodGet, "/eula/status", nil, userSubject, "user@test.com") err := cons.GetEulaStatus(ctx) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaStatusResponse err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.False(t, response.HasAgreed) assert.Equal(t, versionStr, response.CurrentVersion) }) t.Run("returns status for agreed user", func(t *testing.T) { userSubject := "agreed-user-" + uuid.New().String()[:8] userEmail := "agreed@test.com" // Record agreement _, err := svc.Eula.RecordAgreement(t.Context(), &eula.RecordAgreementInput{ CognitoSubjectID: userSubject, UserEmail: userEmail, EulaVersionID: *versionID, IPAddress: "192.168.1.1", }) require.NoError(t, err) ctx, rec := setupEulaTestContextWithUser(http.MethodGet, "/eula/status", nil, userSubject, userEmail) err = cons.GetEulaStatus(ctx) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaStatusResponse err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.HasAgreed) }) t.Run("returns 401 for unauthenticated user", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/eula/status", nil) // Don't set user info err := cons.GetEulaStatus(ctx) require.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) }) } func TestAgreeToEula(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) // Create and activate a version versionStr := "agree-test-" + uuid.New().String()[:8] createTestEulaVersion(t, svc.Eula, versionStr, "Agree Test EULA", true) t.Run("records new agreement", func(t *testing.T) { userSubject := "agree-user-" + uuid.New().String()[:8] userEmail := "agreeuser@test.com" ctx, rec := setupEulaTestContextWithUser(http.MethodPost, "/eula/agree", nil, userSubject, userEmail) ctx.Request().RemoteAddr = "192.168.1.100:12345" err := cons.AgreeToEula(ctx) require.NoError(t, err) assert.Equal(t, http.StatusCreated, rec.Code) var response queryapi.EulaAgreementResponse err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, versionStr, response.EulaVersion) }) t.Run("idempotent - returns 200 for existing agreement", func(t *testing.T) { userSubject := "idempotent-user-" + uuid.New().String()[:8] userEmail := "idempotent@test.com" // First call ctx1, rec1 := setupEulaTestContextWithUser(http.MethodPost, "/eula/agree", nil, userSubject, userEmail) ctx1.Request().RemoteAddr = "192.168.1.100:12345" err := cons.AgreeToEula(ctx1) require.NoError(t, err) assert.Equal(t, http.StatusCreated, rec1.Code) // Second call (same user) ctx2, rec2 := setupEulaTestContextWithUser(http.MethodPost, "/eula/agree", nil, userSubject, userEmail) ctx2.Request().RemoteAddr = "192.168.1.100:12345" err = cons.AgreeToEula(ctx2) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec2.Code) }) t.Run("returns 401 for unauthenticated user", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodPost, "/eula/agree", nil) err := cons.AgreeToEula(ctx) require.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) }) } func TestListEulaVersions(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) // Create multiple versions for i := 0; i < 5; i++ { createTestEulaVersion(t, svc.Eula, "list-v"+uuid.New().String()[:8], "List EULA", false) } t.Run("lists all versions with pagination", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula?page=1&page_size=3", nil) page := int32(1) pageSize := int32(3) params := queryapi.ListEulaVersionsParams{ Page: &page, PageSize: &pageSize, } err := cons.ListEulaVersions(ctx, params) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaVersionList err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.LessOrEqual(t, len(response.Versions), 3) assert.GreaterOrEqual(t, response.Total, int32(5)) }) } func TestCreateEulaVersion(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) t.Run("creates new EULA version", func(t *testing.T) { versionStr := "create-test-" + uuid.New().String()[:8] body := queryapi.EulaVersionCreate{ Version: versionStr, Title: "Created EULA", Content: "# Created EULA Content\n\nTest content here.", EffectiveDate: nullable.NewNullableWithValue(time.Now().Add(24 * time.Hour)), } ctx, rec := setupEulaTestContextWithUser(http.MethodPost, "/admin/eula", body, "admin-subject", "admin@test.com") err := cons.CreateEulaVersion(ctx) require.NoError(t, err) assert.Equal(t, http.StatusCreated, rec.Code) var response queryapi.EulaVersion err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, versionStr, response.Version) assert.Equal(t, "Created EULA", response.Title) }) t.Run("returns 409 for duplicate version", func(t *testing.T) { versionStr := "dup-test-" + uuid.New().String()[:8] body := queryapi.EulaVersionCreate{ Version: versionStr, Title: "First EULA", Content: "Content", EffectiveDate: nullable.NewNullableWithValue(time.Now()), } // First creation ctx1, _ := setupEulaTestContextWithUser(http.MethodPost, "/admin/eula", body, "admin-subject", "admin@test.com") err := cons.CreateEulaVersion(ctx1) require.NoError(t, err) // Second creation (same version) ctx2, rec2 := setupEulaTestContextWithUser(http.MethodPost, "/admin/eula", body, "admin-subject", "admin@test.com") err = cons.CreateEulaVersion(ctx2) require.NoError(t, err) assert.Equal(t, http.StatusConflict, rec2.Code) }) } func TestGetEulaVersion(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) t.Run("gets existing version by ID", func(t *testing.T) { versionStr := "get-test-" + uuid.New().String()[:8] versionID := createTestEulaVersion(t, svc.Eula, versionStr, "Get Test EULA", false) ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/"+versionID.String(), nil) err := cons.GetEulaVersion(ctx, *versionID) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaVersion err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, versionStr, response.Version) }) t.Run("returns 404 for non-existent version", func(t *testing.T) { nonExistentID := uuid.New() ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/"+nonExistentID.String(), nil) err := cons.GetEulaVersion(ctx, nonExistentID) require.NoError(t, err) assert.Equal(t, http.StatusNotFound, rec.Code) }) } func TestUpdateEulaVersion(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) t.Run("updates version title", func(t *testing.T) { versionStr := "update-test-" + uuid.New().String()[:8] versionID := createTestEulaVersion(t, svc.Eula, versionStr, "Original Title", false) newTitle := "Updated Title" body := queryapi.EulaVersionUpdate{ Title: &newTitle, } ctx, rec := setupEulaTestContext(http.MethodPatch, "/admin/eula/"+versionID.String(), body) err := cons.UpdateEulaVersion(ctx, *versionID) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaVersion err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, newTitle, response.Title) }) } func TestActivateEulaVersion(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) t.Run("activates version", func(t *testing.T) { versionStr := "activate-test-" + uuid.New().String()[:8] versionID := createTestEulaVersion(t, svc.Eula, versionStr, "Activate Test EULA", false) ctx, rec := setupEulaTestContextWithUser(http.MethodPost, "/admin/eula/"+versionID.String()+"/activate", nil, "admin-subject", "admin@test.com") err := cons.ActivateEulaVersion(ctx, *versionID) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaVersion err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.IsCurrent) }) t.Run("returns 404 for non-existent version", func(t *testing.T) { nonExistentID := uuid.New() ctx, rec := setupEulaTestContextWithUser(http.MethodPost, "/admin/eula/"+nonExistentID.String()+"/activate", nil, "admin-subject", "admin@test.com") err := cons.ActivateEulaVersion(ctx, nonExistentID) require.NoError(t, err) assert.Equal(t, http.StatusNotFound, rec.Code) }) } func TestListEulaAgreements(t *testing.T) { cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) // Create version and some agreements versionID := createTestEulaVersion(t, svc.Eula, "agreements-test-"+uuid.New().String()[:8], "Agreements Test", true) for i := 0; i < 3; i++ { _, err := svc.Eula.RecordAgreement(t.Context(), &eula.RecordAgreementInput{ CognitoSubjectID: "user-" + uuid.New().String()[:8], UserEmail: "user" + uuid.New().String()[:4] + "@test.com", EulaVersionID: *versionID, IPAddress: "192.168.1.1", }) require.NoError(t, err) } t.Run("lists agreements", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/agreements", nil) page := int32(1) pageSize := int32(10) params := queryapi.ListEulaAgreementsParams{ Page: &page, PageSize: &pageSize, } err := cons.ListEulaAgreements(ctx, params) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaAgreementList err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.GreaterOrEqual(t, len(response.Agreements), 3) }) } func TestGetEulaCompliance(t *testing.T) { // This test requires real Cognito access - opt-in via env var if os.Getenv("ENABLE_COGNITO_INTEGRATION_TESTS") != "true" { t.Skip("Skipping Cognito integration test: set ENABLE_COGNITO_INTEGRATION_TESTS=true to run") } userPoolID := os.Getenv("COGNITO_USER_POOL_ID") awsRegion := os.Getenv("AWS_REGION") if awsRegion == "" { awsRegion = os.Getenv("AUTH_REGION") } if userPoolID == "" || awsRegion == "" { t.Skip("Skipping compliance handler test: COGNITO_USER_POOL_ID and AWS_REGION/AUTH_REGION must be set") } cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) // Create and activate a version for testing versionStr := "compliance-handler-" + uuid.New().String()[:8] createTestEulaVersion(t, svc.Eula, versionStr, "Compliance Handler Test EULA", true) t.Run("returns_compliance_report", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/compliance", nil) params := queryapi.GetEulaComplianceParams{} err := cons.GetEulaCompliance(ctx, params) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaComplianceReport err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) // Verify structure assert.NotEmpty(t, response.EulaVersion.Id) assert.NotEmpty(t, response.EulaVersion.Version) assert.GreaterOrEqual(t, response.Summary.TotalUsers, int32(0)) assert.Equal(t, response.Summary.TotalUsers, response.Summary.AgreedCount+response.Summary.NotAgreedCount) }) t.Run("returns_404_for_nonexistent_version", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/compliance?version_id=00000000-0000-0000-0000-000000000000", nil) nonExistentID := uuid.MustParse("00000000-0000-0000-0000-000000000000") params := queryapi.GetEulaComplianceParams{ VersionId: (*queryapi.EulaVersionID)(&nonExistentID), } err := cons.GetEulaCompliance(ctx, params) require.NoError(t, err) assert.Equal(t, http.StatusNotFound, rec.Code) }) t.Run("filters_by_agreed_status", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/compliance?agreed=false", nil) agreed := false params := queryapi.GetEulaComplianceParams{ Agreed: &agreed, } err := cons.GetEulaCompliance(ctx, params) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaComplianceReport err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) // All users in the result should have agreed=false for _, user := range response.Users { assert.False(t, user.Agreed) } }) t.Run("pagination_params_work", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/compliance?page=1&page_size=5", nil) page := int32(1) pageSize := int32(5) params := queryapi.GetEulaComplianceParams{ Page: &page, PageSize: &pageSize, } err := cons.GetEulaCompliance(ctx, params) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) var response queryapi.EulaComplianceReport err = json.Unmarshal(rec.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, int32(1), response.Pagination.Page) assert.Equal(t, int32(5), response.Pagination.PageSize) assert.LessOrEqual(t, len(response.Users), 5) }) } func TestGetEulaComplianceNoCurrentVersion(t *testing.T) { // This test requires real Cognito access - opt-in via env var if os.Getenv("ENABLE_COGNITO_INTEGRATION_TESTS") != "true" { t.Skip("Skipping Cognito integration test: set ENABLE_COGNITO_INTEGRATION_TESTS=true to run") } userPoolID := os.Getenv("COGNITO_USER_POOL_ID") awsRegion := os.Getenv("AWS_REGION") if awsRegion == "" { awsRegion = os.Getenv("AUTH_REGION") } if userPoolID == "" || awsRegion == "" { t.Skip("Skipping compliance handler test: COGNITO_USER_POOL_ID and AWS_REGION/AUTH_REGION must be set") } // Fresh database with no EULA versions cfg := &ControllerConfig{} test.CreateDB(t, cfg) initializeTestConfig(t, cfg) svc := createControllerServices(cfg) cons := queryapi.NewControllers(svc, cfg) t.Run("returns_404_no_current_eula", func(t *testing.T) { ctx, rec := setupEulaTestContext(http.MethodGet, "/admin/eula/compliance", nil) params := queryapi.GetEulaComplianceParams{} err := cons.GetEulaCompliance(ctx, params) require.NoError(t, err) assert.Equal(t, http.StatusNotFound, rec.Code) }) }