Skip to content

Commit fe54ef0

Browse files
authored
Testsuit (#93)
* add more tests * test: Expand backend test coverage from ~270 to 838 test functions Add comprehensive tests across handlers (contacts, users, templates, chatbot, custom actions, canned responses, API keys, analytics, SLA, webhooks management, chatbot processor) and packages (whatsapp API, websocket, database). Covers CRUD operations, cross-org isolation, validation errors, business logic, and edge cases. * fix: Resolve CI test failures and lint error - Add Redis to chatbot processor test app to fix nil pointer in getKeywordRulesCached - Filter pub/sub messages by campaign ID to fix race condition in queue stats subscriber test - Remove unused withWSHub helper to fix golangci-lint unused error * fix: Resolve chatbot processor test CI failures - Add mock WhatsApp client to processor test app to fix nil pointer in startFlow/sendAndSaveTextMessage - Work around GORM default:true on bool fields: use explicit UPDATE to set is_enabled=false since GORM skips zero-value bools on INSERT * fix: Save flows to DB before creating sessions with FK references TestCompleteFlow_UpdatesSession and TestExitFlow_UpdatesSession created sessions with CurrentFlowID pointing to flows that weren't saved to the database, causing silent FK constraint failures. * refactor: Include Redis in newTestApp, remove newTestAppWithRedis Consolidate into a single test app builder that always sets up Redis, since cache functions require it. All handler tests now use newTestApp. * fix: Remove unused withRedis helper and redis import * fix: Match AssertErrorResponse to fastglue envelope format, add missing models to migrations - APIEnvelope now matches fastglue's actual format: message is a top-level field, not nested under an error object - Add Catalog, CatalogProduct, and CannedResponse models to test DB migrations and cleanup tables * fix: Resolve CI test failures across multiple packages - queue: Remove t.Parallel() from stream tests that share a single Redis stream, preventing cross-test interference - custom_actions: Fix GORM default:true bool issue in createTestCustomAction by explicitly updating is_active to false after INSERT - chatbot: Fix UpdateChatbotSettings to explicitly set default:true bool columns to false after INSERT when requested - contacts: Preserve original limit in GetMessages pagination response instead of returning the adjusted query limit - templates: Add sample values to test template so SubmitTemplate can build a valid Meta API payload * fix: Resolve data race in async message send and queue test parallelism - messages.go: Use Model(&Message{}).Where("id=?") instead of Model(msg) in finalizeMessageSend to avoid mutating the shared msg struct from the async goroutine while the caller reads it concurrently - queue_test.go: Remove t.Parallel() from stream tests that share a single Redis stream, preventing cross-test interference
1 parent 59dea8a commit fe54ef0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+21483
-1320
lines changed

internal/database/database_test.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package database_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/uuid"
7+
"github.com/shridarpatil/whatomate/internal/config"
8+
"github.com/shridarpatil/whatomate/internal/database"
9+
"github.com/shridarpatil/whatomate/internal/models"
10+
"github.com/shridarpatil/whatomate/test/testutil"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"gorm.io/gorm"
14+
)
15+
16+
// cleanAll truncates every table so each test starts with a blank slate.
17+
func cleanAll(t *testing.T, db *gorm.DB) {
18+
t.Helper()
19+
testutil.TruncateTables(db)
20+
}
21+
22+
// --- SeedPermissionsAndRoles ---
23+
24+
func TestSeedPermissionsAndRoles_CreatesAllDefaultPermissions(t *testing.T) {
25+
db := testutil.SetupTestDB(t)
26+
cleanAll(t, db)
27+
28+
err := database.SeedPermissionsAndRoles(db)
29+
require.NoError(t, err)
30+
31+
var count int64
32+
db.Model(&models.Permission{}).Count(&count)
33+
34+
expected := len(models.DefaultPermissions())
35+
assert.Equal(t, int64(expected), count, "all default permissions should be created")
36+
}
37+
38+
func TestSeedPermissionsAndRoles_Idempotent(t *testing.T) {
39+
db := testutil.SetupTestDB(t)
40+
cleanAll(t, db)
41+
42+
// Seed twice
43+
require.NoError(t, database.SeedPermissionsAndRoles(db))
44+
require.NoError(t, database.SeedPermissionsAndRoles(db))
45+
46+
var count int64
47+
db.Model(&models.Permission{}).Count(&count)
48+
49+
expected := len(models.DefaultPermissions())
50+
assert.Equal(t, int64(expected), count, "idempotent: count should remain the same after two seeds")
51+
}
52+
53+
func TestSeedPermissionsAndRoles_PermissionsHaveResourceAndAction(t *testing.T) {
54+
db := testutil.SetupTestDB(t)
55+
cleanAll(t, db)
56+
57+
require.NoError(t, database.SeedPermissionsAndRoles(db))
58+
59+
var perms []models.Permission
60+
db.Find(&perms)
61+
62+
for _, p := range perms {
63+
assert.NotEmpty(t, p.Resource, "permission resource must not be empty")
64+
assert.NotEmpty(t, p.Action, "permission action must not be empty")
65+
assert.NotEqual(t, uuid.Nil, p.ID, "permission ID must be set")
66+
}
67+
}
68+
69+
// --- SeedSystemRolesForOrg ---
70+
71+
func TestSeedSystemRolesForOrg_CreatesThreeSystemRoles(t *testing.T) {
72+
db := testutil.SetupTestDB(t)
73+
cleanAll(t, db)
74+
75+
// Need permissions first
76+
require.NoError(t, database.SeedPermissionsAndRoles(db))
77+
78+
org := models.Organization{
79+
BaseModel: models.BaseModel{ID: uuid.New()},
80+
Name: "Test Org",
81+
Settings: models.JSONB{},
82+
}
83+
require.NoError(t, db.Create(&org).Error)
84+
85+
err := database.SeedSystemRolesForOrg(db, org.ID)
86+
require.NoError(t, err)
87+
88+
var roles []models.CustomRole
89+
db.Where("organization_id = ? AND is_system = ?", org.ID, true).Find(&roles)
90+
assert.Len(t, roles, 3, "should create admin, manager, agent roles")
91+
92+
names := make(map[string]bool)
93+
for _, r := range roles {
94+
names[r.Name] = true
95+
}
96+
assert.True(t, names["admin"], "admin role should exist")
97+
assert.True(t, names["manager"], "manager role should exist")
98+
assert.True(t, names["agent"], "agent role should exist")
99+
}
100+
101+
func TestSeedSystemRolesForOrg_Idempotent(t *testing.T) {
102+
db := testutil.SetupTestDB(t)
103+
cleanAll(t, db)
104+
105+
require.NoError(t, database.SeedPermissionsAndRoles(db))
106+
107+
org := models.Organization{
108+
BaseModel: models.BaseModel{ID: uuid.New()},
109+
Name: "Idempotent Org",
110+
Settings: models.JSONB{},
111+
}
112+
require.NoError(t, db.Create(&org).Error)
113+
114+
require.NoError(t, database.SeedSystemRolesForOrg(db, org.ID))
115+
require.NoError(t, database.SeedSystemRolesForOrg(db, org.ID))
116+
117+
var count int64
118+
db.Model(&models.CustomRole{}).Where("organization_id = ? AND is_system = ?", org.ID, true).Count(&count)
119+
assert.Equal(t, int64(3), count, "idempotent: still exactly 3 system roles")
120+
}
121+
122+
func TestSeedSystemRolesForOrg_AgentIsDefault(t *testing.T) {
123+
db := testutil.SetupTestDB(t)
124+
cleanAll(t, db)
125+
126+
require.NoError(t, database.SeedPermissionsAndRoles(db))
127+
128+
org := models.Organization{
129+
BaseModel: models.BaseModel{ID: uuid.New()},
130+
Name: "Default Role Org",
131+
Settings: models.JSONB{},
132+
}
133+
require.NoError(t, db.Create(&org).Error)
134+
require.NoError(t, database.SeedSystemRolesForOrg(db, org.ID))
135+
136+
var agentRole models.CustomRole
137+
err := db.Where("organization_id = ? AND name = ? AND is_system = ?", org.ID, "agent", true).First(&agentRole).Error
138+
require.NoError(t, err)
139+
assert.True(t, agentRole.IsDefault, "agent role should be the default role")
140+
}
141+
142+
func TestSeedSystemRolesForOrg_AdminRoleHasAllPermissions(t *testing.T) {
143+
db := testutil.SetupTestDB(t)
144+
cleanAll(t, db)
145+
146+
require.NoError(t, database.SeedPermissionsAndRoles(db))
147+
148+
org := models.Organization{
149+
BaseModel: models.BaseModel{ID: uuid.New()},
150+
Name: "Admin Perms Org",
151+
Settings: models.JSONB{},
152+
}
153+
require.NoError(t, db.Create(&org).Error)
154+
require.NoError(t, database.SeedSystemRolesForOrg(db, org.ID))
155+
156+
var adminRole models.CustomRole
157+
err := db.Where("organization_id = ? AND name = ? AND is_system = ?", org.ID, "admin", true).First(&adminRole).Error
158+
require.NoError(t, err)
159+
160+
// Load permissions through the association
161+
var perms []models.Permission
162+
err = db.Model(&adminRole).Association("Permissions").Find(&perms)
163+
require.NoError(t, err)
164+
165+
totalPerms := len(models.DefaultPermissions())
166+
assert.Equal(t, totalPerms, len(perms), "admin role should have all permissions")
167+
}
168+
169+
// --- CreateDefaultAdmin ---
170+
171+
func TestCreateDefaultAdmin_CreatesOrgAndUser(t *testing.T) {
172+
db := testutil.SetupTestDB(t)
173+
cleanAll(t, db)
174+
175+
cfg := &config.DefaultAdminConfig{
176+
Email: "test-admin@example.com",
177+
Password: "testpassword123",
178+
FullName: "Test Admin",
179+
}
180+
181+
err := database.CreateDefaultAdmin(db, cfg)
182+
require.NoError(t, err)
183+
184+
// Verify user was created
185+
var user models.User
186+
err = db.Where("email = ?", cfg.Email).First(&user).Error
187+
require.NoError(t, err)
188+
assert.Equal(t, cfg.FullName, user.FullName)
189+
assert.True(t, user.IsActive)
190+
assert.True(t, user.IsSuperAdmin)
191+
assert.NotEmpty(t, user.PasswordHash)
192+
193+
// Verify an organization was created
194+
var org models.Organization
195+
err = db.First(&org).Error
196+
require.NoError(t, err)
197+
assert.Equal(t, "Default Organization", org.Name)
198+
199+
// Verify the user belongs to the organization
200+
assert.Equal(t, org.ID, user.OrganizationID)
201+
}
202+
203+
func TestCreateDefaultAdmin_Idempotent(t *testing.T) {
204+
db := testutil.SetupTestDB(t)
205+
cleanAll(t, db)
206+
207+
cfg := &config.DefaultAdminConfig{
208+
Email: "idempotent-admin@example.com",
209+
Password: "pass123",
210+
FullName: "Idempotent Admin",
211+
}
212+
213+
require.NoError(t, database.CreateDefaultAdmin(db, cfg))
214+
require.NoError(t, database.CreateDefaultAdmin(db, cfg))
215+
216+
var count int64
217+
db.Model(&models.User{}).Where("email = ?", cfg.Email).Count(&count)
218+
assert.Equal(t, int64(1), count, "should not create duplicate admin")
219+
}
220+
221+
func TestCreateDefaultAdmin_UsesExistingOrg(t *testing.T) {
222+
db := testutil.SetupTestDB(t)
223+
cleanAll(t, db)
224+
225+
// Pre-create an organization
226+
existingOrg := models.Organization{
227+
BaseModel: models.BaseModel{ID: uuid.New()},
228+
Name: "Pre-existing Org",
229+
Settings: models.JSONB{},
230+
}
231+
require.NoError(t, db.Create(&existingOrg).Error)
232+
233+
cfg := &config.DefaultAdminConfig{
234+
Email: "admin-existing-org@example.com",
235+
Password: "password",
236+
FullName: "Admin",
237+
}
238+
239+
err := database.CreateDefaultAdmin(db, cfg)
240+
require.NoError(t, err)
241+
242+
var user models.User
243+
err = db.Where("email = ?", cfg.Email).First(&user).Error
244+
require.NoError(t, err)
245+
assert.Equal(t, existingOrg.ID, user.OrganizationID, "admin should belong to existing org")
246+
247+
// Should not have created a new org
248+
var orgCount int64
249+
db.Model(&models.Organization{}).Count(&orgCount)
250+
assert.Equal(t, int64(1), orgCount, "should reuse existing organization")
251+
}

0 commit comments

Comments
 (0)