1package domainsecrets
2
3import (
4 "context"
5 "fmt"
6 "os/exec"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12
13 "github.com/bnema/gordon/internal/domain"
14)
15
16func passCmd(args ...string) error {
17 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
18 defer cancel()
19 return exec.CommandContext(ctx, "pass", args...).Run()
20}
21
22func requirePass(t *testing.T) {
23 if err := passCmd("version"); err != nil {
24 t.Skip("pass not available")
25 }
26 if err := passCmd("ls"); err != nil {
27 t.Skip("pass store not initialized")
28 }
29}
30
31func cleanupPassDomain(_ *testing.T, domainName string, keys []string) {
32 safeDomain, err := domain.SanitizeDomainForEnvFile(domainName)
33 if err != nil {
34 return
35 }
36
37 for _, key := range keys {
38 path := fmt.Sprintf("%s/%s/%s", PassDomainSecretsPath, safeDomain, key)
39 _ = passCmd("rm", "-f", path)
40 }
41
42 manifestPath := fmt.Sprintf("%s/%s/.keys", PassDomainSecretsPath, safeDomain)
43 _ = passCmd("rm", "-f", manifestPath)
44}
45
46func TestPassStore_SetGetDelete(t *testing.T) {
47 requirePass(t)
48
49 store, err := NewPassStore(testLogger())
50 require.NoError(t, err)
51
52 domainName := fmt.Sprintf("test.%d.example.com", time.Now().UnixNano())
53 keys := []string{"API_KEY", "DB_PASSWORD"}
54 defer cleanupPassDomain(t, domainName, keys)
55
56 secretsMap := map[string]string{
57 "API_KEY": "alpha",
58 "DB_PASSWORD": "bravo",
59 }
60
61 err = store.Set(domainName, secretsMap)
62 require.NoError(t, err)
63
64 keysList, err := store.ListKeys(domainName)
65 require.NoError(t, err)
66 assert.Len(t, keysList, 2)
67 assert.ElementsMatch(t, keys, keysList)
68
69 values, err := store.GetAll(domainName)
70 require.NoError(t, err)
71 assert.Equal(t, "alpha", values["API_KEY"])
72 assert.Equal(t, "bravo", values["DB_PASSWORD"])
73
74 err = store.Delete(domainName, "DB_PASSWORD")
75 require.NoError(t, err)
76
77 keysList, err = store.ListKeys(domainName)
78 require.NoError(t, err)
79 assert.ElementsMatch(t, []string{"API_KEY"}, keysList)
80}
81
82func TestPassStore_SetGetAttachment(t *testing.T) {
83 requirePass(t)
84
85 store, err := NewPassStore(testLogger())
86 require.NoError(t, err)
87
88 containerName := fmt.Sprintf("gitea-postgres-%d", time.Now().UnixNano())
89 keys := []string{"POSTGRES_USER", "POSTGRES_PASSWORD"}
90 defer CleanupPassAttachment(t, containerName, keys)
91
92 secretsMap := map[string]string{
93 "POSTGRES_USER": "gitea",
94 "POSTGRES_PASSWORD": "secret123",
95 }
96
97 err = store.SetAttachment(containerName, secretsMap)
98 require.NoError(t, err)
99
100 values, err := store.GetAllAttachment(containerName)
101 require.NoError(t, err)
102 assert.Equal(t, "gitea", values["POSTGRES_USER"])
103 assert.Equal(t, "secret123", values["POSTGRES_PASSWORD"])
104}
105
106func TestPassStore_ListAttachmentKeys_AfterSet(t *testing.T) {
107 requirePass(t)
108
109 store, err := NewPassStore(testLogger())
110 require.NoError(t, err)
111
112 containerName := fmt.Sprintf("redis-cache-%d", time.Now().UnixNano())
113 keys := []string{"REDIS_PASSWORD"}
114 defer CleanupPassAttachment(t, containerName, keys)
115
116 secretsMap := map[string]string{
117 "REDIS_PASSWORD": "redis123",
118 }
119
120 err = store.SetAttachment(containerName, secretsMap)
121 require.NoError(t, err)
122
123 values, err := store.GetAllAttachment(containerName)
124 require.NoError(t, err)
125 assert.Len(t, values, 1)
126 assert.Equal(t, "redis123", values["REDIS_PASSWORD"])
127}
128
129func TestParsePassListOutput(t *testing.T) {
130 tests := []struct {
131 name string
132 basePath string
133 output string
134 want []passListEntry
135 }{
136 {
137 name: "simple tree",
138 basePath: "domain",
139 output: "domain\n├── key1\n└── key2",
140 want: []passListEntry{{name: "key1", depth: 1}, {name: "key2", depth: 1}},
141 },
142 {
143 name: "nested tree",
144 basePath: "domain",
145 output: "domain\n│ ├── subkey1\n│ └── subkey2\n└── key1",
146 want: []passListEntry{{name: "subkey1", depth: 2}, {name: "subkey2", depth: 2}, {name: "key1", depth: 1}},
147 },
148 {
149 name: "empty output",
150 basePath: "domain",
151 output: "",
152 want: []passListEntry{},
153 },
154 {
155 name: "ASCII fallback chars",
156 basePath: "domain",
157 output: "domain\n| |-- subkey1\n`-- key1",
158 want: []passListEntry{{name: "subkey1", depth: 2}, {name: "key1", depth: 1}},
159 },
160 }
161
162 for _, tt := range tests {
163 t.Run(tt.name, func(t *testing.T) {
164 got := parsePassListOutput(tt.basePath, tt.output)
165 assert.Equal(t, tt.want, got)
166 })
167 }
168}