Files
worker/app/models/cleanup_stale_test.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

182 строки
7.1 KiB
Go

package models_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"rsgit.ru/rsmon/rsmon/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)
}