75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
1023 lines
28 KiB
Go
1023 lines
28 KiB
Go
package telemetry
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
backups_core_logical "databasus-backend/internal/features/backups/backups/core/logical"
|
|
physical_models "databasus-backend/internal/features/backups/backups/core/physical/models"
|
|
"databasus-backend/internal/features/databases"
|
|
"databasus-backend/internal/features/databases/databases/mariadb"
|
|
"databasus-backend/internal/features/databases/databases/mongodb"
|
|
"databasus-backend/internal/features/databases/databases/mysql"
|
|
postgresql_logical "databasus-backend/internal/features/databases/databases/postgresql/logical"
|
|
postgresql_physical "databasus-backend/internal/features/databases/databases/postgresql/physical"
|
|
"databasus-backend/internal/features/intervals"
|
|
"databasus-backend/internal/features/notifiers"
|
|
"databasus-backend/internal/features/storages"
|
|
verification_agents "databasus-backend/internal/features/verification/agents"
|
|
verification_config "databasus-backend/internal/features/verification/config"
|
|
"databasus-backend/internal/util/tools"
|
|
)
|
|
|
|
type fakeSender struct {
|
|
calls []*CollectRequest
|
|
err error
|
|
}
|
|
|
|
func (f *fakeSender) Send(_ context.Context, req *CollectRequest) error {
|
|
f.calls = append(f.calls, req)
|
|
return f.err
|
|
}
|
|
|
|
type fakeDatabaseLister struct {
|
|
databases []*databases.Database
|
|
err error
|
|
}
|
|
|
|
func (f *fakeDatabaseLister) GetAllDatabases() ([]*databases.Database, error) {
|
|
return f.databases, f.err
|
|
}
|
|
|
|
type fakeStorageLister struct {
|
|
storages []*storages.Storage
|
|
err error
|
|
}
|
|
|
|
func (f *fakeStorageLister) GetAllStorages() ([]*storages.Storage, error) {
|
|
return f.storages, f.err
|
|
}
|
|
|
|
type fakeNotifierLister struct {
|
|
notifiers []*notifiers.Notifier
|
|
err error
|
|
}
|
|
|
|
func (f *fakeNotifierLister) GetAllNotifiers() ([]*notifiers.Notifier, error) {
|
|
return f.notifiers, f.err
|
|
}
|
|
|
|
type fakeBackupChecker struct {
|
|
hasBackupSince map[uuid.UUID]bool
|
|
latestBackups map[uuid.UUID]*backups_core_logical.LogicalBackup
|
|
err error
|
|
latestErr error
|
|
}
|
|
|
|
func (f *fakeBackupChecker) HasSuccessfulBackupSince(
|
|
databaseID uuid.UUID,
|
|
_ time.Time,
|
|
) (bool, error) {
|
|
if f.err != nil {
|
|
return false, f.err
|
|
}
|
|
|
|
return f.hasBackupSince[databaseID], nil
|
|
}
|
|
|
|
func (f *fakeBackupChecker) GetLatestCompletedBackup(
|
|
databaseID uuid.UUID,
|
|
) (*backups_core_logical.LogicalBackup, error) {
|
|
if f.latestErr != nil {
|
|
return nil, f.latestErr
|
|
}
|
|
|
|
return f.latestBackups[databaseID], nil
|
|
}
|
|
|
|
type fakePhysicalFullBackupSizer struct {
|
|
fullBackups map[uuid.UUID]*physical_models.PhysicalFullBackup
|
|
err error
|
|
}
|
|
|
|
func (f *fakePhysicalFullBackupSizer) GetLatestCompletedFullBackup(
|
|
databaseID uuid.UUID,
|
|
) (*physical_models.PhysicalFullBackup, error) {
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
|
|
return f.fullBackups[databaseID], nil
|
|
}
|
|
|
|
type fakeUserCounter struct {
|
|
count int64
|
|
err error
|
|
}
|
|
|
|
func (f *fakeUserCounter) GetUsersCount() (int64, error) {
|
|
if f.err != nil {
|
|
return 0, f.err
|
|
}
|
|
|
|
return f.count, nil
|
|
}
|
|
|
|
type fakeVerificationAgentLister struct {
|
|
agents []*verification_agents.Agent
|
|
err error
|
|
}
|
|
|
|
func (f *fakeVerificationAgentLister) ListAgents() ([]*verification_agents.Agent, error) {
|
|
return f.agents, f.err
|
|
}
|
|
|
|
type fakeVerificationConfigLister struct {
|
|
enabled []*verification_config.BackupVerificationConfig
|
|
err error
|
|
}
|
|
|
|
func (f *fakeVerificationConfigLister) ListEnabled() (
|
|
[]*verification_config.BackupVerificationConfig,
|
|
error,
|
|
) {
|
|
return f.enabled, f.err
|
|
}
|
|
|
|
func newServiceUnderTest(
|
|
t *testing.T,
|
|
databaseLister databaseLister,
|
|
storageLister storageLister,
|
|
notifierLister notifierLister,
|
|
backupChecker backupChecker,
|
|
verificationAgentLister verificationAgentLister,
|
|
verificationConfigLister verificationConfigLister,
|
|
sender TelemetrySender,
|
|
) *TelemetryService {
|
|
t.Helper()
|
|
|
|
return newServiceUnderTestWith(
|
|
t,
|
|
databaseLister,
|
|
storageLister,
|
|
notifierLister,
|
|
backupChecker,
|
|
&fakePhysicalFullBackupSizer{},
|
|
verificationAgentLister,
|
|
verificationConfigLister,
|
|
&fakeUserCounter{},
|
|
sender,
|
|
)
|
|
}
|
|
|
|
func newServiceUnderTestWith(
|
|
t *testing.T,
|
|
databaseLister databaseLister,
|
|
storageLister storageLister,
|
|
notifierLister notifierLister,
|
|
backupChecker backupChecker,
|
|
physicalBackupSizer physicalFullBackupSizer,
|
|
verificationAgentLister verificationAgentLister,
|
|
verificationConfigLister verificationConfigLister,
|
|
userCounter userCounter,
|
|
sender TelemetrySender,
|
|
) *TelemetryService {
|
|
t.Helper()
|
|
loader := NewInstanceFileLoader(
|
|
filepath.Join(t.TempDir(), "instance.json"),
|
|
slog.New(slog.DiscardHandler),
|
|
)
|
|
|
|
return NewTelemetryService(
|
|
loader,
|
|
sender,
|
|
databaseLister,
|
|
storageLister,
|
|
notifierLister,
|
|
backupChecker,
|
|
physicalBackupSizer,
|
|
verificationAgentLister,
|
|
verificationConfigLister,
|
|
userCounter,
|
|
"9.9.9",
|
|
slog.New(slog.DiscardHandler),
|
|
)
|
|
}
|
|
|
|
func availableStatus() *databases.HealthStatus {
|
|
s := databases.HealthStatusAvailable
|
|
return &s
|
|
}
|
|
|
|
func unavailableStatus() *databases.HealthStatus {
|
|
s := databases.HealthStatusUnavailable
|
|
return &s
|
|
}
|
|
|
|
func postgresDatabase(name string, status *databases.HealthStatus) *databases.Database {
|
|
return &databases.Database{
|
|
ID: uuid.New(),
|
|
Name: name,
|
|
Type: databases.DatabaseTypePostgresLogical,
|
|
HealthStatus: status,
|
|
PostgresqlLogical: &postgresql_logical.PostgresqlLogicalDatabase{
|
|
Version: tools.PostgresqlVersion("16"),
|
|
},
|
|
}
|
|
}
|
|
|
|
func physicalDatabase(
|
|
name string,
|
|
backupType postgresql_physical.BackupType,
|
|
status *databases.HealthStatus,
|
|
) *databases.Database {
|
|
return &databases.Database{
|
|
ID: uuid.New(),
|
|
Name: name,
|
|
Type: databases.DatabaseTypePostgresPhysical,
|
|
HealthStatus: status,
|
|
PostgresqlPhysical: &postgresql_physical.PostgresqlPhysicalDatabase{
|
|
Version: tools.PostgresqlVersion("17"),
|
|
BackupType: backupType,
|
|
},
|
|
}
|
|
}
|
|
|
|
func floatPtr(value float64) *float64 {
|
|
return &value
|
|
}
|
|
|
|
func Test_BuildAndSend_ProducesExpectedRequest(t *testing.T) {
|
|
pgDB := postgresDatabase("pg", availableStatus())
|
|
mysqlDB := &databases.Database{
|
|
ID: uuid.New(),
|
|
Name: "my",
|
|
Type: databases.DatabaseTypeMysql,
|
|
HealthStatus: availableStatus(),
|
|
Mysql: &mysql.MysqlDatabase{Version: tools.MysqlVersion("8.0")},
|
|
}
|
|
mariaDB := &databases.Database{
|
|
ID: uuid.New(),
|
|
Name: "maria",
|
|
Type: databases.DatabaseTypeMariadb,
|
|
HealthStatus: availableStatus(),
|
|
Mariadb: &mariadb.MariadbDatabase{Version: tools.MariadbVersion("10.6")},
|
|
}
|
|
mongoDB := &databases.Database{
|
|
ID: uuid.New(),
|
|
Name: "mongo",
|
|
Type: databases.DatabaseTypeMongodb,
|
|
HealthStatus: availableStatus(),
|
|
Mongodb: &mongodb.MongodbDatabase{Version: tools.MongodbVersion("6.0")},
|
|
}
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{pgDB, mysqlDB, mariaDB, mongoDB}},
|
|
&fakeStorageLister{storages: []*storages.Storage{
|
|
{Type: storages.StorageTypeS3},
|
|
{Type: storages.StorageTypeLocal},
|
|
}},
|
|
&fakeNotifierLister{notifiers: []*notifiers.Notifier{
|
|
{NotifierType: notifiers.NotifierTypeEmail},
|
|
{NotifierType: notifiers.NotifierTypeTelegram},
|
|
}},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
|
|
req := sender.calls[0]
|
|
assert.Equal(t, "9.9.9", req.AppVersion)
|
|
assert.Equal(t, runtime.GOOS, req.OS)
|
|
assert.Equal(t, runtime.GOARCH, req.Arch)
|
|
require.Len(t, req.Databases, 4)
|
|
|
|
types := make([]string, 0, len(req.Databases))
|
|
for _, d := range req.Databases {
|
|
types = append(types, d.Type)
|
|
}
|
|
assert.ElementsMatch(t,
|
|
[]string{"POSTGRES_LOGICAL", "MYSQL", "MARIADB", "MONGODB"},
|
|
types,
|
|
)
|
|
|
|
assert.Equal(t, []string{"LOCAL", "S3"}, req.Storages)
|
|
assert.Equal(t, []string{"EMAIL", "TELEGRAM"}, req.Notifiers)
|
|
assert.Equal(t, time.Now().UTC().Format("2006-01-02"), req.InstalledAt)
|
|
_, err := uuid.Parse(req.InstanceID)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func Test_BuildAndSend_PreservesStorageAndNotifierDuplicates(t *testing.T) {
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{storages: []*storages.Storage{
|
|
{Type: storages.StorageTypeS3},
|
|
{Type: storages.StorageTypeS3},
|
|
{Type: storages.StorageTypeS3},
|
|
{Type: storages.StorageTypeLocal},
|
|
}},
|
|
&fakeNotifierLister{notifiers: []*notifiers.Notifier{
|
|
{NotifierType: notifiers.NotifierTypeEmail},
|
|
{NotifierType: notifiers.NotifierTypeEmail},
|
|
{NotifierType: notifiers.NotifierTypeTelegram},
|
|
}},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
|
|
assert.Equal(t, []string{"LOCAL", "S3", "S3", "S3"}, sender.calls[0].Storages)
|
|
assert.Equal(t, []string{"EMAIL", "EMAIL", "TELEGRAM"}, sender.calls[0].Notifiers)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenInstanceFileFails_DoesNotCallSender(t *testing.T) {
|
|
// Construct a loader pointing at an unwritable path so LoadOrCreate returns false.
|
|
dir := t.TempDir()
|
|
blocker := filepath.Join(dir, "blocker")
|
|
require.NoError(t, writeFileForTest(blocker))
|
|
|
|
sender := &fakeSender{}
|
|
loader := NewInstanceFileLoader(
|
|
filepath.Join(blocker, "nested", "instance.json"),
|
|
slog.New(slog.DiscardHandler),
|
|
)
|
|
|
|
service := NewTelemetryService(
|
|
loader,
|
|
sender,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakePhysicalFullBackupSizer{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
&fakeUserCounter{},
|
|
"9.9.9",
|
|
slog.New(slog.DiscardHandler),
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
assert.Empty(t, sender.calls)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenSenderFails_PropagatesError(t *testing.T) {
|
|
sendErr := errors.New("network down")
|
|
sender := &fakeSender{err: sendErr}
|
|
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
err := service.BuildAndSend(context.Background())
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, sendErr)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenDbHealthStatusAvailable_DbIncluded(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
assert.Len(t, sender.calls[0].Databases, 1)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenDbHealthStatusUnavailable_DbExcluded(t *testing.T) {
|
|
db := postgresDatabase("pg", unavailableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
assert.Empty(t, sender.calls[0].Databases)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenHealthcheckOffAndRecentBackup_DbIncluded(t *testing.T) {
|
|
db := postgresDatabase("pg", nil)
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{hasBackupSince: map[uuid.UUID]bool{db.ID: true}},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
assert.Equal(t, "POSTGRES_LOGICAL", sender.calls[0].Databases[0].Type)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenHealthcheckOffAndNoRecentBackup_DbExcluded(t *testing.T) {
|
|
db := postgresDatabase("pg", nil)
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{hasBackupSince: map[uuid.UUID]bool{}},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
assert.Empty(t, sender.calls[0].Databases)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenBackupCheckerFails_ReturnsError(t *testing.T) {
|
|
db := postgresDatabase("pg", nil)
|
|
checkerErr := errors.New("db down")
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{err: checkerErr},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
err := service.BuildAndSend(context.Background())
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, checkerErr)
|
|
assert.Empty(t, sender.calls)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenLatestBackupHasBothSizes_IncludesBoth(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{
|
|
latestBackups: map[uuid.UUID]*backups_core_logical.LogicalBackup{
|
|
db.ID: {BackupSizeMb: 870.4, BackupRawDbSizeMb: 4321.7},
|
|
},
|
|
},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Equal(t, int64(871), entry.BackupSizeMb)
|
|
assert.Equal(t, int64(4322), entry.RawSizeMb)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenSizesAreSubMb_RoundsUpToOne(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{
|
|
latestBackups: map[uuid.UUID]*backups_core_logical.LogicalBackup{
|
|
db.ID: {BackupSizeMb: 0.3, BackupRawDbSizeMb: 0.1},
|
|
},
|
|
},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Equal(t, int64(1), entry.BackupSizeMb)
|
|
assert.Equal(t, int64(1), entry.RawSizeMb)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(encoded), "backupSizeMb")
|
|
assert.Contains(t, string(encoded), "rawSizeMb")
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenRawSizeZero_IncludesOnlyBackupSize(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{
|
|
latestBackups: map[uuid.UUID]*backups_core_logical.LogicalBackup{
|
|
db.ID: {BackupSizeMb: 100, BackupRawDbSizeMb: 0},
|
|
},
|
|
},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Equal(t, int64(100), entry.BackupSizeMb)
|
|
assert.Equal(t, int64(0), entry.RawSizeMb)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, string(encoded), "rawSizeMb")
|
|
assert.Contains(t, string(encoded), "backupSizeMb")
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenBackupSizeZero_IncludesOnlyRawSize(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{
|
|
latestBackups: map[uuid.UUID]*backups_core_logical.LogicalBackup{
|
|
db.ID: {BackupSizeMb: 0, BackupRawDbSizeMb: 999},
|
|
},
|
|
},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Equal(t, int64(0), entry.BackupSizeMb)
|
|
assert.Equal(t, int64(999), entry.RawSizeMb)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenNoCompletedBackup_OmitsBothSizes(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Equal(t, int64(0), entry.BackupSizeMb)
|
|
assert.Equal(t, int64(0), entry.RawSizeMb)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, string(encoded), "rawSizeMb")
|
|
assert.NotContains(t, string(encoded), "backupSizeMb")
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenLatestBackupLookupFails_ReturnsError(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
lookupErr := errors.New("query exploded")
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{latestErr: lookupErr},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
err := service.BuildAndSend(context.Background())
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, lookupErr)
|
|
assert.Empty(t, sender.calls)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenAgentsRegistered_IncludesCapacityRows(t *testing.T) {
|
|
registeredAgents := []*verification_agents.Agent{
|
|
{
|
|
ID: uuid.New(),
|
|
Name: "agent-1",
|
|
MaxCPU: 4,
|
|
MaxRAMGb: 16,
|
|
MaxDiskGb: 100,
|
|
MaxConcurrentJobs: 2,
|
|
},
|
|
{
|
|
ID: uuid.New(),
|
|
Name: "agent-2",
|
|
MaxCPU: 8,
|
|
MaxRAMGb: 32,
|
|
MaxDiskGb: 200,
|
|
MaxConcurrentJobs: 4,
|
|
},
|
|
}
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{agents: registeredAgents},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
|
|
assert.Equal(t, []VerificationAgentEntry{
|
|
{MaxCPU: 4, MaxRAMGb: 16, MaxDiskGb: 100, MaxConcurrentJobs: 2},
|
|
{MaxCPU: 8, MaxRAMGb: 32, MaxDiskGb: 200, MaxConcurrentJobs: 4},
|
|
}, sender.calls[0].VerificationAgents)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenNoAgents_VerificationAgentsIsEmpty(t *testing.T) {
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
|
|
require.NotNil(t, sender.calls[0].VerificationAgents)
|
|
assert.Empty(t, sender.calls[0].VerificationAgents)
|
|
|
|
encoded, err := json.Marshal(sender.calls[0])
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(encoded), `"verificationAgents":[]`)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenDbHasAfterBackupConfig_VerificationBlockOmitsIntervalType(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{enabled: []*verification_config.BackupVerificationConfig{
|
|
{
|
|
DatabaseID: db.ID,
|
|
IsScheduledVerificationEnabled: true,
|
|
ScheduleType: verification_config.VerificationScheduleAfterBackup,
|
|
},
|
|
}},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
require.NotNil(t, entry.Verification)
|
|
assert.True(t, entry.Verification.IsEnabled)
|
|
assert.Equal(t, "AFTER_BACKUP", entry.Verification.ScheduleType)
|
|
assert.Empty(t, entry.Verification.IntervalType)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(encoded), `"verification"`)
|
|
assert.NotContains(t, string(encoded), "intervalType")
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenDbHasIntervalDailyConfig_IncludesIntervalType(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{enabled: []*verification_config.BackupVerificationConfig{
|
|
{
|
|
DatabaseID: db.ID,
|
|
IsScheduledVerificationEnabled: true,
|
|
ScheduleType: verification_config.VerificationScheduleInterval,
|
|
VerificationInterval: intervals.Interval{Type: intervals.IntervalDaily},
|
|
},
|
|
}},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
require.NotNil(t, entry.Verification)
|
|
assert.True(t, entry.Verification.IsEnabled)
|
|
assert.Equal(t, "INTERVAL", entry.Verification.ScheduleType)
|
|
assert.Equal(t, "DAILY", entry.Verification.IntervalType)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(encoded), `"intervalType":"DAILY"`)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenDbHasNoEnabledConfig_VerificationBlockAbsent(t *testing.T) {
|
|
db := postgresDatabase("pg", availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Nil(t, entry.Verification)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, string(encoded), "verification")
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenVerificationAgentListFails_ReturnsError(t *testing.T) {
|
|
listErr := errors.New("agents query exploded")
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{err: listErr},
|
|
&fakeVerificationConfigLister{},
|
|
sender,
|
|
)
|
|
|
|
err := service.BuildAndSend(context.Background())
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, listErr)
|
|
assert.Empty(t, sender.calls)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenVerificationConfigListFails_ReturnsError(t *testing.T) {
|
|
listErr := errors.New("configs query exploded")
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTest(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{err: listErr},
|
|
sender,
|
|
)
|
|
|
|
err := service.BuildAndSend(context.Background())
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, listErr)
|
|
assert.Empty(t, sender.calls)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenPhysicalDatabase_EmitsTypeBackupTypeAndFullBackupSizes(t *testing.T) {
|
|
db := physicalDatabase(
|
|
"pg-physical",
|
|
postgresql_physical.BackupTypeFullIncrementalAndWalStream,
|
|
availableStatus(),
|
|
)
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTestWith(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakePhysicalFullBackupSizer{
|
|
fullBackups: map[uuid.UUID]*physical_models.PhysicalFullBackup{
|
|
db.ID: {BackupSizeMb: floatPtr(38400.2), RawSizeMb: floatPtr(192000.7)},
|
|
},
|
|
},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
&fakeUserCounter{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Equal(t, "POSTGRES_PHYSICAL", entry.Type)
|
|
assert.Equal(t, "17", entry.Version)
|
|
assert.Equal(t, "FULL_INCREMENTAL_WAL_STREAM", entry.BackupType)
|
|
assert.Equal(t, int64(38401), entry.BackupSizeMb)
|
|
assert.Equal(t, int64(192001), entry.RawSizeMb)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, string(encoded), `"backupType":"FULL_INCREMENTAL_WAL_STREAM"`)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenPhysicalDatabaseHasNoFullBackup_OmitsSizes(t *testing.T) {
|
|
db := physicalDatabase("pg-physical", postgresql_physical.BackupTypeFullOnly, availableStatus())
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTestWith(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakePhysicalFullBackupSizer{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
&fakeUserCounter{},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
require.Len(t, sender.calls[0].Databases, 1)
|
|
|
|
entry := sender.calls[0].Databases[0]
|
|
assert.Equal(t, "FULL", entry.BackupType)
|
|
assert.Equal(t, int64(0), entry.BackupSizeMb)
|
|
assert.Equal(t, int64(0), entry.RawSizeMb)
|
|
|
|
encoded, err := json.Marshal(entry)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, string(encoded), "rawSizeMb")
|
|
assert.NotContains(t, string(encoded), "backupSizeMb")
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenPhysicalFullBackupLookupFails_ReturnsError(t *testing.T) {
|
|
db := physicalDatabase("pg-physical", postgresql_physical.BackupTypeFullOnly, availableStatus())
|
|
lookupErr := errors.New("physical query exploded")
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTestWith(
|
|
t,
|
|
&fakeDatabaseLister{databases: []*databases.Database{db}},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakePhysicalFullBackupSizer{err: lookupErr},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
&fakeUserCounter{},
|
|
sender,
|
|
)
|
|
|
|
err := service.BuildAndSend(context.Background())
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, lookupErr)
|
|
assert.Empty(t, sender.calls)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenUsersCounted_PopulatesUserCount(t *testing.T) {
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTestWith(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakePhysicalFullBackupSizer{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
&fakeUserCounter{count: 7},
|
|
sender,
|
|
)
|
|
|
|
require.NoError(t, service.BuildAndSend(context.Background()))
|
|
require.Len(t, sender.calls, 1)
|
|
assert.Equal(t, 7, sender.calls[0].UserCount)
|
|
}
|
|
|
|
func Test_BuildAndSend_WhenUserCounterFails_ReturnsError(t *testing.T) {
|
|
countErr := errors.New("users count exploded")
|
|
|
|
sender := &fakeSender{}
|
|
service := newServiceUnderTestWith(
|
|
t,
|
|
&fakeDatabaseLister{},
|
|
&fakeStorageLister{},
|
|
&fakeNotifierLister{},
|
|
&fakeBackupChecker{},
|
|
&fakePhysicalFullBackupSizer{},
|
|
&fakeVerificationAgentLister{},
|
|
&fakeVerificationConfigLister{},
|
|
&fakeUserCounter{err: countErr},
|
|
sender,
|
|
)
|
|
|
|
err := service.BuildAndSend(context.Background())
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, countErr)
|
|
assert.Empty(t, sender.calls)
|
|
}
|
|
|
|
func writeFileForTest(path string) error {
|
|
return os.WriteFile(path, []byte("x"), 0o600)
|
|
}
|