package handlers import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/Wikid82/charon/backend/internal/models" "github.com/Wikid82/charon/backend/internal/services" ) func setupDomainTestRouter(t *testing.T) (*gin.Engine, *gorm.DB) { t.Helper() dsn := "file:" + t.Name() + "?mode=memory&cache=shared" db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{}) require.NoError(t, err) require.NoError(t, db.AutoMigrate(&models.Domain{}, &models.Notification{}, &models.NotificationProvider{})) ns := services.NewNotificationService(db) h := NewDomainHandler(db, ns) r := gin.New() // Manually register routes since DomainHandler doesn't have a RegisterRoutes method yet // or we can just register them here for testing r.GET("/api/v1/domains", h.List) r.POST("/api/v1/domains", h.Create) r.DELETE("/api/v1/domains/:id", h.Delete) return r, db } func TestDomainLifecycle(t *testing.T) { router, _ := setupDomainTestRouter(t) // 1. Create Domain body := `{"name":"example.com"}` req := httptest.NewRequest(http.MethodPost, "/api/v1/domains", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) var created models.Domain require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &created)) require.Equal(t, "example.com", created.Name) require.NotEmpty(t, created.UUID) // 2. List Domains req = httptest.NewRequest(http.MethodGet, "/api/v1/domains", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) var list []models.Domain require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &list)) require.Len(t, list, 1) require.Equal(t, "example.com", list[0].Name) // 3. Delete Domain req = httptest.NewRequest(http.MethodDelete, "/api/v1/domains/"+created.UUID, http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) // 4. Verify Deletion req = httptest.NewRequest(http.MethodGet, "/api/v1/domains", http.NoBody) resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &list)) require.Len(t, list, 0) } func TestDomainErrors(t *testing.T) { router, _ := setupDomainTestRouter(t) // 1. Create Invalid JSON req := httptest.NewRequest(http.MethodPost, "/api/v1/domains", strings.NewReader(`{invalid}`)) req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusBadRequest, resp.Code) // 2. Create Missing Name req = httptest.NewRequest(http.MethodPost, "/api/v1/domains", strings.NewReader(`{}`)) req.Header.Set("Content-Type", "application/json") resp = httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusBadRequest, resp.Code) } func TestDomainDelete_NotFound(t *testing.T) { router, _ := setupDomainTestRouter(t) req := httptest.NewRequest(http.MethodDelete, "/api/v1/domains/nonexistent-uuid", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) // Handler may return 200 with deleted=true even if not found (soft delete behavior) require.True(t, resp.Code == http.StatusOK || resp.Code == http.StatusNotFound) } func TestDomainCreate_Duplicate(t *testing.T) { router, db := setupDomainTestRouter(t) // Create first domain body := `{"name":"duplicate.com"}` req := httptest.NewRequest(http.MethodPost, "/api/v1/domains", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusCreated, resp.Code) // Try creating duplicate req = httptest.NewRequest(http.MethodPost, "/api/v1/domains", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp = httptest.NewRecorder() router.ServeHTTP(resp, req) // Should error - could be 409 Conflict or 500 depending on implementation require.True(t, resp.Code >= 400, "Expected error status for duplicate domain") // Verify only one exists var count int64 db.Model(&models.Domain{}).Where("name = ?", "duplicate.com").Count(&count) require.Equal(t, int64(1), count) } func TestDomainList_Empty(t *testing.T) { router, _ := setupDomainTestRouter(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/domains", http.NoBody) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) require.Equal(t, http.StatusOK, resp.Code) var list []models.Domain require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &list)) require.Empty(t, list) } func TestDomainCreate_LongName(t *testing.T) { router, _ := setupDomainTestRouter(t) longName := strings.Repeat("a", 300) + ".com" body := `{"name":"` + longName + `"}` req := httptest.NewRequest(http.MethodPost, "/api/v1/domains", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) // Should succeed (database will truncate or accept) require.True(t, resp.Code == http.StatusCreated || resp.Code >= 400) }