package app import ( "context" "errors" "os" "path/filepath" "strings" "testing" "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" "gitea.maximumdirect.net/eric/narratio/internal/config" ) func TestNewCommandObjectStoreLoadsSecretsBeforeFactory(t *testing.T) { accessKeyEnv := "NARRATIO_TEST_COMMAND_STORE_KEY_ID" secretKeyEnv := "NARRATIO_TEST_COMMAND_STORE_SECRET" restoreEnvAfterTest(t, accessKeyEnv, secretKeyEnv) secretsDir := t.TempDir() mustWriteSecretFile(t, filepath.Join(secretsDir, accessKeyEnv), "loaded-key-id\n") mustWriteSecretFile(t, filepath.Join(secretsDir, secretKeyEnv), "loaded-secret\n") cfg := commandObjectStoreTestConfig(secretsDir) fake := &storage.FakeBackend{} called := false origStoreFn := newObjectStoreFromConfigFn newObjectStoreFromConfigFn = func(context.Context, *config.Config) (storage.ObjectStore, error) { called = true if got := os.Getenv(accessKeyEnv); got != "loaded-key-id" { return nil, errors.New("access key was not loaded before object store init") } if got := os.Getenv(secretKeyEnv); got != "loaded-secret" { return nil, errors.New("secret key was not loaded before object store init") } return fake, nil } t.Cleanup(func() { newObjectStoreFromConfigFn = origStoreFn }) store, err := newCommandObjectStore(context.Background(), cfg, nil) if err != nil { t.Fatalf("newCommandObjectStore() error = %v", err) } if store != fake { t.Fatalf("store = %#v, want fake backend", store) } if !called { t.Fatal("object store factory was not called") } } func TestNewCommandObjectStorePreservesExistingEnv(t *testing.T) { accessKeyEnv := "NARRATIO_TEST_COMMAND_STORE_EXISTING_KEY_ID" secretKeyEnv := "NARRATIO_TEST_COMMAND_STORE_EXISTING_SECRET" t.Setenv(accessKeyEnv, "existing-key-id") t.Setenv(secretKeyEnv, "existing-secret") secretsDir := t.TempDir() mustWriteSecretFile(t, filepath.Join(secretsDir, accessKeyEnv), "file-key-id\n") mustWriteSecretFile(t, filepath.Join(secretsDir, secretKeyEnv), "file-secret\n") cfg := commandObjectStoreTestConfig(secretsDir) origStoreFn := newObjectStoreFromConfigFn newObjectStoreFromConfigFn = func(context.Context, *config.Config) (storage.ObjectStore, error) { if got := os.Getenv(accessKeyEnv); got != "existing-key-id" { return nil, errors.New("existing access key was overwritten") } if got := os.Getenv(secretKeyEnv); got != "existing-secret" { return nil, errors.New("existing secret key was overwritten") } return &storage.FakeBackend{}, nil } t.Cleanup(func() { newObjectStoreFromConfigFn = origStoreFn }) if _, err := newCommandObjectStore(context.Background(), cfg, nil); err != nil { t.Fatalf("newCommandObjectStore() error = %v", err) } } func TestNewCommandObjectStoreSecretErrorStopsFactory(t *testing.T) { cfg := commandObjectStoreTestConfig(filepath.Join(t.TempDir(), "missing")) called := false origStoreFn := newObjectStoreFromConfigFn newObjectStoreFromConfigFn = func(context.Context, *config.Config) (storage.ObjectStore, error) { called = true return &storage.FakeBackend{}, nil } t.Cleanup(func() { newObjectStoreFromConfigFn = origStoreFn }) _, err := newCommandObjectStore(context.Background(), cfg, nil) if err == nil { t.Fatal("expected error, got nil") } if called { t.Fatal("object store factory was called after secret load failure") } if !strings.Contains(err.Error(), "load secrets from files") { t.Fatalf("error = %q, want secret loading context", err.Error()) } } func TestNewCommandObjectStoreFactoryErrorIsContextual(t *testing.T) { cfg := commandObjectStoreTestConfig("") origStoreFn := newObjectStoreFromConfigFn newObjectStoreFromConfigFn = func(context.Context, *config.Config) (storage.ObjectStore, error) { return nil, errors.New("factory boom") } t.Cleanup(func() { newObjectStoreFromConfigFn = origStoreFn }) _, err := newCommandObjectStore(context.Background(), cfg, nil) if err == nil { t.Fatal("expected error, got nil") } if !strings.Contains(err.Error(), "initialize object store backend") || !strings.Contains(err.Error(), "factory boom") { t.Fatalf("error = %q, want factory context", err.Error()) } } func commandObjectStoreTestConfig(secretsDir string) *config.Config { cfg := &config.Config{ Pipeline: &config.PipelineConfig{ Storage: config.StorageConfig{ Backend: "s3", S3: &config.StorageS3Config{ Bucket: "test-bucket", AccessKeyIDEnv: "NARRATIO_TEST_COMMAND_STORE_KEY_ID", SecretKeyEnv: "NARRATIO_TEST_COMMAND_STORE_SECRET", }, }, }, } if strings.TrimSpace(secretsDir) != "" { cfg.Pipeline.Secrets = &config.SecretsConfig{EnvDir: secretsDir} } return cfg }