Files
wild-cloud/api/internal/backup/strategies/postgres_test.go
Paul Payne dd0166b5d5 chore: bump to 0.2.0
XDG instance layout, multi-file config editor, talosconfig renewal,
node upgrade/rollback CLI, operation cancellation, app restart, and
removal of now-unneeded migration code.
2026-06-21 09:12:32 +00:00

295 lines
6.5 KiB
Go

package strategies
import (
"bytes"
"io"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
btypes "github.com/wild-cloud/wild-central/daemon/internal/backup/types"
)
// MockPostgresDestination for testing
type MockPostgresDestination struct {
putData map[string][]byte
getData map[string][]byte
putError error
getError error
deleteKeys []string
}
func NewMockPostgresDestination() *MockPostgresDestination {
return &MockPostgresDestination{
putData: make(map[string][]byte),
getData: make(map[string][]byte),
}
}
func (m *MockPostgresDestination) Put(key string, reader io.Reader) (int64, error) {
if m.putError != nil {
return 0, m.putError
}
data, err := io.ReadAll(reader)
if err != nil {
return 0, err
}
m.putData[key] = data
return int64(len(data)), nil
}
func (m *MockPostgresDestination) Get(key string) (io.ReadCloser, error) {
if m.getError != nil {
return nil, m.getError
}
data, exists := m.getData[key]
if !exists {
data = []byte("mock backup data")
}
return io.NopCloser(bytes.NewReader(data)), nil
}
func (m *MockPostgresDestination) Delete(key string) error {
m.deleteKeys = append(m.deleteKeys, key)
return nil
}
func (m *MockPostgresDestination) List(prefix string) ([]btypes.BackupObject, error) {
var objects []btypes.BackupObject
for key, data := range m.putData {
if strings.HasPrefix(key, prefix) {
objects = append(objects, btypes.BackupObject{
Key: key,
Size: int64(len(data)),
LastModified: time.Now(),
})
}
}
return objects, nil
}
func (m *MockPostgresDestination) GetURL(key string, expiry time.Duration) (string, error) {
return "https://mock.example.com/" + key, nil
}
func (m *MockPostgresDestination) Type() string {
return "mock"
}
func TestPostgreSQLStrategy_Name(t *testing.T) {
s := NewPostgreSQLStrategy("/tmp")
assert.Equal(t, "postgres", s.Name())
}
func TestPostgreSQLStrategy_Verify(t *testing.T) {
s := NewPostgreSQLStrategy("/tmp")
tests := []struct {
name string
plan *btypes.RecoveryPlan
destData map[string][]byte
expectError bool
}{
{
name: "successful verification with PGDMP header",
plan: &btypes.RecoveryPlan{
App: "test-app",
Instance: "test-instance",
Strategies: []btypes.StrategyEntry{
{
Name: "postgres",
Status: "backed_up",
Backup: map[string]interface{}{
"location": "test/backup.dump",
},
},
},
},
destData: map[string][]byte{
"test/backup.dump": []byte("PGDMP\x00\x00\x00\x00"),
},
expectError: false,
},
{
name: "invalid dump format",
plan: &btypes.RecoveryPlan{
App: "test-app",
Instance: "test-instance",
Strategies: []btypes.StrategyEntry{
{
Name: "postgres",
Status: "backed_up",
Backup: map[string]interface{}{
"location": "test/invalid.dump",
},
},
},
},
destData: map[string][]byte{
"test/invalid.dump": []byte("not a valid dump"),
},
expectError: true,
},
{
name: "missing backup location in plan",
plan: &btypes.RecoveryPlan{
App: "test-app",
Instance: "test-instance",
Strategies: []btypes.StrategyEntry{
{
Name: "postgres",
Status: "backed_up",
Backup: map[string]interface{}{},
},
},
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dest := NewMockPostgresDestination()
dest.getData = tt.destData
err := s.Verify(tt.plan, dest)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
// writeAppConfig writes a per-app config file at config/apps/{appName}/config.yaml.
func writeAppConfig(t *testing.T, tmpDir, instanceName, appName, config string) {
t.Helper()
appConfigDir := filepath.Join(tmpDir, "instances", instanceName, "config", "apps", appName)
err := os.MkdirAll(appConfigDir, 0755)
assert.NoError(t, err)
err = os.WriteFile(filepath.Join(appConfigDir, "config.yaml"), []byte(config), 0644)
assert.NoError(t, err)
}
func TestPostgreSQLStrategy_GetDatabaseName(t *testing.T) {
tests := []struct {
name string
config string
appName string
expected string
}{
{
name: "flat dbName key",
config: "dbName: my_database\n",
appName: "myapp",
expected: "my_database",
},
{
name: "nested db.name key",
config: `namespace: e2e-test-app
db:
host: postgres
name: e2e_test_app
user: e2e_test_app
`,
appName: "e2e-test-app",
expected: "e2e_test_app",
},
{
name: "flat key takes precedence over nested",
config: `dbName: flat_name
db:
name: nested_name
`,
appName: "myapp",
expected: "flat_name",
},
{
name: "no config falls back to appName",
config: "namespace: myapp\n",
appName: "myapp",
expected: "myapp",
},
{
name: "missing app falls back to appName",
config: "",
appName: "missing-app",
expected: "missing-app",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
if tt.config != "" {
writeAppConfig(t, tmpDir, "test-instance", tt.appName, tt.config)
}
s := &PostgreSQLStrategy{dataDir: tmpDir}
result := s.getDatabaseName("test-instance", tt.appName)
assert.Equal(t, tt.expected, result)
})
}
}
func TestPostgreSQLStrategy_GetAppUser(t *testing.T) {
tests := []struct {
name string
config string
appName string
expected string
}{
{
name: "flat dbUser key",
config: "dbUser: my_user\n",
appName: "myapp",
expected: "my_user",
},
{
name: "flat dbUsername key",
config: "dbUsername: my_username\n",
appName: "myapp",
expected: "my_username",
},
{
name: "nested db.user key",
config: `namespace: e2e-test-app
db:
host: postgres
name: e2e_test_app
user: e2e_test_app
`,
appName: "e2e-test-app",
expected: "e2e_test_app",
},
{
name: "flat key takes precedence over nested",
config: `dbUser: flat_user
db:
user: nested_user
`,
appName: "myapp",
expected: "flat_user",
},
{
name: "no user config falls back to appName",
config: "namespace: myapp\n",
appName: "myapp",
expected: "myapp",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
writeAppConfig(t, tmpDir, "test-instance", tt.appName, tt.config)
s := &PostgreSQLStrategy{dataDir: tmpDir}
result := s.getAppUser("test-instance", tt.appName)
assert.Equal(t, tt.expected, result)
})
}
}