1package app
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "testing"
10 "time"
11
12 "github.com/bnema/zerowrap"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15
16 "github.com/bnema/gordon/internal/adapters/out/domainsecrets"
17 "github.com/bnema/gordon/internal/domain"
18)
19
20func passCmd(args ...string) error {
21 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
22 defer cancel()
23 return exec.CommandContext(ctx, "pass", args...).Run()
24}
25
26func requirePass(t *testing.T) {
27 if err := passCmd("version"); err != nil {
28 t.Skip("pass not available")
29 }
30 if err := passCmd("ls"); err != nil {
31 t.Skip("pass store not initialized")
32 }
33}
34
35func cleanupPassDomain(t *testing.T, domainName string, keys []string) {
36 safeDomain, err := domain.SanitizeDomainForEnvFile(domainName)
37 if err != nil {
38 return
39 }
40
41 for _, key := range keys {
42 path := fmt.Sprintf("%s/%s/%s", domainsecrets.PassDomainSecretsPath, safeDomain, key)
43 _ = passCmd("rm", "-f", path)
44 }
45
46 manifestPath := fmt.Sprintf("%s/%s/.keys", domainsecrets.PassDomainSecretsPath, safeDomain)
47 _ = passCmd("rm", "-f", manifestPath)
48}
49
50func TestMigrateEnvFile(t *testing.T) {
51 requirePass(t)
52
53 tmpDir, err := os.MkdirTemp("", "migrate-env-test-*")
54 require.NoError(t, err)
55 defer os.RemoveAll(tmpDir)
56
57 logger := zerowrap.Default()
58 store, err := domainsecrets.NewPassStore(logger)
59 require.NoError(t, err)
60
61 domainName := fmt.Sprintf("test.%d.example.com", time.Now().UnixNano())
62 keys := []string{"API_KEY", "DB_PASSWORD"}
63 defer cleanupPassDomain(t, domainName, keys)
64
65 envContent := "API_KEY=secret123\nDB_PASSWORD=pass456\n"
66 envFileName := domainName + ".env"
67 envFilePath := filepath.Join(tmpDir, envFileName)
68 err = os.WriteFile(envFilePath, []byte(envContent), 0600)
69 require.NoError(t, err)
70
71 err = migrateEnvFile(tmpDir, envFileName, store, logger)
72 require.NoError(t, err)
73
74 migratedPath := envFilePath + ".migrated"
75 _, err = os.Stat(envFilePath)
76 assert.True(t, os.IsNotExist(err), "original .env file should be removed")
77
78 _, err = os.Stat(migratedPath)
79 assert.NoError(t, err, ".env.migrated file should exist")
80
81 passKeys, err := store.ListKeys(domainName)
82 require.NoError(t, err)
83 assert.ElementsMatch(t, keys, passKeys)
84
85 values, err := store.GetAll(domainName)
86 require.NoError(t, err)
87 assert.Equal(t, "secret123", values["API_KEY"])
88 assert.Equal(t, "pass456", values["DB_PASSWORD"])
89}
90
91func cleanupPassAttachment(t *testing.T, containerName string, keys []string) {
92 for _, key := range keys {
93 path := fmt.Sprintf("%s/%s/%s", domainsecrets.PassAttachmentPath, containerName, key)
94 _ = passCmd("rm", "-f", path)
95 }
96
97 manifestPath := fmt.Sprintf("%s/%s/.keys", domainsecrets.PassAttachmentPath, containerName)
98 _ = passCmd("rm", "-f", manifestPath)
99}
100
101func TestMigrateAttachmentEnvFile(t *testing.T) {
102 requirePass(t)
103
104 tmpDir, err := os.MkdirTemp("", "migrate-attachment-env-test-*")
105 require.NoError(t, err)
106 defer os.RemoveAll(tmpDir)
107
108 logger := zerowrap.Default()
109 store, err := domainsecrets.NewPassStore(logger)
110 require.NoError(t, err)
111
112 containerName := fmt.Sprintf("gitea-postgres-%d", time.Now().UnixNano())
113 envFileName := "gordon-" + containerName + ".env"
114 // extractContainerNameFromAttachmentFile strips the .env suffix, so the
115 // stored name includes the "gordon-" prefix from the filename.
116 storedContainerName := extractContainerNameFromAttachmentFile(envFileName)
117 keys := []string{"POSTGRES_USER", "POSTGRES_PASSWORD"}
118 defer cleanupPassAttachment(t, storedContainerName, keys)
119
120 envContent := "POSTGRES_USER=gitea\nPOSTGRES_PASSWORD=secret123\n"
121 envFilePath := filepath.Join(tmpDir, envFileName)
122 err = os.WriteFile(envFilePath, []byte(envContent), 0600)
123 require.NoError(t, err)
124
125 err = migrateAttachmentEnvFile(tmpDir, envFileName, store, logger)
126 require.NoError(t, err)
127
128 migratedPath := envFilePath + ".migrated"
129 _, err = os.Stat(envFilePath)
130 assert.True(t, os.IsNotExist(err), "original .env file should be removed")
131
132 _, err = os.Stat(migratedPath)
133 assert.NoError(t, err, ".env.migrated file should exist")
134
135 values, err := store.GetAllAttachment(storedContainerName)
136 require.NoError(t, err)
137 assert.Equal(t, "gitea", values["POSTGRES_USER"])
138 assert.Equal(t, "secret123", values["POSTGRES_PASSWORD"])
139}
140
141func TestIsAttachmentEnvFile(t *testing.T) {
142 tests := []struct {
143 name string
144 filename string
145 want bool
146 }{
147 {
148 name: "attachment env file",
149 filename: "gordon-gitea-postgres.env",
150 want: true,
151 },
152 {
153 name: "attachment env file with complex name",
154 filename: "gordon-git-example-com-gitea-postgres.env",
155 want: true,
156 },
157 {
158 name: "plain domain env file",
159 filename: "example.com.env",
160 want: false,
161 },
162 {
163 name: "migrated attachment file",
164 filename: "gordon-gitea-postgres.env.migrated",
165 want: false,
166 },
167 {
168 name: "non-env file",
169 filename: "config.yaml",
170 want: false,
171 },
172 {
173 name: "gordon prefix but no env suffix",
174 filename: "gordon-something",
175 want: false,
176 },
177 }
178
179 for _, tt := range tests {
180 t.Run(tt.name, func(t *testing.T) {
181 got := isAttachmentEnvFile(tt.filename)
182 assert.Equal(t, tt.want, got)
183 })
184 }
185}
186
187func TestExtractContainerNameFromAttachmentFile(t *testing.T) {
188 tests := []struct {
189 name string
190 filename string
191 want string
192 }{
193 {
194 name: "simple attachment file",
195 filename: "gordon-gitea-postgres.env",
196 want: "gordon-gitea-postgres",
197 },
198 {
199 name: "complex attachment file",
200 filename: "gordon-git-example-com-gitea-redis.env",
201 want: "gordon-git-example-com-gitea-redis",
202 },
203 {
204 name: "single word container",
205 filename: "gordon-redis.env",
206 want: "gordon-redis",
207 },
208 }
209
210 for _, tt := range tests {
211 t.Run(tt.name, func(t *testing.T) {
212 got := extractContainerNameFromAttachmentFile(tt.filename)
213 assert.Equal(t, tt.want, got)
214 })
215 }
216}