feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
181
app/models/cleanup_stale_test.go
Обычный файл
181
app/models/cleanup_stale_test.go
Обычный файл
@@ -0,0 +1,181 @@
|
||||
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)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user