Files
gochat/backend/internal/service/dashboard_app_service_test.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

486 lines
14 KiB
Go

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)
assert.Error(t, err)
assert.Nil(t, app)
assert.Contains(t, err.Error(), "at least one widget")
}
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)
}