Files
mopac-keyproxy/internal/backend/env_test.go
T
mrcharles b43bc55963 Add backend resolver interface with file and env backends plus phase-3 stubs
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
2026-08-28 22:30:50 -05:00

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")
}
}