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 keys := []string{"POSTGRES_USER", "POSTGRES_PASSWORD"}
114 defer cleanupPassAttachment(t, containerName, keys)
115
116 envContent := "POSTGRES_USER=gitea\nPOSTGRES_PASSWORD=secret123\n"
117 envFileName := "gordon-" + containerName + ".env"
118 envFilePath := filepath.Join(tmpDir, envFileName)
119 err = os.WriteFile(envFilePath, []byte(envContent), 0600)
120 require.NoError(t, err)
121
122 err = migrateAttachmentEnvFile(tmpDir, envFileName, store, logger)
123 require.NoError(t, err)
124
125 migratedPath := envFilePath + ".migrated"
126 _, err = os.Stat(envFilePath)
127 assert.True(t, os.IsNotExist(err), "original .env file should be removed")
128
129 _, err = os.Stat(migratedPath)
130 assert.NoError(t, err, ".env.migrated file should exist")
131
132 values, err := store.GetAllAttachment(containerName)
133 require.NoError(t, err)
134 assert.Equal(t, "gitea", values["POSTGRES_USER"])
135 assert.Equal(t, "secret123", values["POSTGRES_PASSWORD"])
136}
137
138func TestIsAttachmentEnvFile(t *testing.T) {
139 tests := []struct {
140 name string
141 filename string
142 want bool
143 }{
144 {
145 name: "attachment env file",
146 filename: "gordon-gitea-postgres.env",
147 want: true,
148 },
149 {
150 name: "attachment env file with complex name",
151 filename: "gordon-git-example-com-gitea-postgres.env",
152 want: true,
153 },
154 {
155 name: "plain domain env file",
156 filename: "example.com.env",
157 want: false,
158 },
159 {
160 name: "migrated attachment file",
161 filename: "gordon-gitea-postgres.env.migrated",
162 want: false,
163 },
164 {
165 name: "non-env file",
166 filename: "config.yaml",
167 want: false,
168 },
169 {
170 name: "gordon prefix but no env suffix",
171 filename: "gordon-something",
172 want: false,
173 },
174 }
175
176 for _, tt := range tests {
177 t.Run(tt.name, func(t *testing.T) {
178 got := isAttachmentEnvFile(tt.filename)
179 assert.Equal(t, tt.want, got)
180 })
181 }
182}
183
184func TestExtractContainerNameFromAttachmentFile(t *testing.T) {
185 tests := []struct {
186 name string
187 filename string
188 want string
189 }{
190 {
191 name: "simple attachment file",
192 filename: "gordon-gitea-postgres.env",
193 want: "gordon-gitea-postgres",
194 },
195 {
196 name: "complex attachment file",
197 filename: "gordon-git-example-com-gitea-redis.env",
198 want: "gordon-git-example-com-gitea-redis",
199 },
200 {
201 name: "single word container",
202 filename: "gordon-redis.env",
203 want: "gordon-redis",
204 },
205 }
206
207 for _, tt := range tests {
208 t.Run(tt.name, func(t *testing.T) {
209 got := extractContainerNameFromAttachmentFile(tt.filename)
210 assert.Equal(t, tt.want, got)
211 })
212 }
213}