feat(vault-tui): implement KV client and service for managing secrets
- Added internal/vault/client.go for creating a Vault client with configuration settings. - Introduced internal/vault/errors.go to classify Vault API errors for better UI handling. - Created internal/vault/kv.go to manage KV secrets, including listing, reading, writing, and deleting operations. - Implemented internal/vault/mounts.go to list and describe secret engine mounts. - Developed internal/vault/service.go to provide a unified entry point for Vault operations. - Added internal/vault/kv_test.go for comprehensive testing of KV operations. - Introduced internal/ui/toast.go for transient notifications in the UI. - Added renovate.json for dependency management and updates.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func envFrom(m map[string]string) Getenv {
|
||||
return func(k string) (string, bool) {
|
||||
v, ok := m[k]
|
||||
return v, ok
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePrecedence_Address(t *testing.T) {
|
||||
f := &File{
|
||||
Version: SchemaVersion,
|
||||
CurrentProfile: "p1",
|
||||
Defaults: Profile{Address: "https://defaults.example.com"},
|
||||
Profiles: map[string]*Profile{
|
||||
"p1": {Address: "https://profile.example.com"},
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
ov Overrides
|
||||
env map[string]string
|
||||
want string
|
||||
}{
|
||||
{"builtin only", Overrides{}, nil, "https://profile.example.com"}, // profile beats defaults
|
||||
{"env beats profile", Overrides{}, map[string]string{EnvAddress: "https://env.example.com"}, "https://env.example.com"},
|
||||
{"flag beats env", Overrides{Address: "https://flag.example.com"}, map[string]string{EnvAddress: "https://env.example.com"}, "https://flag.example.com"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
s, err := Resolve(f, "", c.ov, envFrom(c.env))
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if s.Address != c.want {
|
||||
t.Errorf("Address = %q, want %q", s.Address, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_DefaultsOnlyWhenNoProfile(t *testing.T) {
|
||||
f := &File{Version: SchemaVersion, Defaults: Profile{Address: "https://defaults.example.com"}}
|
||||
s, err := Resolve(f, "", Overrides{}, envFrom(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if s.Address != "https://defaults.example.com" {
|
||||
t.Errorf("Address = %q, want defaults value", s.Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_BuiltinFallback(t *testing.T) {
|
||||
f := &File{Version: SchemaVersion}
|
||||
s, err := Resolve(f, "", Overrides{}, envFrom(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if s.Address != "https://127.0.0.1:8200" {
|
||||
t.Errorf("Address = %q, want builtin default", s.Address)
|
||||
}
|
||||
if s.OriginOf("address").Layer != LayerBuiltin {
|
||||
t.Errorf("origin layer = %q, want %q", s.OriginOf("address").Layer, LayerBuiltin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_IgnoreEnvPerProfile(t *testing.T) {
|
||||
ignore := true
|
||||
f := &File{
|
||||
Version: SchemaVersion,
|
||||
Profiles: map[string]*Profile{
|
||||
"staging": {Address: "https://staging.example.com", IgnoreEnv: &ignore},
|
||||
},
|
||||
}
|
||||
s, err := Resolve(f, "staging", Overrides{}, envFrom(map[string]string{EnvAddress: "https://env.example.com"}))
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if s.Address != "https://staging.example.com" {
|
||||
t.Errorf("Address = %q, want profile value (env should be ignored)", s.Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_NoEnvOverrideFlag(t *testing.T) {
|
||||
f := &File{
|
||||
Version: SchemaVersion,
|
||||
Profiles: map[string]*Profile{
|
||||
"p": {Address: "https://profile.example.com"},
|
||||
},
|
||||
}
|
||||
s, err := Resolve(f, "p", Overrides{NoEnv: true}, envFrom(map[string]string{EnvAddress: "https://env.example.com"}))
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if s.Address != "https://profile.example.com" {
|
||||
t.Errorf("Address = %q, want profile value (--no-env should suppress VAULT_ADDR)", s.Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectProfile(t *testing.T) {
|
||||
multi := &File{Profiles: map[string]*Profile{"a": {}, "b": {}}}
|
||||
if _, err := SelectProfile(multi, "", envFrom(nil)); err == nil {
|
||||
t.Error("expected error selecting among multiple profiles with no selection")
|
||||
}
|
||||
if name, err := SelectProfile(multi, "a", envFrom(nil)); err != nil || name != "a" {
|
||||
t.Errorf("flag selection: got (%q, %v)", name, err)
|
||||
}
|
||||
if name, err := SelectProfile(multi, "", envFrom(map[string]string{EnvTUIProfile: "b"})); err != nil || name != "b" {
|
||||
t.Errorf("env selection: got (%q, %v)", name, err)
|
||||
}
|
||||
|
||||
single := &File{Profiles: map[string]*Profile{"only": {}}}
|
||||
if name, err := SelectProfile(single, "", envFrom(nil)); err != nil || name != "only" {
|
||||
t.Errorf("sole profile selection: got (%q, %v)", name, err)
|
||||
}
|
||||
|
||||
empty := &File{}
|
||||
if name, err := SelectProfile(empty, "", envFrom(nil)); err != nil || name != "default" {
|
||||
t.Errorf("empty profiles: got (%q, %v)", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve_ReadOnlyDefaultsSafe(t *testing.T) {
|
||||
f := &File{Version: SchemaVersion}
|
||||
s, err := Resolve(f, "", Overrides{}, envFrom(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if !s.ReadOnly {
|
||||
t.Error("ReadOnly should default to true when nothing overrides it")
|
||||
}
|
||||
|
||||
no := false
|
||||
s, err = Resolve(f, "", Overrides{ReadOnly: &no}, envFrom(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve: %v", err)
|
||||
}
|
||||
if s.ReadOnly {
|
||||
t.Error("--write (ReadOnly=false override) should have taken effect")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user