Files
mopac-keyproxy/internal/backend/envfile_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

117 lines
2.7 KiB
Go

package backend
import (
"strings"
"testing"
)
func TestParseEnvFile(t *testing.T) {
tests := []struct {
name string
in string
want map[string]string
wantErr string
}{
{
name: "simple lines",
in: "A=1\nB=two words\n",
want: map[string]string{"A": "1", "B": "two words"},
},
{
name: "comments and blanks",
in: "# header\n\nA=1\n # indented comment\nB=2\n",
want: map[string]string{"A": "1", "B": "2"},
},
{
name: "export prefix",
in: "export A=1\nexport\tB=2\n",
want: map[string]string{"A": "1", "B": "2"},
},
{
name: "quoted values stripped",
in: "A=\"1 2\"\nB='3 4'\n",
want: map[string]string{"A": "1 2", "B": "3 4"},
},
{
name: "spaces around key and value trimmed",
in: " A = 1 \n",
want: map[string]string{"A": "1"},
},
{
name: "inline comment after value",
in: "A=1 # trailing\nB=\"2 # kept\"\n",
want: map[string]string{"A": "1", "B": "2 # kept"},
},
{
name: "equals inside value",
in: "A=b=c\n",
want: map[string]string{"A": "b=c"},
},
{
name: "crlf line endings",
in: "A=1\r\nB=2\r\n",
want: map[string]string{"A": "1", "B": "2"},
},
{
name: "later duplicate wins",
in: "A=first\nA=second\n",
want: map[string]string{"A": "second"},
},
{
name: "line without equals",
in: "SECRET_MATERIAL_NO_EQUALS\n",
wantErr: "line 1: malformed",
},
{
name: "illegal key character",
in: "A-B=supersecret\n",
wantErr: "line 1: malformed",
},
{
name: "key starting with digit",
in: "1KEY=supersecret\n",
wantErr: "line 1: malformed",
},
{
name: "empty key",
in: "=value\n",
wantErr: "line 1: malformed",
},
{
name: "line number reported",
in: "A=1\nB=2\nbroken line here\n",
wantErr: "line 3: malformed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseEnvFile([]byte(tt.in))
if tt.wantErr != "" {
if err == nil {
t.Fatalf("want error %q, got nil (parsed %v)", tt.wantErr, got)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("error %q does not contain %q", err, tt.wantErr)
}
// Parse errors carry line numbers only — never line
// contents (contents may be material).
if strings.Contains(err.Error(), "supersecret") || strings.Contains(err.Error(), "SECRET_MATERIAL") {
t.Fatalf("parse error leaks line contents: %q", err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got) != len(tt.want) {
t.Fatalf("got %v, want %v", got, tt.want)
}
for k, v := range tt.want {
if got[k] != v {
t.Fatalf("key %s: got %q, want %q", k, got[k], v)
}
}
})
}
}