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.
273 lines
8.1 KiB
Go
273 lines
8.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// helper: build a minimal valid DashboardApp
|
|
func newTestDashboardApp(accountID uint, title string) *model.DashboardApp {
|
|
return &model.DashboardApp{
|
|
AccountID: accountID,
|
|
Title: title,
|
|
Kind: "frame",
|
|
Active: model.BoolPtr(true),
|
|
Content: json.RawMessage(`[{"type":"frame","url":"https://example.com"}]`),
|
|
}
|
|
}
|
|
|
|
// ========== Create ==========
|
|
|
|
func TestDashboardAppRepo_Create(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
app := newTestDashboardApp(1, "MyDashboard")
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, app.ID, "ID should be set after Create")
|
|
assert.Equal(t, uint(1), app.AccountID)
|
|
assert.Equal(t, "MyDashboard", app.Title)
|
|
assert.Equal(t, "frame", app.Kind)
|
|
assert.True(t, *app.Active)
|
|
}
|
|
|
|
// ========== GetByID ==========
|
|
|
|
func TestDashboardAppRepo_GetByID(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
app := newTestDashboardApp(1, "FindByIDApp")
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
|
|
found, err := repo.GetByID(context.Background(), app.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, app.ID, found.ID)
|
|
assert.Equal(t, app.Title, found.Title)
|
|
assert.Equal(t, app.AccountID, found.AccountID)
|
|
assert.Equal(t, "frame", found.Kind)
|
|
assert.True(t, *found.Active)
|
|
}
|
|
|
|
func TestDashboardAppRepo_GetByID_NotFound(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
found, err := repo.GetByID(context.Background(), 9999)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, found)
|
|
}
|
|
|
|
// ========== Update ==========
|
|
|
|
func TestDashboardAppRepo_Update(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
app := newTestDashboardApp(1, "BeforeUpdate")
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
|
|
app.Title = "AfterUpdate"
|
|
app.Description = "新描述"
|
|
app.Icon = "chart-icon"
|
|
app.Active = model.BoolPtr(false)
|
|
app.Content = json.RawMessage(`[{"type":"frame","url":"https://updated.com/widget"}]`)
|
|
err = repo.Update(context.Background(), app)
|
|
require.NoError(t, err)
|
|
|
|
found, err := repo.GetByID(context.Background(), app.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "AfterUpdate", found.Title)
|
|
assert.Equal(t, "新描述", found.Description)
|
|
assert.Equal(t, "chart-icon", found.Icon)
|
|
assert.False(t, *found.Active)
|
|
assert.Equal(t, `[{"type":"frame","url":"https://updated.com/widget"}]`, string(found.Content))
|
|
}
|
|
|
|
// ========== Delete ==========
|
|
|
|
func TestDashboardAppRepo_Delete(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
app := newTestDashboardApp(1, "DeleteApp")
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
|
|
err = repo.Delete(context.Background(), app.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Verify deletion: GetByID should return error
|
|
found, err := repo.GetByID(context.Background(), app.ID)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, found)
|
|
}
|
|
|
|
// ========== FindAllByAccountID ==========
|
|
|
|
func TestDashboardAppRepo_FindAllByAccountID(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
accountID := uint(10)
|
|
// Create 3 apps for this account
|
|
for i := 0; i < 3; i++ {
|
|
app := newTestDashboardApp(accountID, "App"+string(rune('A'+i)))
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
}
|
|
// Create 1 app for a different account (should not appear)
|
|
otherApp := newTestDashboardApp(99, "OtherAccountApp")
|
|
err := repo.Create(context.Background(), otherApp)
|
|
require.NoError(t, err)
|
|
|
|
// Find all without pagination (matches Chatwoot behavior)
|
|
apps, err := repo.FindAllByAccountID(context.Background(), accountID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, apps, 3)
|
|
|
|
// Verify all apps belong to the correct account
|
|
for _, a := range apps {
|
|
assert.Equal(t, accountID, a.AccountID)
|
|
}
|
|
}
|
|
|
|
// ========== FindByUserID ==========
|
|
|
|
func TestDashboardAppRepo_FindByUserID(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
userID := uint(100)
|
|
accountID := uint(10)
|
|
// Create 2 apps with the given userID
|
|
for i := 0; i < 2; i++ {
|
|
app := newTestDashboardApp(accountID, "UserApp"+string(rune('A'+i)))
|
|
app.UserID = &userID
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
}
|
|
// Create 1 app with a different userID (should not appear)
|
|
otherUserID := uint(999)
|
|
otherApp := newTestDashboardApp(accountID, "OtherUserApp")
|
|
otherApp.UserID = &otherUserID
|
|
err := repo.Create(context.Background(), otherApp)
|
|
require.NoError(t, err)
|
|
|
|
apps, count, err := repo.FindByUserID(context.Background(), userID, 0, 10)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), count)
|
|
assert.Len(t, apps, 2)
|
|
|
|
for _, a := range apps {
|
|
assert.NotNil(t, a.UserID)
|
|
assert.Equal(t, userID, *a.UserID)
|
|
}
|
|
}
|
|
|
|
// ========== FindAllActiveByAccountID ==========
|
|
|
|
func TestDashboardAppRepo_FindAllActiveByAccountID(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
accountID := uint(30)
|
|
// Create 2 active apps
|
|
for i := 0; i < 2; i++ {
|
|
app := newTestDashboardApp(accountID, "ActiveApp"+string(rune('A'+i)))
|
|
app.Active = model.BoolPtr(true)
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
}
|
|
// Create 1 inactive app (should not appear)
|
|
inactiveApp := newTestDashboardApp(accountID, "InactiveApp")
|
|
inactiveApp.Active = model.BoolPtr(false)
|
|
err := repo.Create(context.Background(), inactiveApp)
|
|
require.NoError(t, err)
|
|
|
|
apps, err := repo.FindAllActiveByAccountID(context.Background(), accountID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, apps, 2)
|
|
|
|
for _, a := range apps {
|
|
assert.True(t, *a.Active)
|
|
}
|
|
}
|
|
|
|
// ========== Search ==========
|
|
|
|
// Search uses ILIKE on PG / LIKE on SQLite — both DBs should work.
|
|
func TestDashboardAppRepo_Search(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
accountID := uint(40)
|
|
// Create apps with different titles
|
|
titles := []string{"销售仪表盘", "客服监控面板", "运营数据看板", "工单统计"}
|
|
for _, title := range titles {
|
|
app := newTestDashboardApp(accountID, title)
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
}
|
|
// Create app in different account (should not appear)
|
|
otherApp := newTestDashboardApp(99, "仪表盘")
|
|
err := repo.Create(context.Background(), otherApp)
|
|
require.NoError(t, err)
|
|
|
|
// Search for "仪表盘" — should match "销售仪表盘"
|
|
apps, count, err := repo.Search(context.Background(), accountID, "仪表盘", 0, 10)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), count)
|
|
assert.Len(t, apps, 1)
|
|
assert.Equal(t, "销售仪表盘", apps[0].Title)
|
|
}
|
|
|
|
func TestDashboardAppRepo_Search_空结果(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
accountID := uint(41)
|
|
// Create 1 app
|
|
app := newTestDashboardApp(accountID, "客服监控")
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
|
|
apps, count, err := repo.Search(context.Background(), accountID, "不存在", 0, 10)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), count)
|
|
assert.Len(t, apps, 0)
|
|
}
|
|
|
|
func TestDashboardAppRepo_Search_分页(t *testing.T) {
|
|
db := setupTestDB(t, &model.DashboardApp{})
|
|
repo := NewDashboardAppRepo(db)
|
|
|
|
accountID := uint(42)
|
|
// Create 3 apps matching "监控"
|
|
for i := 0; i < 3; i++ {
|
|
app := newTestDashboardApp(accountID, "监控面板"+string(rune('A'+i)))
|
|
err := repo.Create(context.Background(), app)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Page 1: offset 0, limit 2
|
|
apps, count, err := repo.Search(context.Background(), accountID, "监控", 0, 2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), count)
|
|
assert.Len(t, apps, 2)
|
|
|
|
// Page 2: offset 2, limit 2
|
|
apps2, count2, err := repo.Search(context.Background(), accountID, "监控", 2, 2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), count2)
|
|
assert.Len(t, apps2, 1)
|
|
} |