package models_test import ( "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "rocketgit.ru/rsmon/worker/app/models" "rocketgit.ru/rsmon/worker/config/database" ) func init() { database.Init() } // TestNotificationCredential_EncryptDecryptRoundTrip verifies that SetSecret // followed by GetSecret returns the original plaintext regardless of which // encryption mode (AES-GCM or plain: fallback) is active. func TestNotificationCredential_EncryptDecryptRoundTrip(t *testing.T) { c := &models.NotificationCredential{} plaintext := "super-secret-smtp-password" require.NoError(t, c.SetSecret(plaintext)) got, err := c.GetSecret() require.NoError(t, err) assert.Equal(t, plaintext, got) } // TestNotificationCredential_EncryptDecryptWithKey verifies that when // RSMON_CRED_KEY is configured the stored value is prefixed with "enc:" and // can still be decrypted back to the original plaintext. func TestNotificationCredential_EncryptDecryptWithKey(t *testing.T) { t.Setenv("RSMON_CRED_KEY", "test-key-rotate-me-1234567890") c := &models.NotificationCredential{} plaintext := "bot-token-9876543210:ABCDEFG" require.NoError(t, c.SetSecret(plaintext)) assert.True(t, strings.HasPrefix(c.SecretEnc, "enc:"), "expected SecretEnc to start with 'enc:' prefix, got %q", c.SecretEnc) assert.NotEqual(t, plaintext, c.SecretEnc, "encrypted value must not equal plaintext") got, err := c.GetSecret() require.NoError(t, err) assert.Equal(t, plaintext, got) } // TestNotificationCredential_CRUD exercises create / find-by-id / find-by-name // / delete against the test database. func TestNotificationCredential_CRUD(t *testing.T) { models.Drop() models.Migrate() server := "smtp.example.com" port := 587 login := "alerts@example.com" enabled := true c := &models.NotificationCredential{ Kind: models.CredentialKindSMTP, Name: "primary", Server: &server, Port: &port, Login: &login, Enabled: &enabled, } require.NoError(t, c.SetSecret("smtp-password-xyz")) require.NoError(t, models.DB().Create(c).Error) require.NotZero(t, c.ID, "expected ID to be assigned after Create") found, err := models.FindCredential(c.ID) require.NoError(t, err) assert.Equal(t, "primary", found.Name) assert.Equal(t, models.CredentialKindSMTP, found.Kind) require.NotNil(t, found.Server) assert.Equal(t, "smtp.example.com", *found.Server) gotSecret, err := found.GetSecret() require.NoError(t, err) assert.Equal(t, "smtp-password-xyz", gotSecret) assert.Equal(t, "s***z", found.SecretMasked) byName, err := models.FindCredentialByName(models.CredentialKindSMTP, "primary") require.NoError(t, err) assert.Equal(t, c.ID, byName.ID) require.NoError(t, models.DeleteCredential(c.ID)) _, err = models.FindCredential(c.ID) assert.Error(t, err, "FindCredential should fail after delete") } // TestNotificationCredential_UniqueKindName verifies that two credentials with // the same (kind, name) pair violate the unique index. func TestNotificationCredential_UniqueKindName(t *testing.T) { models.Drop() models.Migrate() enTrue := true first := &models.NotificationCredential{ Kind: models.CredentialKindTelegram, Name: "main-bot", Enabled: &enTrue, } require.NoError(t, first.SetSecret("token-a")) require.NoError(t, models.DB().Create(first).Error) second := &models.NotificationCredential{ Kind: models.CredentialKindTelegram, Name: "main-bot", Enabled: &enTrue, } require.NoError(t, second.SetSecret("token-b")) err := models.DB().Create(second).Error require.Error(t, err, "expected unique constraint violation for duplicate (kind, name)") assert.True(t, strings.Contains(strings.ToLower(err.Error()), "unique") || strings.Contains(strings.ToLower(err.Error()), "duplicate"), "expected error mentioning unique/duplicate, got: %v", err) } // TestEnabledCredentialsByKind verifies the kind+enabled filter. func TestEnabledCredentialsByKind(t *testing.T) { models.Drop() models.Migrate() enTrue := true enFalse := false enabled := &models.NotificationCredential{Kind: models.CredentialKindSMTP, Name: "enabled-1", Enabled: &enTrue} disabled := &models.NotificationCredential{Kind: models.CredentialKindSMTP, Name: "disabled-1", Enabled: &enFalse} require.NoError(t, models.DB().Create(enabled).Error) require.NoError(t, models.DB().Create(disabled).Error) got, err := models.EnabledCredentialsByKind(models.CredentialKindSMTP) require.NoError(t, err) var names []string for _, c := range got { names = append(names, c.Name) } assert.Contains(t, names, "enabled-1") assert.NotContains(t, names, "disabled-1") } func TestNotificationCredentialsAreScopedToSystemOrAccount(t *testing.T) { models.Drop() models.Migrate() accountA := &models.Account{Name: "credential-a"} accountB := &models.Account{Name: "credential-b"} require.NoError(t, models.DB().Create(accountA).Error) require.NoError(t, models.DB().Create(accountB).Error) enabled := true system := &models.NotificationCredential{Kind: models.CredentialKindSMTP, Name: "system", Enabled: &enabled} ownedA := &models.NotificationCredential{AccountID: &accountA.ID, Kind: models.CredentialKindSMTP, Name: "owned", Enabled: &enabled} ownedB := &models.NotificationCredential{AccountID: &accountB.ID, Kind: models.CredentialKindSMTP, Name: "owned", Enabled: &enabled} require.NoError(t, models.DB().Create(system).Error) require.NoError(t, models.DB().Create(ownedA).Error) require.NoError(t, models.DB().Create(ownedB).Error) systemCreds, err := models.EnabledCredentialsByKind(models.CredentialKindSMTP) require.NoError(t, err) require.Len(t, systemCreds, 1) assert.Equal(t, system.ID, systemCreds[0].ID) accountCreds, err := models.EnabledCredentialsByAccountAndKind(accountA.ID, models.CredentialKindSMTP) require.NoError(t, err) require.Len(t, accountCreds, 1) assert.Equal(t, ownedA.ID, accountCreds[0].ID) } func TestNotificationCredentialSecretMasked(t *testing.T) { models.Drop() models.Migrate() cred := models.NotificationCredential{Kind: models.CredentialKindSMTP, Name: "smtp"} require.NoError(t, cred.SetSecret("password")) require.NoError(t, models.DB().Create(&cred).Error) loaded, err := models.FindCredential(cred.ID) require.NoError(t, err) assert.Equal(t, "p***d", loaded.SecretMasked) }