Introduce the single Backend interface every credential source implements.
v0 ships two working backends: file (0600 KEY=VALUE env files, parsed in
pure Go, never sourced; looser permission masks refused before read) and
env (process-environment indirection). Bitwarden Secrets Manager and
HashiCorp Vault ship as explicit not-implemented stubs behind the same
interface so the phase-3 connectors are drop-ins. All failures are typed
ResolveErrors carrying only the ref, backend, and a fixed reason enum —
never material.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
103 lines
2.6 KiB
Go
103 lines
2.6 KiB
Go
package backend
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestEnvResolve(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
set func(t *testing.T)
|
|
ref Ref
|
|
want string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "set variable resolves",
|
|
set: func(t *testing.T) { t.Setenv("KEYPROXY_TEST_VAR", testMaterial) },
|
|
ref: Ref{Name: "mpk-env", Backend: "env", Source: "KEYPROXY_TEST_VAR"},
|
|
want: testMaterial,
|
|
},
|
|
{
|
|
name: "unset variable",
|
|
set: func(t *testing.T) {},
|
|
ref: Ref{Name: "mpk-env", Backend: "env", Source: "KEYPROXY_TEST_UNSET"},
|
|
wantErr: "missing_key",
|
|
},
|
|
{
|
|
name: "empty variable",
|
|
set: func(t *testing.T) { t.Setenv("KEYPROXY_TEST_EMPTY", "") },
|
|
ref: Ref{Name: "mpk-env", Backend: "env", Source: "KEYPROXY_TEST_EMPTY"},
|
|
wantErr: "empty_value",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.set(t)
|
|
e := NewEnv()
|
|
got, err := e.Resolve(context.Background(), tt.ref)
|
|
if tt.wantErr != "" {
|
|
if err == nil {
|
|
t.Fatalf("want error %q, got value", tt.wantErr)
|
|
}
|
|
msg := err.Error()
|
|
if !strings.Contains(msg, tt.wantErr) {
|
|
t.Fatalf("error %q does not contain %q", msg, tt.wantErr)
|
|
}
|
|
assertRedactedError(t, msg, tt.ref.Name, "env", testMaterial)
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got != tt.want {
|
|
t.Fatalf("got %q, want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStubsNotImplemented(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
backend Backend
|
|
bname string
|
|
}{
|
|
{"bitwarden", NewBitwarden(), "bitwarden"},
|
|
{"vault", NewVault(), "vault"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ref := Ref{Name: "mpk-stub", Backend: tt.bname, Source: "sm://x", Key: "K"}
|
|
got, err := tt.backend.Resolve(context.Background(), ref)
|
|
if err == nil {
|
|
t.Fatalf("stub resolved to %q; want explicit not-implemented error", got)
|
|
}
|
|
if got != "" {
|
|
t.Fatalf("stub returned non-empty value %q", got)
|
|
}
|
|
msg := err.Error()
|
|
for _, want := range []string{"mpk-stub", tt.bname, "not_implemented"} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Fatalf("stub error %q must contain %q", msg, want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistry(t *testing.T) {
|
|
r := NewRegistry(NewFile(), NewEnv(), NewBitwarden(), NewVault())
|
|
if got := r.Names(); strings.Join(got, ",") != "bitwarden,env,file,vault" {
|
|
t.Fatalf("names = %v", got)
|
|
}
|
|
if _, ok := r.Get("nope"); ok {
|
|
t.Fatal("unknown backend must not resolve")
|
|
}
|
|
if b, ok := r.Get("file"); !ok || b.Name() != "file" {
|
|
t.Fatal("file backend must resolve")
|
|
}
|
|
}
|