Некоторые проверки не удались
CI / test (push) Successful in 2m5s
Docker / Build and publish worker image (push) Failing after 31s
147 строки
5.5 KiB
Go
147 строки
5.5 KiB
Go
package models_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"rocketgit.ru/rsmon/worker/app/models"
|
|
"rocketgit.ru/rsmon/worker/spec/factories"
|
|
)
|
|
|
|
// TestNotificationGetContactsIncludesSystemContact exercises the regression
|
|
// reported when the production dump was restored into dev: the
|
|
// contacts.is_system column was missing and the GORM preload panicked on
|
|
// GetContacts. The fix has two layers:
|
|
//
|
|
// 1. AutoMigrate must add is_system (and deletion_requested_at) before the
|
|
// notifier scheduler starts running.
|
|
// 2. GetContacts itself must not panic on a query error so a single bad row
|
|
// cannot tear down the scheduler goroutine.
|
|
//
|
|
// This test verifies both layers by:
|
|
// - asserting that the schema post-Migrate includes is_system, so the
|
|
// production-like scenario no longer panics; and
|
|
// - building a notification that contains a contact flagged is_system=true
|
|
// and checking GetContacts returns it.
|
|
func TestNotificationGetContactsIncludesSystemContact(t *testing.T) {
|
|
models.Drop()
|
|
models.Migrate()
|
|
|
|
// Column must exist after Migrate; otherwise GetContacts would fail
|
|
// with the same panic we saw in production.
|
|
assertColumnExists(t, "contacts", "is_system")
|
|
|
|
account := &models.Account{Name: "acct-get-contacts"}
|
|
require.NoError(t, models.DB().Create(account).Error)
|
|
accountID := account.ID
|
|
|
|
trueVal := true
|
|
contact := &models.Contact{
|
|
AccountID: &accountID,
|
|
Name: "system-admin",
|
|
Kind: "email",
|
|
Value: "ops@example.com",
|
|
IsSystem: &trueVal,
|
|
}
|
|
require.NoError(t, models.DB().Create(contact).Error)
|
|
|
|
notification := factories.PersistedNotification(
|
|
account, []int64{contact.ID}, nil, 300, false,
|
|
)
|
|
|
|
// Reload so the model has its persisted ID; the factory's PersistRelations
|
|
// may have left ContactIDs empty on the returned value depending on the
|
|
// GORM version, so fetch fresh.
|
|
require.NoError(t, models.DB().
|
|
Preload("Contacts").
|
|
First(¬ification, notification.ID).Error)
|
|
|
|
got := notification.GetContacts()
|
|
|
|
ids := make([]int64, 0, len(got))
|
|
for _, c := range got {
|
|
ids = append(ids, c.ID)
|
|
}
|
|
assert.Contains(t, ids, contact.ID, "GetContacts must include the is_system contact")
|
|
}
|
|
|
|
// TestUserDeletionRequestedAtColumnAndRoundTrip verifies the second missing
|
|
// column reported by the panic: users.deletion_requested_at. It asserts that
|
|
// AutoMigrate creates the column and that the field round-trips through the
|
|
// DB correctly. ProcessPendingDeletions (the consumer of this column) relies
|
|
// on it being present and queryable.
|
|
func TestUserDeletionRequestedAtColumnAndRoundTrip(t *testing.T) {
|
|
models.Drop()
|
|
models.Migrate()
|
|
|
|
assertColumnExists(t, "users", "deletion_requested_at")
|
|
|
|
user := factories.PersistedUser("deletion-roundtrip@test.ru", "secret")
|
|
now := time.Now().UTC().Truncate(time.Microsecond)
|
|
user.DeletionRequestedAt = &now
|
|
|
|
require.NoError(t, models.DB().Save(&user).Error)
|
|
|
|
reloaded := models.User{}
|
|
require.NoError(t, models.DB().First(&reloaded, user.ID).Error)
|
|
|
|
require.NotNil(t, reloaded.DeletionRequestedAt, "deletion_requested_at must round-trip via Save/First")
|
|
assert.True(t, reloaded.DeletionRequestedAt.Equal(now),
|
|
"deletion_requested_at must preserve the timestamp value (got %v, want %v)",
|
|
reloaded.DeletionRequestedAt, now)
|
|
|
|
// ProcessPendingDeletions should not panic on the populated schema and
|
|
// must respect the cutoff: a recently-set deletion_requested_at is
|
|
// still inside the 7-day grace period, so no hard-delete must occur.
|
|
deleted, err := models.ProcessPendingDeletions()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, deleted, "users within the 7-day grace period must not be hard-deleted")
|
|
}
|
|
|
|
// TestProcessPendingDeletionsQueriesMissingColumnGracefully asserts that even
|
|
// if the deletion_requested_at column were missing, ProcessPendingDeletions
|
|
// would not panic (the panic-on-error pattern was historically present in
|
|
// other notifier helpers). We force the failure by renaming the column back,
|
|
// calling ProcessPendingDeletions, then restoring the column.
|
|
func TestProcessPendingDeletionsQueriesMissingColumnGracefully(t *testing.T) {
|
|
models.Drop()
|
|
models.Migrate()
|
|
assertColumnExists(t, "users", "deletion_requested_at")
|
|
|
|
// Simulate the production-missing-column scenario in a contained way:
|
|
// rename the column so the SELECT against deletion_requested_at fails.
|
|
require.NoError(t, models.DB().
|
|
Exec("ALTER TABLE users RENAME COLUMN deletion_requested_at TO deletion_requested_at_hidden").Error)
|
|
t.Cleanup(func() {
|
|
// Restore so subsequent tests in this package keep working.
|
|
_ = models.DB().
|
|
Exec("ALTER TABLE users RENAME COLUMN deletion_requested_at_hidden TO deletion_requested_at").Error
|
|
})
|
|
|
|
// Must not panic; must return an error.
|
|
assert.NotPanics(t, func() {
|
|
_, err := models.ProcessPendingDeletions()
|
|
assert.Error(t, err, "missing column must surface as an error, not a panic")
|
|
})
|
|
}
|
|
|
|
// assertColumnExists checks that the given table has the given column by
|
|
// querying information_schema. It is the canary for the AutoMigrate step
|
|
// ordering bug: if the column is missing, every test that touches it will
|
|
// panic with SQLSTATE 42703.
|
|
func assertColumnExists(t *testing.T, table, column string) {
|
|
t.Helper()
|
|
var n int
|
|
err := models.DB().Raw(
|
|
`SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = current_schema()
|
|
AND table_name = ? AND column_name = ?`,
|
|
table, column,
|
|
).Scan(&n).Error
|
|
require.NoError(t, err, "information_schema query must succeed")
|
|
assert.Equal(t, 1, n, "table %q must have column %q after Migrate()", table, column)
|
|
}
|