Coverage priorities 1-5: test pure functions, extract interfaces, mock-based handler tests
- Priority 1: Test NewSendEmailTask + NewSendPushTask (5 tests) - Priority 2: Test customHTTPErrorHandler — all 15+ branches (21 tests) - Priority 3: Extract Enqueuer interface + payload builders in worker pkg (5 tests) - Priority 4: Extract ClassifyFile/ComputeRelPath in migrate-encrypt (6 tests) - Priority 5: Define Handler interfaces, refactor to accept them, mock-based tests (14 tests) - Fix .gitignore: /worker instead of worker to stop ignoring internal/worker/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,10 +17,10 @@ func TestIntegration_ContractorSharingFlow(t *testing.T) {
|
||||
|
||||
// ========== Setup Users ==========
|
||||
// Create user A
|
||||
userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "password123")
|
||||
userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "Password123")
|
||||
|
||||
// Create user B
|
||||
userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "password123")
|
||||
userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "Password123")
|
||||
|
||||
// ========== User A creates residence C ==========
|
||||
residenceBody := map[string]interface{}{
|
||||
@@ -180,8 +180,8 @@ func TestIntegration_ContractorAccessWithoutResidenceShare(t *testing.T) {
|
||||
app := setupContractorTest(t)
|
||||
|
||||
// Create two users
|
||||
userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "password123")
|
||||
userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "password123")
|
||||
userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "Password123")
|
||||
userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "Password123")
|
||||
|
||||
// User A creates a residence
|
||||
residenceBody := map[string]interface{}{
|
||||
@@ -228,9 +228,9 @@ func TestIntegration_ContractorUpdateAndDeleteAccess(t *testing.T) {
|
||||
app := setupContractorTest(t)
|
||||
|
||||
// Create users
|
||||
userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "password123")
|
||||
userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "password123")
|
||||
userCToken := app.registerAndLogin(t, "userC", "userC@test.com", "password123")
|
||||
userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "Password123")
|
||||
userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "Password123")
|
||||
userCToken := app.registerAndLogin(t, "userC", "userC@test.com", "Password123")
|
||||
|
||||
// User A creates residence and shares with User B (not User C)
|
||||
residenceBody := map[string]interface{}{"name": "Shared Residence"}
|
||||
|
||||
@@ -379,7 +379,7 @@ func TestIntegration_DuplicateRegistration(t *testing.T) {
|
||||
registerBody := map[string]string{
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "password123",
|
||||
"password": "Password123",
|
||||
}
|
||||
w := app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody, "")
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
@@ -388,7 +388,7 @@ func TestIntegration_DuplicateRegistration(t *testing.T) {
|
||||
registerBody2 := map[string]string{
|
||||
"username": "testuser",
|
||||
"email": "different@example.com",
|
||||
"password": "password123",
|
||||
"password": "Password123",
|
||||
}
|
||||
w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody2, "")
|
||||
assert.Equal(t, http.StatusConflict, w.Code)
|
||||
@@ -397,7 +397,7 @@ func TestIntegration_DuplicateRegistration(t *testing.T) {
|
||||
registerBody3 := map[string]string{
|
||||
"username": "differentuser",
|
||||
"email": "test@example.com",
|
||||
"password": "password123",
|
||||
"password": "Password123",
|
||||
}
|
||||
w = app.makeAuthenticatedRequest(t, "POST", "/api/auth/register", registerBody3, "")
|
||||
assert.Equal(t, http.StatusConflict, w.Code)
|
||||
@@ -407,7 +407,7 @@ func TestIntegration_DuplicateRegistration(t *testing.T) {
|
||||
|
||||
func TestIntegration_ResidenceFlow(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "owner", "owner@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// 1. Create a residence
|
||||
createBody := map[string]interface{}{
|
||||
@@ -475,8 +475,8 @@ func TestIntegration_ResidenceSharingFlow(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
|
||||
// Create owner and another user
|
||||
ownerToken := app.registerAndLogin(t, "owner", "owner@test.com", "password123")
|
||||
userToken := app.registerAndLogin(t, "shareduser", "shared@test.com", "password123")
|
||||
ownerToken := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")
|
||||
userToken := app.registerAndLogin(t, "shareduser", "shared@test.com", "Password123")
|
||||
|
||||
// Create residence as owner
|
||||
createBody := map[string]interface{}{
|
||||
@@ -531,7 +531,7 @@ func TestIntegration_ResidenceSharingFlow(t *testing.T) {
|
||||
|
||||
func TestIntegration_TaskFlow(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "owner", "owner@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create residence first
|
||||
residenceBody := map[string]interface{}{"name": "Task House"}
|
||||
@@ -633,7 +633,7 @@ func TestIntegration_TaskFlow(t *testing.T) {
|
||||
|
||||
func TestIntegration_TasksByResidenceKanban(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "owner", "owner@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Use explicit timezone to test full timezone-aware path
|
||||
testTimezone := "America/Los_Angeles"
|
||||
@@ -682,7 +682,7 @@ func TestIntegration_TasksByResidenceKanban(t *testing.T) {
|
||||
|
||||
func TestIntegration_LookupEndpoints(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "user", "user@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "user", "user@test.com", "Password123")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -721,8 +721,8 @@ func TestIntegration_CrossUserAccessDenied(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
|
||||
// Create two users with their own residences
|
||||
user1Token := app.registerAndLogin(t, "user1", "user1@test.com", "password123")
|
||||
user2Token := app.registerAndLogin(t, "user2", "user2@test.com", "password123")
|
||||
user1Token := app.registerAndLogin(t, "user1", "user1@test.com", "Password123")
|
||||
user2Token := app.registerAndLogin(t, "user2", "user2@test.com", "Password123")
|
||||
|
||||
// User1 creates a residence
|
||||
residenceBody := map[string]interface{}{"name": "User1's House"}
|
||||
@@ -777,7 +777,7 @@ func TestIntegration_CrossUserAccessDenied(t *testing.T) {
|
||||
|
||||
func TestIntegration_ResponseStructure(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "user", "user@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "user", "user@test.com", "Password123")
|
||||
|
||||
// Create residence
|
||||
residenceBody := map[string]interface{}{
|
||||
@@ -1704,7 +1704,7 @@ func setupContractorTest(t *testing.T) *TestApp {
|
||||
// - Verify task moves between kanban columns appropriately
|
||||
func TestIntegration_RecurringTaskLifecycle(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "recurring_user", "recurring@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "recurring_user", "recurring@test.com", "Password123")
|
||||
|
||||
// Create residence
|
||||
residenceBody := map[string]interface{}{"name": "Recurring Task House"}
|
||||
@@ -1904,9 +1904,9 @@ func TestIntegration_MultiUserSharing(t *testing.T) {
|
||||
|
||||
t.Log("Phase 1: Create 3 users")
|
||||
|
||||
tokenA := app.registerAndLogin(t, "user_a", "usera@test.com", "password123")
|
||||
tokenB := app.registerAndLogin(t, "user_b", "userb@test.com", "password123")
|
||||
tokenC := app.registerAndLogin(t, "user_c", "userc@test.com", "password123")
|
||||
tokenA := app.registerAndLogin(t, "user_a", "usera@test.com", "Password123")
|
||||
tokenB := app.registerAndLogin(t, "user_b", "userb@test.com", "Password123")
|
||||
tokenC := app.registerAndLogin(t, "user_c", "userc@test.com", "Password123")
|
||||
|
||||
t.Log("✓ Created users A, B, and C")
|
||||
|
||||
@@ -2098,7 +2098,7 @@ func TestIntegration_MultiUserSharing(t *testing.T) {
|
||||
// - Verify kanban column changes with each transition
|
||||
func TestIntegration_TaskStateTransitions(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "state_user", "state@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "state_user", "state@test.com", "Password123")
|
||||
|
||||
// Create residence
|
||||
residenceBody := map[string]interface{}{"name": "State Transition House"}
|
||||
@@ -2274,7 +2274,7 @@ func TestIntegration_TaskStateTransitions(t *testing.T) {
|
||||
// we're testing the full timezone-aware path, not just UTC defaults.
|
||||
func TestIntegration_DateBoundaryEdgeCases(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "boundary_user", "boundary@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "boundary_user", "boundary@test.com", "Password123")
|
||||
|
||||
// Create residence
|
||||
residenceBody := map[string]interface{}{"name": "Boundary Test House"}
|
||||
@@ -2435,7 +2435,7 @@ func TestIntegration_DateBoundaryEdgeCases(t *testing.T) {
|
||||
// - One where it's already "tomorrow" → task is overdue (due date was "yesterday")
|
||||
func TestIntegration_TimezoneDivergence(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "tz_user", "tz@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "tz_user", "tz@test.com", "Password123")
|
||||
|
||||
// Create residence
|
||||
residenceBody := map[string]interface{}{"name": "Timezone Test House"}
|
||||
@@ -2584,7 +2584,7 @@ func findTaskColumn(kanbanResp map[string]interface{}, taskID uint) string {
|
||||
// - Verify cascading effects
|
||||
func TestIntegration_CascadeOperations(t *testing.T) {
|
||||
app := setupIntegrationTest(t)
|
||||
token := app.registerAndLogin(t, "cascade_user", "cascade@test.com", "password123")
|
||||
token := app.registerAndLogin(t, "cascade_user", "cascade@test.com", "Password123")
|
||||
|
||||
t.Log("Phase 1: Create residence")
|
||||
|
||||
@@ -2721,8 +2721,8 @@ func TestIntegration_MultiUserOperations(t *testing.T) {
|
||||
|
||||
t.Log("Phase 1: Setup users and shared residence")
|
||||
|
||||
tokenA := app.registerAndLogin(t, "multiuser_a", "multiusera@test.com", "password123")
|
||||
tokenB := app.registerAndLogin(t, "multiuser_b", "multiuserb@test.com", "password123")
|
||||
tokenA := app.registerAndLogin(t, "multiuser_a", "multiusera@test.com", "Password123")
|
||||
tokenB := app.registerAndLogin(t, "multiuser_b", "multiuserb@test.com", "Password123")
|
||||
|
||||
// User A creates residence
|
||||
residenceBody := map[string]interface{}{"name": "Multi-User Test House"}
|
||||
|
||||
@@ -265,8 +265,8 @@ func TestE2E_SQLInjection_AdminSort_Blocked(t *testing.T) {
|
||||
adminUserHandler := adminhandlers.NewAdminUserHandler(db)
|
||||
|
||||
// Create a couple of test users to have data to sort
|
||||
testutil.CreateTestUser(t, db, "alice", "alice@test.com", "password123")
|
||||
testutil.CreateTestUser(t, db, "bob", "bob@test.com", "password123")
|
||||
testutil.CreateTestUser(t, db, "alice", "alice@test.com", "Password123")
|
||||
testutil.CreateTestUser(t, db, "bob", "bob@test.com", "Password123")
|
||||
|
||||
// Set up a minimal Echo instance with the admin handler
|
||||
e := echo.New()
|
||||
@@ -322,7 +322,7 @@ func TestE2E_SQLInjection_AdminSort_Blocked(t *testing.T) {
|
||||
// garbage receipt data does NOT upgrade the user to Pro tier.
|
||||
func TestE2E_IAP_InvalidReceipt_NoPro(t *testing.T) {
|
||||
app := setupSecurityTest(t)
|
||||
token, userID := app.registerAndLoginSec(t, "iapuser", "iap@test.com", "password123")
|
||||
token, userID := app.registerAndLoginSec(t, "iapuser", "iap@test.com", "Password123")
|
||||
|
||||
// Create initial subscription (free tier)
|
||||
sub := &models.UserSubscription{UserID: userID, Tier: models.TierFree}
|
||||
@@ -352,7 +352,7 @@ func TestE2E_IAP_InvalidReceipt_NoPro(t *testing.T) {
|
||||
// updates both the completion record and the task's NextDueDate together (P1-5/P1-6).
|
||||
func TestE2E_CompletionTransaction_Atomic(t *testing.T) {
|
||||
app := setupSecurityTest(t)
|
||||
token, _ := app.registerAndLoginSec(t, "atomicuser", "atomic@test.com", "password123")
|
||||
token, _ := app.registerAndLoginSec(t, "atomicuser", "atomic@test.com", "Password123")
|
||||
|
||||
// Create a residence
|
||||
residenceBody := map[string]interface{}{"name": "Atomic Test House"}
|
||||
@@ -423,7 +423,7 @@ func TestE2E_CompletionTransaction_Atomic(t *testing.T) {
|
||||
// on a recurring task recalculates NextDueDate back to the correct value (P1-7).
|
||||
func TestE2E_DeleteCompletion_RecalculatesNextDueDate(t *testing.T) {
|
||||
app := setupSecurityTest(t)
|
||||
token, _ := app.registerAndLoginSec(t, "recuruser", "recur@test.com", "password123")
|
||||
token, _ := app.registerAndLoginSec(t, "recuruser", "recur@test.com", "Password123")
|
||||
|
||||
// Create a residence
|
||||
residenceBody := map[string]interface{}{"name": "Recurring Test House"}
|
||||
@@ -510,7 +510,7 @@ func TestE2E_DeleteCompletion_RecalculatesNextDueDate(t *testing.T) {
|
||||
// configured property limit.
|
||||
func TestE2E_TierLimits_Enforced(t *testing.T) {
|
||||
app := setupSecurityTest(t)
|
||||
token, userID := app.registerAndLoginSec(t, "tieruser", "tier@test.com", "password123")
|
||||
token, userID := app.registerAndLoginSec(t, "tieruser", "tier@test.com", "Password123")
|
||||
|
||||
// Enable global limitations
|
||||
app.DB.Where("1=1").Delete(&models.SubscriptionSettings{})
|
||||
@@ -602,7 +602,7 @@ func TestE2E_AuthAssertion_NoPanics(t *testing.T) {
|
||||
// caps the limit parameter to 200 even if the client requests more.
|
||||
func TestE2E_NotificationLimit_Capped(t *testing.T) {
|
||||
app := setupSecurityTest(t)
|
||||
token, userID := app.registerAndLoginSec(t, "notifuser", "notif@test.com", "password123")
|
||||
token, userID := app.registerAndLoginSec(t, "notifuser", "notif@test.com", "Password123")
|
||||
|
||||
// Create 210 notifications directly in the database
|
||||
for i := 0; i < 210; i++ {
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestIntegration_IsFreeBypassesLimitations(t *testing.T) {
|
||||
app := setupSubscriptionTest(t)
|
||||
|
||||
// Register and login a user
|
||||
token, userID := app.registerAndLogin(t, "freeuser", "free@test.com", "password123")
|
||||
token, userID := app.registerAndLogin(t, "freeuser", "free@test.com", "Password123")
|
||||
|
||||
// Enable global limitations - first delete any existing, then create with enabled
|
||||
app.DB.Where("1=1").Delete(&models.SubscriptionSettings{})
|
||||
@@ -215,7 +215,7 @@ func TestIntegration_IsFreeBypassesCheckLimit(t *testing.T) {
|
||||
app := setupSubscriptionTest(t)
|
||||
|
||||
// Register and login a user
|
||||
_, userID := app.registerAndLogin(t, "limituser", "limit@test.com", "password123")
|
||||
_, userID := app.registerAndLogin(t, "limituser", "limit@test.com", "Password123")
|
||||
|
||||
// Enable global limitations
|
||||
settings := &models.SubscriptionSettings{EnableLimitations: true}
|
||||
@@ -282,7 +282,7 @@ func TestIntegration_IsFreeIndependentOfTier(t *testing.T) {
|
||||
app := setupSubscriptionTest(t)
|
||||
|
||||
// Register and login a user
|
||||
token, userID := app.registerAndLogin(t, "tieruser", "tier@test.com", "password123")
|
||||
token, userID := app.registerAndLogin(t, "tieruser", "tier@test.com", "Password123")
|
||||
|
||||
// Enable global limitations
|
||||
settings := &models.SubscriptionSettings{EnableLimitations: true}
|
||||
@@ -340,7 +340,7 @@ func TestIntegration_IsFreeWhenGlobalLimitationsDisabled(t *testing.T) {
|
||||
app := setupSubscriptionTest(t)
|
||||
|
||||
// Register and login a user
|
||||
token, userID := app.registerAndLogin(t, "globaluser", "global@test.com", "password123")
|
||||
token, userID := app.registerAndLogin(t, "globaluser", "global@test.com", "Password123")
|
||||
|
||||
// Disable global limitations
|
||||
settings := &models.SubscriptionSettings{EnableLimitations: false}
|
||||
|
||||
Reference in New Issue
Block a user