package backup import ( "context" "os" "strings" "testing" "time" "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/config" "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/keymanager" "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/mlkem768" "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/x25519" "git.tswf.io/infra/go-synapse-backupper/pkg/domain/crypto" ) // TestRunOnce_MissingKeys verifies that RunOnce surfaces a configuration error // when the public key paths are not provided, exercising the early validation // path (registry build, keymanager construction, key load). func TestRunOnce_MissingKeys(t *testing.T) { cfg := &config.Config{} // PQPublicKeyPath and ClassicalPublicKeyPath intentionally left empty. err := RunOnce(context.Background(), cfg) if err == nil { t.Fatal("RunOnce returned nil error with empty config, want error") } if !strings.Contains(err.Error(), "load pq public key") { t.Fatalf("RunOnce error = %q, want it to mention %q", err, "load pq public key") } } // TestRunOnce_PQKeyNotFound verifies the error returned when the post-quantum // public key path is set but the file does not exist on disk. func TestRunOnce_PQKeyNotFound(t *testing.T) { cfg := &config.Config{ PQScheme: 0x0006, // Path points to a file that does not exist; use t.TempDir() to keep // the test hermetic regardless of the working directory. PQPublicKeyPath: t.TempDir() + "/does-not-exist-pq.pem", ClassicalPublicKeyPath: t.TempDir() + "/does-not-exist-classical.pem", } err := RunOnce(context.Background(), cfg) if err == nil { t.Fatal("RunOnce returned nil error when keys are missing on disk, want error") } if !strings.Contains(err.Error(), "load pq public key") { t.Fatalf("RunOnce error = %q, want it to mention %q", err, "load pq public key") } } // TestRunOnce_ValidKeys_PgDumpMissing generates real dual-KEM keys, builds a // fully valid config, and calls RunOnce. Since pg_dump is not installed in the // test environment, the pipeline fails at the dump step, giving coverage of // the full orchestration path (registry, keymanager, encryptor, sink, runner, // pgDumpOpts assembly) while still asserting the expected error. func TestRunOnce_ValidKeys_PgDumpMissing(t *testing.T) { dir := t.TempDir() keyDir := dir + "/keys" backupDir := dir + "/backups" _ = os.MkdirAll(keyDir, 0o755) _ = os.MkdirAll(backupDir, 0o755) reg := crypto.NewRegistry() _ = reg.Register(0x0006, func() crypto.KEM { return mlkem768.New() }) _ = reg.Register(0x0007, func() crypto.KEM { return x25519.New() }) km := keymanager.NewKeyManager(reg) pqPubFile, _ := os.Create(keyDir + "/pq.pub.pem") pqPrivFile, _ := os.Create(keyDir + "/pq.priv.pem") _ = km.Generate(0x0006, pqPubFile, pqPrivFile, nil) _ = pqPubFile.Close() _ = pqPrivFile.Close() classicalPubFile, _ := os.Create(keyDir + "/classical.pub.pem") classicalPrivFile, _ := os.Create(keyDir + "/classical.priv.pem") _ = km.Generate(0x0007, classicalPubFile, classicalPrivFile, nil) _ = classicalPubFile.Close() _ = classicalPrivFile.Close() cfg := &config.Config{ PQScheme: 0x0006, ClassicalScheme: 0x0007, PQPublicKeyPath: keyDir + "/pq.pub.pem", ClassicalPublicKeyPath: keyDir + "/classical.pub.pem", Backup: struct { Dir string `mapstructure:"dir"` RetentionDays int `mapstructure:"retention_days"` Cron string `mapstructure:"cron"` }{ Dir: backupDir, RetentionDays: 180, }, PG: struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` User string `mapstructure:"user"` Password string `mapstructure:"password"` Database string `mapstructure:"database"` SSLMode string `mapstructure:"sslmode"` ExcludeTables []string `mapstructure:"exclude_tables"` }{ Host: "localhost", Port: 5432, User: "test", Password: "test", Database: "test", ExcludeTables: []string{"e2e_one_time_keys_json"}, }, } err := RunOnce(context.Background(), cfg) if err == nil { t.Fatal("expected error because pg_dump is not installed in the test environment") } if !strings.Contains(err.Error(), "backup pipeline failed") { t.Fatalf("expected error containing 'backup pipeline failed', got: %v", err) } entries, _ := os.ReadDir(backupDir) for _, e := range entries { if strings.HasSuffix(e.Name(), ".tmp") { t.Fatalf("unexpected .tmp file after failed backup: %s", e.Name()) } } } func TestArtifactKey(t *testing.T) { ts := time.Date(2025, 1, 2, 15, 4, 5, 0, time.UTC) key := ArtifactKey(ts) wantPrefix := "synapse-20250102-150405-" if !strings.HasPrefix(key, wantPrefix) { t.Errorf("key = %q, want prefix %q", key, wantPrefix) } if !strings.HasSuffix(key, ".dump.pqenc") { t.Errorf("key = %q, want suffix .dump.pqenc", key) } secondKey := ArtifactKey(ts) if key == secondKey { t.Errorf("two keys for the same timestamp collide: %q", key) } }