package domainsecrets import ( "context" "fmt" "os/exec" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/bnema/gordon/internal/domain" ) func passCmd(args ...string) error { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() return exec.CommandContext(ctx, "pass", args...).Run() } func requirePass(t *testing.T) { if err := passCmd("version"); err != nil { t.Skip("pass not available") } if err := passCmd("ls"); err != nil { t.Skip("pass store not initialized") } } func cleanupPassDomain(_ *testing.T, domainName string, keys []string) { safeDomain, err := domain.SanitizeDomainForEnvFile(domainName) if err != nil { return } for _, key := range keys { path := fmt.Sprintf("%s/%s/%s", PassDomainSecretsPath, safeDomain, key) _ = passCmd("rm", "-f", path) } manifestPath := fmt.Sprintf("%s/%s/.keys", PassDomainSecretsPath, safeDomain) _ = passCmd("rm", "-f", manifestPath) } func TestPassStore_SetGetDelete(t *testing.T) { requirePass(t) store, err := NewPassStore(testLogger()) require.NoError(t, err) domainName := fmt.Sprintf("test.%d.example.com", time.Now().UnixNano()) keys := []string{"API_KEY", "DB_PASSWORD"} defer cleanupPassDomain(t, domainName, keys) secretsMap := map[string]string{ "API_KEY": "alpha", "DB_PASSWORD": "bravo", } err = store.Set(domainName, secretsMap) require.NoError(t, err) keysList, err := store.ListKeys(domainName) require.NoError(t, err) assert.Len(t, keysList, 2) assert.ElementsMatch(t, keys, keysList) values, err := store.GetAll(domainName) require.NoError(t, err) assert.Equal(t, "alpha", values["API_KEY"]) assert.Equal(t, "bravo", values["DB_PASSWORD"]) err = store.Delete(domainName, "DB_PASSWORD") require.NoError(t, err) keysList, err = store.ListKeys(domainName) require.NoError(t, err) assert.ElementsMatch(t, []string{"API_KEY"}, keysList) } func TestPassStore_SetGetAttachment(t *testing.T) { requirePass(t) store, err := NewPassStore(testLogger()) require.NoError(t, err) containerName := fmt.Sprintf("gitea-postgres-%d", time.Now().UnixNano()) keys := []string{"POSTGRES_USER", "POSTGRES_PASSWORD"} defer CleanupPassAttachment(t, containerName, keys) secretsMap := map[string]string{ "POSTGRES_USER": "gitea", "POSTGRES_PASSWORD": "secret123", } err = store.SetAttachment(containerName, secretsMap) require.NoError(t, err) values, err := store.GetAllAttachment(containerName) require.NoError(t, err) assert.Equal(t, "gitea", values["POSTGRES_USER"]) assert.Equal(t, "secret123", values["POSTGRES_PASSWORD"]) } func TestPassStore_ListAttachmentKeys_AfterSet(t *testing.T) { requirePass(t) store, err := NewPassStore(testLogger()) require.NoError(t, err) containerName := fmt.Sprintf("redis-cache-%d", time.Now().UnixNano()) keys := []string{"REDIS_PASSWORD"} defer CleanupPassAttachment(t, containerName, keys) secretsMap := map[string]string{ "REDIS_PASSWORD": "redis123", } err = store.SetAttachment(containerName, secretsMap) require.NoError(t, err) values, err := store.GetAllAttachment(containerName) require.NoError(t, err) assert.Len(t, values, 1) assert.Equal(t, "redis123", values["REDIS_PASSWORD"]) } func TestParsePassListOutput(t *testing.T) { tests := []struct { name string basePath string output string want []passListEntry }{ { name: "simple tree", basePath: "domain", output: "domain\n├── key1\n└── key2", want: []passListEntry{{name: "key1", depth: 1}, {name: "key2", depth: 1}}, }, { name: "nested tree", basePath: "domain", output: "domain\n│ ├── subkey1\n│ └── subkey2\n└── key1", want: []passListEntry{{name: "subkey1", depth: 2}, {name: "subkey2", depth: 2}, {name: "key1", depth: 1}}, }, { name: "empty output", basePath: "domain", output: "", want: []passListEntry{}, }, { name: "ASCII fallback chars", basePath: "domain", output: "domain\n| |-- subkey1\n`-- key1", want: []passListEntry{{name: "subkey1", depth: 2}, {name: "key1", depth: 1}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := parsePassListOutput(tt.basePath, tt.output) assert.Equal(t, tt.want, got) }) } }