package stacks import ( "io" "log" "os" "path/filepath" "regexp" "testing" ) // newSecretGenManager builds a Manager with one stack whose .felhom.yml declares the O4 test // fields: a generatable resettable secret, a data-key, and a spec-less secret. func newSecretGenManager(t *testing.T) *Manager { t.Helper() stackDir := filepath.Join(t.TempDir(), "app") if err := os.MkdirAll(stackDir, 0755); err != nil { t.Fatal(err) } meta := `display_name: App deploy_fields: - env_var: DB_PASSWORD type: secret generate: "password:24" - env_var: SECRET_KEY type: secret generate: "hex:32" data_key: true - env_var: ADMIN_TOKEN type: secret - env_var: SUBDOMAIN type: subdomain default: app ` if err := os.WriteFile(filepath.Join(stackDir, ".felhom.yml"), []byte(meta), 0644); err != nil { t.Fatal(err) } return &Manager{ logger: log.New(io.Discard, "", 0), stacks: map[string]*Stack{ "app": {Name: "app", ComposePath: filepath.Join(stackDir, "docker-compose.yml")}, }, } } // TestGenerateSecretForField covers the O4 generator seam's contract: spec-conformant values for // resettable secrets, and REFUSAL for data-keys (frozen fail-closed territory), spec-less fields, // non-secret fields, and unknown stacks/vars. func TestGenerateSecretForField(t *testing.T) { m := newSecretGenManager(t) t.Run("resettable secret with spec → spec-conformant value", func(t *testing.T) { v, ok := m.GenerateSecretForField("app", "DB_PASSWORD") if !ok { t.Fatal("expected generation for DB_PASSWORD (generate: password:24)") } if len(v) != 24 || !regexp.MustCompile(`^[A-Za-z0-9]+$`).MatchString(v) { t.Errorf("value does not conform to password:24 (len=%d)", len(v)) } // Distinct per call (crypto/rand-backed, not a constant). if v2, _ := m.GenerateSecretForField("app", "DB_PASSWORD"); v2 == v { t.Error("two generations returned the same value") } }) t.Run("data-key → REFUSED even with a generate spec", func(t *testing.T) { if v, ok := m.GenerateSecretForField("app", "SECRET_KEY"); ok || v != "" { t.Error("a data-encrypting key must NEVER be generated") } }) t.Run("no generate spec → refused", func(t *testing.T) { if _, ok := m.GenerateSecretForField("app", "ADMIN_TOKEN"); ok { t.Error("spec-less secret must not be generated (Scenario G: proceed-with-warn instead)") } }) t.Run("non-secret field / unknown var / unknown stack → refused", func(t *testing.T) { if _, ok := m.GenerateSecretForField("app", "SUBDOMAIN"); ok { t.Error("non-secret field must not be generated") } if _, ok := m.GenerateSecretForField("app", "NOPE"); ok { t.Error("unknown env var must not be generated") } if _, ok := m.GenerateSecretForField("ghost", "DB_PASSWORD"); ok { t.Error("unknown stack must not be generated") } }) }