package service import ( "context" "encoding/json" "testing" "github.com/gochat/gochat/internal/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // ========== Create ========== func TestDashboardAppService_Create_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) req := &CreateDashboardAppRequest{ Title: "销售仪表盘", Description: "销售数据看板", Kind: "frame", Content: json.RawMessage(`[{"type":"frame","url":"https://example.com/widget"}]`), } app, err := svc.Create(context.Background(), account.ID, nil, req) require.NoError(t, err) assert.NotZero(t, app.ID) assert.Equal(t, account.ID, app.AccountID) assert.Nil(t, app.UserID) assert.Equal(t, "销售仪表盘", app.Title) assert.Equal(t, "销售数据看板", app.Description) assert.Equal(t, "frame", app.Kind) assert.True(t, *app.Active) assert.Equal(t, json.RawMessage(`[{"type":"frame","url":"https://example.com/widget"}]`), app.Content) } func TestDashboardAppService_Create_带UserID(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) user := createTestUser(t, db, account.ID) userID := user.ID req := &CreateDashboardAppRequest{ Title: "个人仪表盘", Content: json.RawMessage(`[{"type":"frame","url":"https://example.com/personal"}]`), } app, err := svc.Create(context.Background(), account.ID, &userID, req) require.NoError(t, err) assert.NotZero(t, app.ID) assert.Equal(t, account.ID, app.AccountID) assert.NotNil(t, app.UserID) assert.Equal(t, user.ID, *app.UserID) } func TestDashboardAppService_Create_空Content(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) req := &CreateDashboardAppRequest{ Title: "空内容仪表盘", Content: json.RawMessage("[]"), } app, err := svc.Create(context.Background(), account.ID, nil, req) require.NoError(t, err) assert.NotZero(t, app.ID) assert.Equal(t, json.RawMessage("[]"), app.Content) } func TestDashboardAppService_Create_无效Content非数组(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) req := &CreateDashboardAppRequest{ Title: "无效内容", Content: json.RawMessage(`{"type":"frame"}`), } app, err := svc.Create(context.Background(), account.ID, nil, req) assert.Error(t, err) assert.Nil(t, app) assert.Contains(t, err.Error(), "JSON array") } func TestDashboardAppService_Create_无效WidgetURL(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) req := &CreateDashboardAppRequest{ Title: "无效URL", Content: json.RawMessage(`[{"type":"frame","url":"ftp://bad.url"}]`), } app, err := svc.Create(context.Background(), account.ID, nil, req) assert.Error(t, err) assert.Nil(t, app) } // ========== GetByID ========== func TestDashboardAppService_GetByID_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) app, err := svc.GetByID(context.Background(), created.ID) require.NoError(t, err) assert.Equal(t, created.ID, app.ID) assert.Equal(t, created.AccountID, app.AccountID) assert.Equal(t, created.Title, app.Title) } func TestDashboardAppService_GetByID_不存在(t *testing.T) { _, _, svc := setupDashboardAppService(t) app, err := svc.GetByID(context.Background(), 99999) assert.Error(t, err) assert.Nil(t, app) } // ========== Update ========== func TestDashboardAppService_Update_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) req := &UpdateDashboardAppRequest{ Title: "更新标题", Content: json.RawMessage(`[{"type":"frame","url":"https://example.com/updated"}]`), } app, err := svc.Update(context.Background(), created.ID, req) require.NoError(t, err) assert.Equal(t, "更新标题", app.Title) assert.Equal(t, json.RawMessage(`[{"type":"frame","url":"https://example.com/updated"}]`), app.Content) } func TestDashboardAppService_Update_不存在(t *testing.T) { _, _, svc := setupDashboardAppService(t) req := &UpdateDashboardAppRequest{Title: "不存在"} app, err := svc.Update(context.Background(), 99999, req) assert.Error(t, err) assert.Nil(t, app) } func TestDashboardAppService_Update_部分字段(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) originalContent := created.Content req := &UpdateDashboardAppRequest{ Title: "只改标题", } app, err := svc.Update(context.Background(), created.ID, req) require.NoError(t, err) assert.Equal(t, "只改标题", app.Title) assert.Equal(t, originalContent, app.Content) } func TestDashboardAppService_Update_无效Content(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) req := &UpdateDashboardAppRequest{ Content: json.RawMessage(`{"not":"array"}`), } app, err := svc.Update(context.Background(), created.ID, req) assert.Error(t, err) assert.Nil(t, app) } // ========== Delete ========== func TestDashboardAppService_Delete_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) err := svc.Delete(context.Background(), created.ID) require.NoError(t, err) // Verify soft deleted app, err := svc.GetByID(context.Background(), created.ID) assert.Error(t, err) assert.Nil(t, app) } func TestDashboardAppService_Delete_不存在(t *testing.T) { _, _, svc := setupDashboardAppService(t) err := svc.Delete(context.Background(), 99999) assert.Error(t, err) } // ========== ListByAccount ========== func TestDashboardAppService_ListByAccount_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) for i := 0; i < 3; i++ { createTestDashboardApp(t, db, account.ID, func(app *model.DashboardApp) { app.Title = "仪表盘-" + string(rune('A'+i)) }) } apps, err := svc.ListByAccount(context.Background(), account.ID) require.NoError(t, err) assert.Len(t, apps, 3) } func TestDashboardAppService_ListByAccount_空列表(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) apps, err := svc.ListByAccount(context.Background(), account.ID) require.NoError(t, err) assert.Len(t, apps, 0) } func TestDashboardAppService_ListByAccount_不同账户隔离(t *testing.T) { db, _, svc := setupDashboardAppService(t) account1 := createTestAccount(t, db) account2 := createTestAccount(t, db) createTestDashboardApp(t, db, account1.ID) createTestDashboardApp(t, db, account1.ID) createTestDashboardApp(t, db, account2.ID) apps, err := svc.ListByAccount(context.Background(), account1.ID) require.NoError(t, err) assert.Len(t, apps, 2) apps, err = svc.ListByAccount(context.Background(), account2.ID) require.NoError(t, err) assert.Len(t, apps, 1) } // ========== ListActiveByAccount ========== func TestDashboardAppService_ListActiveByAccount_只返回Active(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) createTestDashboardApp(t, db, account.ID) createTestDashboardApp(t, db, account.ID) createTestDashboardApp(t, db, account.ID, func(app *model.DashboardApp) { app.Active = model.BoolPtr(false) }) apps, err := svc.ListActiveByAccount(context.Background(), account.ID) require.NoError(t, err) assert.Len(t, apps, 2) for _, a := range apps { assert.True(t, *a.Active) } } // ========== Search ========== func TestDashboardAppService_Search_按标题搜索(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) createTestDashboardApp(t, db, account.ID, func(app *model.DashboardApp) { app.Title = "销售仪表盘" }) createTestDashboardApp(t, db, account.ID, func(app *model.DashboardApp) { app.Title = "客服监控面板" }) createTestDashboardApp(t, db, account.ID, func(app *model.DashboardApp) { app.Title = "运营数据看板" }) apps, err := svc.Search(context.Background(), account.ID, "仪表盘") require.NoError(t, err) assert.Len(t, apps, 1) assert.Equal(t, "销售仪表盘", apps[0].Title) } func TestDashboardAppService_Search_空结果(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) createTestDashboardApp(t, db, account.ID, func(app *model.DashboardApp) { app.Title = "客服监控" }) apps, err := svc.Search(context.Background(), account.ID, "不存在") require.NoError(t, err) assert.Len(t, apps, 0) } func TestDashboardAppService_Search_账户隔离(t *testing.T) { db, _, svc := setupDashboardAppService(t) account1 := createTestAccount(t, db) account2 := createTestAccount(t, db) createTestDashboardApp(t, db, account1.ID, func(app *model.DashboardApp) { app.Title = "仪表盘A" }) createTestDashboardApp(t, db, account2.ID, func(app *model.DashboardApp) { app.Title = "仪表盘B" }) apps, err := svc.Search(context.Background(), account1.ID, "仪表盘") require.NoError(t, err) assert.Len(t, apps, 1) assert.Equal(t, "仪表盘A", apps[0].Title) } // ========== Widget Management ========== func TestDashboardAppService_AddWidget_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) req := &AddWidgetRequest{ Type: "frame", URL: "https://example.com/new-widget", Name: "新Widget", } app, err := svc.AddWidget(context.Background(), created.ID, req) require.NoError(t, err) var widgets []model.DashboardWidget err = json.Unmarshal(app.Content, &widgets) require.NoError(t, err) assert.Len(t, widgets, 2) lastWidget := widgets[len(widgets)-1] assert.Equal(t, "frame", lastWidget.Type) assert.Equal(t, "https://example.com/new-widget", lastWidget.URL) assert.Equal(t, "新Widget", lastWidget.Name) } func TestDashboardAppService_AddWidget_到空Content(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID, func(app *model.DashboardApp) { app.Content = json.RawMessage(`[]`) }) req := &AddWidgetRequest{ Type: "frame", URL: "https://example.com/widget", } app, err := svc.AddWidget(context.Background(), created.ID, req) require.NoError(t, err) var widgets []model.DashboardWidget err = json.Unmarshal(app.Content, &widgets) require.NoError(t, err) assert.Len(t, widgets, 1) assert.Equal(t, "https://example.com/widget", widgets[0].URL) } func TestDashboardAppService_AddWidget_无效Type(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) req := &AddWidgetRequest{ Type: "link", URL: "https://example.com/widget", } app, err := svc.AddWidget(context.Background(), created.ID, req) assert.Error(t, err) assert.Nil(t, app) assert.Contains(t, err.Error(), "must be 'frame'") } func TestDashboardAppService_AddWidget_无效URL(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) req := &AddWidgetRequest{ Type: "frame", URL: "ftp://invalid.url", } app, err := svc.AddWidget(context.Background(), created.ID, req) assert.Error(t, err) assert.Nil(t, app) } func TestDashboardAppService_AddWidget_不存在App(t *testing.T) { _, _, svc := setupDashboardAppService(t) req := &AddWidgetRequest{ Type: "frame", URL: "https://example.com/widget", } app, err := svc.AddWidget(context.Background(), 99999, req) assert.Error(t, err) assert.Nil(t, app) } func TestDashboardAppService_RemoveWidget_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) // Remove first widget (index 0) app, err := svc.RemoveWidget(context.Background(), created.ID, 0) require.NoError(t, err) var widgets []model.DashboardWidget err = json.Unmarshal(app.Content, &widgets) require.NoError(t, err) assert.Len(t, widgets, 0) } func TestDashboardAppService_RemoveWidget_越界索引(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) app, err := svc.RemoveWidget(context.Background(), created.ID, 99) assert.Error(t, err) assert.Nil(t, app) } func TestDashboardAppService_UpdateWidget_成功(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) req := &UpdateWidgetRequest{ Type: "frame", URL: "https://example.com/updated-widget", Name: "更新Widget", } app, err := svc.UpdateWidget(context.Background(), created.ID, 0, req) require.NoError(t, err) var widgets []model.DashboardWidget err = json.Unmarshal(app.Content, &widgets) require.NoError(t, err) assert.Len(t, widgets, 1) assert.Equal(t, "https://example.com/updated-widget", widgets[0].URL) assert.Equal(t, "更新Widget", widgets[0].Name) } func TestDashboardAppService_UpdateWidget_越界索引(t *testing.T) { db, _, svc := setupDashboardAppService(t) account := createTestAccount(t, db) created := createTestDashboardApp(t, db, account.ID) req := &UpdateWidgetRequest{ Type: "frame", URL: "https://example.com/widget", } app, err := svc.UpdateWidget(context.Background(), created.ID, 99, req) assert.Error(t, err) assert.Nil(t, app) } func TestDashboardAppService_UpdateWidget_不存在App(t *testing.T) { _, _, svc := setupDashboardAppService(t) req := &UpdateWidgetRequest{ Type: "frame", URL: "https://example.com/widget", } app, err := svc.UpdateWidget(context.Background(), 99999, 0, req) assert.Error(t, err) assert.Nil(t, app) }