package app import ( "context" "fmt" "os" "os/exec" "path/filepath" "testing" "time" "github.com/bnema/zerowrap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/bnema/gordon/internal/adapters/out/domainsecrets" "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(t *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", domainsecrets.PassDomainSecretsPath, safeDomain, key) _ = passCmd("rm", "-f", path) } manifestPath := fmt.Sprintf("%s/%s/.keys", domainsecrets.PassDomainSecretsPath, safeDomain) _ = passCmd("rm", "-f", manifestPath) } func TestMigrateEnvFile(t *testing.T) { requirePass(t) tmpDir, err := os.MkdirTemp("", "migrate-env-test-*") require.NoError(t, err) defer os.RemoveAll(tmpDir) logger := zerowrap.Default() store, err := domainsecrets.NewPassStore(logger) 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) envContent := "API_KEY=secret123\nDB_PASSWORD=pass456\n" envFileName := domainName + ".env" envFilePath := filepath.Join(tmpDir, envFileName) err = os.WriteFile(envFilePath, []byte(envContent), 0600) require.NoError(t, err) err = migrateEnvFile(tmpDir, envFileName, store, logger) require.NoError(t, err) migratedPath := envFilePath + ".migrated" _, err = os.Stat(envFilePath) assert.True(t, os.IsNotExist(err), "original .env file should be removed") _, err = os.Stat(migratedPath) assert.NoError(t, err, ".env.migrated file should exist") passKeys, err := store.ListKeys(domainName) require.NoError(t, err) assert.ElementsMatch(t, keys, passKeys) values, err := store.GetAll(domainName) require.NoError(t, err) assert.Equal(t, "secret123", values["API_KEY"]) assert.Equal(t, "pass456", values["DB_PASSWORD"]) } func cleanupPassAttachment(t *testing.T, containerName string, keys []string) { for _, key := range keys { path := fmt.Sprintf("%s/%s/%s", domainsecrets.PassAttachmentPath, containerName, key) _ = passCmd("rm", "-f", path) } manifestPath := fmt.Sprintf("%s/%s/.keys", domainsecrets.PassAttachmentPath, containerName) _ = passCmd("rm", "-f", manifestPath) } func TestMigrateAttachmentEnvFile(t *testing.T) { requirePass(t) tmpDir, err := os.MkdirTemp("", "migrate-attachment-env-test-*") require.NoError(t, err) defer os.RemoveAll(tmpDir) logger := zerowrap.Default() store, err := domainsecrets.NewPassStore(logger) require.NoError(t, err) containerName := fmt.Sprintf("gitea-postgres-%d", time.Now().UnixNano()) envFileName := "gordon-" + containerName + ".env" // extractContainerNameFromAttachmentFile strips the .env suffix, so the // stored name includes the "gordon-" prefix from the filename. storedContainerName := extractContainerNameFromAttachmentFile(envFileName) keys := []string{"POSTGRES_USER", "POSTGRES_PASSWORD"} defer cleanupPassAttachment(t, storedContainerName, keys) envContent := "POSTGRES_USER=gitea\nPOSTGRES_PASSWORD=secret123\n" envFilePath := filepath.Join(tmpDir, envFileName) err = os.WriteFile(envFilePath, []byte(envContent), 0600) require.NoError(t, err) err = migrateAttachmentEnvFile(tmpDir, envFileName, store, logger) require.NoError(t, err) migratedPath := envFilePath + ".migrated" _, err = os.Stat(envFilePath) assert.True(t, os.IsNotExist(err), "original .env file should be removed") _, err = os.Stat(migratedPath) assert.NoError(t, err, ".env.migrated file should exist") values, err := store.GetAllAttachment(storedContainerName) require.NoError(t, err) assert.Equal(t, "gitea", values["POSTGRES_USER"]) assert.Equal(t, "secret123", values["POSTGRES_PASSWORD"]) } func TestIsAttachmentEnvFile(t *testing.T) { tests := []struct { name string filename string want bool }{ { name: "attachment env file", filename: "gordon-gitea-postgres.env", want: true, }, { name: "attachment env file with complex name", filename: "gordon-git-example-com-gitea-postgres.env", want: true, }, { name: "plain domain env file", filename: "example.com.env", want: false, }, { name: "migrated attachment file", filename: "gordon-gitea-postgres.env.migrated", want: false, }, { name: "non-env file", filename: "config.yaml", want: false, }, { name: "gordon prefix but no env suffix", filename: "gordon-something", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := isAttachmentEnvFile(tt.filename) assert.Equal(t, tt.want, got) }) } } func TestExtractContainerNameFromAttachmentFile(t *testing.T) { tests := []struct { name string filename string want string }{ { name: "simple attachment file", filename: "gordon-gitea-postgres.env", want: "gordon-gitea-postgres", }, { name: "complex attachment file", filename: "gordon-git-example-com-gitea-redis.env", want: "gordon-git-example-com-gitea-redis", }, { name: "single word container", filename: "gordon-redis.env", want: "gordon-redis", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := extractContainerNameFromAttachmentFile(tt.filename) assert.Equal(t, tt.want, got) }) } }