Некоторые проверки не удались
CI / test (push) Successful in 2m5s
Docker / Build and publish worker image (push) Failing after 31s
161 строка
5.4 KiB
Go
161 строка
5.4 KiB
Go
package notifier
|
|
|
|
import (
|
|
"log"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"rocketgit.ru/rsmon/worker/app/models"
|
|
"rocketgit.ru/rsmon/worker/config/database"
|
|
"rocketgit.ru/rsmon/worker/spec/factories"
|
|
)
|
|
|
|
func init() {
|
|
database.Init()
|
|
}
|
|
|
|
func TestRunExp(t *testing.T) {
|
|
log.Println("TestRunExp")
|
|
models.Drop()
|
|
models.Migrate()
|
|
var err error
|
|
contact, notification, monitor := factories.MonitorWithNotification()
|
|
|
|
check := factories.PersistedCheck(&monitor, "ssl")
|
|
assert.Equal(t, "ssl", check.Kind, "check kind should be ssl")
|
|
assert.Equal(t, monitor.ID, check.MonitorID, "check should have correct monitor id")
|
|
|
|
exp := time.Now().Add(14 * 24 * time.Hour)
|
|
check.Expires = &exp
|
|
err = models.DB().Save(&check).Error
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
RunExp()
|
|
shoudHaveMessages("exp1 - contact should receive no messages", t, "exp", notification.ID, contact.ID, []int64{})
|
|
|
|
exp = time.Now().Add(1 * time.Hour)
|
|
check.Expires = &exp
|
|
err = models.DB().Save(&check).Error
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
RunExp()
|
|
shoudHaveMessages("exp2 - contact should receive messages", t, "exp", notification.ID, contact.ID, []int64{check.ID})
|
|
|
|
RunExp()
|
|
shoudHaveMessages("exp3 - contact should not receive duplicate messages", t, "exp", notification.ID, contact.ID, []int64{check.ID})
|
|
}
|
|
|
|
// TestRunExpExpiresSystemContact exercises the regression scenario that the
|
|
// dev DB hit after being restored from the production dump: the
|
|
// contacts.is_system column was missing from the production schema and the
|
|
// notifier's RunExp panic'd on the GORM preload.
|
|
//
|
|
// The fix has three layers: AutoMigrate adds the column, GetContacts logs
|
|
// instead of panicking, and RunExp/RunExpCheck recover from panics. This
|
|
// test verifies all three by setting a contact's is_system=true, queueing an
|
|
// expiring SSL check, and confirming RunExp:
|
|
// - does not panic;
|
|
// - writes a queued exp message for the is_system contact (i.e. the schema
|
|
// has the column and the field round-trips); and
|
|
// - leaves RunExp returnable to its caller.
|
|
func TestRunExpExpiresSystemContact(t *testing.T) {
|
|
log.Println("TestRunExpExpiresSystemContact")
|
|
models.Drop()
|
|
models.Migrate()
|
|
|
|
account := &models.Account{Name: "acct-system-contact"}
|
|
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)
|
|
|
|
group := factories.PersistedGroup(account)
|
|
notification := factories.PersistedNotification(
|
|
account, []int64{contact.ID}, []int64{group.ID}, 300, false,
|
|
)
|
|
monitor := factories.PersistedMonitor(&group)
|
|
|
|
check := factories.PersistedCheck(&monitor, "ssl")
|
|
exp := time.Now().Add(1 * time.Hour)
|
|
check.Expires = &exp
|
|
require.NoError(t, models.DB().Save(&check).Error)
|
|
|
|
assert.NotPanics(t, func() {
|
|
RunExp()
|
|
}, "RunExp must not panic when processing an is_system contact")
|
|
|
|
shoudHaveMessages(
|
|
"is_system contact must receive the exp message",
|
|
t, "exp", notification.ID, contact.ID, []int64{check.ID},
|
|
)
|
|
}
|
|
|
|
// TestRunExpDoesNotPanicOnBrokenAssociation replays the original prod-dump
|
|
// panic in a contained way: the notification_contacts join row references a
|
|
// non-existent contact id, which forces GORM's preload of Contacts to fail.
|
|
// The fix's defensive recover() must keep RunExpCheck returning cleanly so
|
|
// the scheduler loop survives a single bad row.
|
|
//
|
|
// We deliberately bypass the contacts.is_system column-drop path because
|
|
// Postgres caches prepared statements per session; mutating the contacts
|
|
// schema mid-test triggers SQLSTATE 0A000 (cached plan must not change
|
|
// result type) on the pool's other connections and masks the panic we want
|
|
// to verify.
|
|
func TestRunExpDoesNotPanicOnBrokenAssociation(t *testing.T) {
|
|
log.Println("TestRunExpDoesNotPanicOnBrokenAssociation")
|
|
models.Drop()
|
|
models.Migrate()
|
|
|
|
_, notification, monitor := factories.MonitorWithNotification()
|
|
|
|
// Force a broken association by deleting the contact that the
|
|
// notification points to. GORM's preload of Contacts will then have no
|
|
// rows for that notification, exercising the empty-contacts path
|
|
// without involving DDL or FK violations.
|
|
require.NoError(t, models.DB().
|
|
Exec("DELETE FROM notification_contacts WHERE notification_id = ?", notification.ID).Error)
|
|
// Re-add a join row pointing to a contact id that has been deleted
|
|
// from contacts. We disable the FK temporarily so the join row sticks.
|
|
require.NoError(t, models.DB().
|
|
Exec("SET session_replication_role = 'replica'").Error)
|
|
t.Cleanup(func() {
|
|
_ = models.DB().
|
|
Exec("SET session_replication_role = 'origin'").Error
|
|
})
|
|
bogusContactID := int64(9999999)
|
|
require.NoError(t, models.DB().
|
|
Exec(
|
|
"INSERT INTO notification_contacts (notification_id, contact_id) VALUES (?, ?)",
|
|
notification.ID, bogusContactID,
|
|
).Error)
|
|
|
|
check := factories.PersistedCheck(&monitor, "ssl")
|
|
exp := time.Now().Add(1 * time.Hour)
|
|
check.Expires = &exp
|
|
require.NoError(t, models.DB().Save(&check).Error)
|
|
|
|
require.NoError(t, models.DB().
|
|
Preload("Monitor").
|
|
Preload("Monitor.Group").
|
|
Preload("Monitor.Group.Notifications").
|
|
Preload("Monitor.Group.Notifications.Contacts").
|
|
First(&check, check.ID).Error)
|
|
|
|
assert.NotPanics(t, func() {
|
|
RunExpCheck(&check)
|
|
}, "RunExpCheck must not panic when Contact preload encounters a broken association")
|
|
}
|