package models_test import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "rocketgit.ru/rsmon/worker/app/models" ) // makeStaleUser inserts a user whose LastActiveAt is older than the // 3-month cutoff used by the cleanup filter. The returned user is what // the candidates query should pick up. func makeStaleUser(t *testing.T, email string, lastActive *time.Time) *models.User { t.Helper() u := &models.User{ Email: &email, Name: "stale " + email, Enabled: true, Confirmed: true, LastActiveAt: lastActive, } require.NoError(t, models.DB().Create(u).Error) return u } // TestFindStaleAccounts_EmptyWhenNoCandidates checks the obvious // negative case: a fresh account with an active owner is not eligible. func TestFindStaleAccounts_EmptyWhenNoCandidates(t *testing.T) { models.Drop() models.Migrate() plan := models.Plan{Name: "test", Default: true} require.NoError(t, models.DB().Create(&plan).Error) acc := models.Account{Name: "fresh", PlanID: &plan.ID} require.NoError(t, models.DB().Create(&acc).Error) // Group + owner access + active user group := models.Group{AccountID: acc.ID, Name: "default"} require.NoError(t, models.DB().Create(&group).Error) recent := time.Now().Add(-1 * time.Hour) user := makeStaleUser(t, "active@example.com", &recent) access := models.Access{AccountID: acc.ID, UserID: &user.ID, Kind: "account", Role: "owner"} require.NoError(t, models.DB().Create(&access).Error) got, err := models.FindStaleAccounts() require.NoError(t, err) assert.Empty(t, got, "an account with an active owner is not stale") } // TestFindStaleAccounts_PicksStaleEmptyAccount checks the happy path: // account with no monitors + single user + last login > 3 months ago. func TestFindStaleAccounts_PicksStaleEmptyAccount(t *testing.T) { models.Drop() models.Migrate() plan := models.Plan{Name: "test", Default: true} require.NoError(t, models.DB().Create(&plan).Error) acc := models.Account{Name: "ghost", PlanID: &plan.ID} require.NoError(t, models.DB().Create(&acc).Error) group := models.Group{AccountID: acc.ID, Name: "default"} require.NoError(t, models.DB().Create(&group).Error) neverLoggedIn := (*time.Time)(nil) user := makeStaleUser(t, "ghost@example.com", neverLoggedIn) access := models.Access{AccountID: acc.ID, UserID: &user.ID, Kind: "account", Role: "owner"} require.NoError(t, models.DB().Create(&access).Error) got, err := models.FindStaleAccounts() require.NoError(t, err) require.Len(t, got, 1, "the empty stale account should be picked up") assert.Equal(t, acc.ID, got[0].AccountID) assert.Equal(t, user.ID, got[0].UserID) } // TestFindStaleAccounts_SkipsAccountWithMonitors makes sure the // "zero monitors" gate is enforced. func TestFindStaleAccounts_SkipsAccountWithMonitors(t *testing.T) { models.Drop() models.Migrate() plan := models.Plan{Name: "test", Default: true} require.NoError(t, models.DB().Create(&plan).Error) acc := models.Account{Name: "active", PlanID: &plan.ID} require.NoError(t, models.DB().Create(&acc).Error) group := models.Group{AccountID: acc.ID, Name: "default"} require.NoError(t, models.DB().Create(&group).Error) // One monitor → account is NOT eligible even if the user is stale. monitor := models.Monitor{GroupID: group.ID, Host: "example.com"} require.NoError(t, models.DB().Create(&monitor).Error) stale := time.Now().Add(-365 * 24 * time.Hour) user := makeStaleUser(t, "owner@example.com", &stale) access := models.Access{AccountID: acc.ID, UserID: &user.ID, Kind: "account", Role: "owner"} require.NoError(t, models.DB().Create(&access).Error) got, err := models.FindStaleAccounts() require.NoError(t, err) assert.Empty(t, got) } // TestFindStaleAccounts_SkipsUserWithMultipleAccounts verifies that a // user holding two accounts disqualifies BOTH accounts. func TestFindStaleAccounts_SkipsUserWithMultipleAccounts(t *testing.T) { models.Drop() models.Migrate() plan := models.Plan{Name: "test", Default: true} require.NoError(t, models.DB().Create(&plan).Error) acc1 := models.Account{Name: "acc1", PlanID: &plan.ID} require.NoError(t, models.DB().Create(&acc1).Error) acc2 := models.Account{Name: "acc2", PlanID: &plan.ID} require.NoError(t, models.DB().Create(&acc2).Error) models.DB().Create(&models.Group{AccountID: acc1.ID, Name: "g1"}) models.DB().Create(&models.Group{AccountID: acc2.ID, Name: "g2"}) stale := time.Now().Add(-365 * 24 * time.Hour) user := makeStaleUser(t, "shared@example.com", &stale) require.NoError(t, models.DB().Create(&models.Access{AccountID: acc1.ID, UserID: &user.ID, Kind: "account", Role: "owner"}).Error) require.NoError(t, models.DB().Create(&models.Access{AccountID: acc2.ID, UserID: &user.ID, Kind: "account", Role: "owner"}).Error) got, err := models.FindStaleAccounts() require.NoError(t, err) assert.Empty(t, got, "user with two accounts disqualifies both accounts") } // TestCleanupStaleAccounts_HardDeletesEligibleAndOrphans verifies the // end-to-end cleanup: matching account + user are removed, and // recently-active accounts survive. func TestCleanupStaleAccounts_HardDeletesEligibleAndOrphans(t *testing.T) { models.Drop() models.Migrate() plan := models.Plan{Name: "test", Default: true} require.NoError(t, models.DB().Create(&plan).Error) // Stale account with a stale user that has only this one account. staleAcc := models.Account{Name: "ghost", PlanID: &plan.ID} require.NoError(t, models.DB().Create(&staleAcc).Error) staleGroup := models.Group{AccountID: staleAcc.ID, Name: "g"} require.NoError(t, models.DB().Create(&staleGroup).Error) staleUser := makeStaleUser(t, "ghost@example.com", nil) require.NoError(t, models.DB().Create(&models.Access{AccountID: staleAcc.ID, UserID: &staleUser.ID, Kind: "account", Role: "owner"}).Error) // Active account with a recent user — must NOT be touched. freshAcc := models.Account{Name: "fresh", PlanID: &plan.ID} require.NoError(t, models.DB().Create(&freshAcc).Error) require.NoError(t, models.DB().Create(&models.Group{AccountID: freshAcc.ID, Name: "g"}).Error) recent := time.Now().Add(-1 * time.Hour) freshUser := makeStaleUser(t, "fresh@example.com", &recent) require.NoError(t, models.DB().Create(&models.Access{AccountID: freshAcc.ID, UserID: &freshUser.ID, Kind: "account", Role: "owner"}).Error) deleted, err := models.CleanupStaleAccounts() require.NoError(t, err) assert.Equal(t, 1, deleted, "only the stale empty account should be deleted") // Stale account is gone. var count int64 require.NoError(t, models.DB().Model(&models.Account{}).Where("id = ?", staleAcc.ID).Count(&count).Error) assert.Equal(t, int64(0), count) // Stale user is orphaned → also hard-deleted by the cleanup pass. require.NoError(t, models.DB().Model(&models.User{}).Where("id = ?", staleUser.ID).Count(&count).Error) assert.Equal(t, int64(0), count) // Fresh account and user survive. require.NoError(t, models.DB().Model(&models.Account{}).Where("id = ?", freshAcc.ID).Count(&count).Error) assert.Equal(t, int64(1), count) require.NoError(t, models.DB().Model(&models.User{}).Where("id = ?", freshUser.ID).Count(&count).Error) assert.Equal(t, int64(1), count) }