package handler_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/user-management-system/internal/api/handler" "github.com/user-management-system/internal/domain" "github.com/user-management-system/internal/repository" "github.com/user-management-system/internal/service" gormsqlite "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func setupThemeTestEnv(t *testing.T) (*handler.ThemeHandler, *gorm.DB) { t.Helper() gin.SetMode(gin.TestMode) db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: "file:theme_test?mode=memory&cache=shared", }), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("failed to connect database: %v", err) } if err := db.AutoMigrate(&domain.ThemeConfig{}); err != nil { t.Fatalf("failed to migrate: %v", err) } themeRepo := repository.NewThemeConfigRepository(db) themeSvc := service.NewThemeService(themeRepo) return handler.NewThemeHandler(themeSvc), db } func createThemeForTest(t *testing.T, h *handler.ThemeHandler, body string) { t.Helper() w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/api/v1/themes", bytes.NewReader([]byte(body))) c.Request.Header.Set("Content-Type", "application/json") h.CreateTheme(c) if w.Code != http.StatusCreated { t.Fatalf("create theme failed: %d %s", w.Code, w.Body.String()) } } func TestThemeHandler_CreateTheme(t *testing.T) { h, _ := setupThemeTestEnv(t) t.Run("create success", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) body := `{"name":"test-theme","primary_color":"#1976d2"}` c.Request = httptest.NewRequest("POST", "/api/v1/themes", bytes.NewReader([]byte(body))) c.Request.Header.Set("Content-Type", "application/json") h.CreateTheme(c) if w.Code != http.StatusCreated { t.Fatalf("expected status %d, got %d", http.StatusCreated, w.Code) } var resp map[string]interface{} if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode response failed: %v", err) } if resp["code"].(float64) != 0 { t.Fatalf("expected code=0, got %v", resp["code"]) } }) t.Run("create missing name", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) body := `{"primary_color":"#1976d2"}` c.Request = httptest.NewRequest("POST", "/api/v1/themes", bytes.NewReader([]byte(body))) c.Request.Header.Set("Content-Type", "application/json") h.CreateTheme(c) if w.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, w.Code) } }) } func TestThemeHandler_ListThemes(t *testing.T) { h, _ := setupThemeTestEnv(t) createThemeForTest(t, h, `{"name":"list-theme","primary_color":"#1976d2"}`) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("GET", "/api/v1/themes", nil) h.ListThemes(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, w.Code) } } func TestThemeHandler_GetTheme(t *testing.T) { h, _ := setupThemeTestEnv(t) t.Run("get invalid id", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "invalid"}} c.Request = httptest.NewRequest("GET", "/api/v1/themes/invalid", nil) h.GetTheme(c) if w.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, w.Code) } }) t.Run("get success", func(t *testing.T) { createThemeForTest(t, h, `{"name":"get-theme","primary_color":"#1976d2"}`) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "1"}} c.Request = httptest.NewRequest("GET", "/api/v1/themes/1", nil) h.GetTheme(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d, body=%s", http.StatusOK, w.Code, w.Body.String()) } }) } func TestThemeHandler_UpdateTheme(t *testing.T) { h, _ := setupThemeTestEnv(t) createThemeForTest(t, h, `{"name":"theme-update","primary_color":"#111111"}`) t.Run("update success", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "1"}} body := `{"primary_color":"#222222","enabled":true}` c.Request = httptest.NewRequest("PUT", "/api/v1/themes/1", bytes.NewReader([]byte(body))) c.Request.Header.Set("Content-Type", "application/json") h.UpdateTheme(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d, body=%s", http.StatusOK, w.Code, w.Body.String()) } }) t.Run("update invalid id", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "invalid"}} c.Request = httptest.NewRequest("PUT", "/api/v1/themes/invalid", bytes.NewReader([]byte(`{}`))) c.Request.Header.Set("Content-Type", "application/json") h.UpdateTheme(c) if w.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, w.Code) } }) t.Run("update invalid json", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "1"}} c.Request = httptest.NewRequest("PUT", "/api/v1/themes/1", bytes.NewReader([]byte(`{"primary_color":`))) c.Request.Header.Set("Content-Type", "application/json") h.UpdateTheme(c) if w.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, w.Code) } }) } func TestThemeHandler_DeleteTheme(t *testing.T) { h, _ := setupThemeTestEnv(t) t.Run("delete invalid id", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "invalid"}} c.Request = httptest.NewRequest("DELETE", "/api/v1/themes/invalid", nil) h.DeleteTheme(c) if w.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, w.Code) } }) t.Run("delete success", func(t *testing.T) { createThemeForTest(t, h, `{"name":"theme-delete","primary_color":"#1976d2"}`) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "1"}} c.Request = httptest.NewRequest("DELETE", "/api/v1/themes/1", nil) h.DeleteTheme(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d, body=%s", http.StatusOK, w.Code, w.Body.String()) } }) } func TestThemeHandler_DefaultAndActiveFlows(t *testing.T) { h, _ := setupThemeTestEnv(t) createThemeForTest(t, h, `{"name":"default-theme","primary_color":"#111111","is_default":true}`) createThemeForTest(t, h, `{"name":"other-theme","primary_color":"#222222"}`) t.Run("list all themes", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("GET", "/api/v1/themes/all", nil) h.ListAllThemes(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, w.Code) } }) t.Run("get default theme", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("GET", "/api/v1/themes/default", nil) h.GetDefaultTheme(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, w.Code) } }) t.Run("set default invalid id", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "bad"}} c.Request = httptest.NewRequest("PUT", "/api/v1/themes/bad/default", nil) h.SetDefaultTheme(c) if w.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, w.Code) } }) t.Run("set default success", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "2"}} c.Request = httptest.NewRequest("PUT", "/api/v1/themes/2/default", nil) h.SetDefaultTheme(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d, body=%s", http.StatusOK, w.Code, w.Body.String()) } }) t.Run("get active theme", func(t *testing.T) { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("GET", "/api/v1/themes/active", nil) h.GetActiveTheme(c) if w.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, w.Code) } }) }