1package domain_test
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/gordon/internal/domain"
10)
11
12func fileSecretSpec() domain.AppSpec {
13 spec := validSpec()
14 spec.Services[0].SecretFiles = []string{"DATABASE_URL"}
15 return spec
16}
17
18func TestAppSpec_ValidateSecretFilesOK(t *testing.T) {
19 require.NoError(t, fileSecretSpec().Validate())
20 assert.True(t, fileSecretSpec().Services[0].SecretAsFile("DATABASE_URL"))
21 assert.False(t, validSpec().Services[0].SecretAsFile("DATABASE_URL"))
22}
23
24func TestAppSpec_ValidateSecretFilesErrors(t *testing.T) {
25 cases := []struct {
26 name string
27 mutate func(*domain.AppSpec)
28 wantErr string
29 }{
30 {"not a secret", func(s *domain.AppSpec) { s.Services[0].SecretFiles = []string{"OTHER"} }, "not a declared secret"},
31 {"unsorted", func(s *domain.AppSpec) {
32 s.Services[0].Secrets["API"] = "api"
33 s.Services[0].SecretFiles = []string{"DATABASE_URL", "API"}
34 }, "must be sorted"},
35 {"duplicate", func(s *domain.AppSpec) {
36 s.Services[0].SecretFiles = []string{"DATABASE_URL", "DATABASE_URL"}
37 }, "duplicated"},
38 {"file key collides with secret", func(s *domain.AppSpec) {
39 s.Services[0].Secrets["DATABASE_URL_FILE"] = "other"
40 }, "collides with a secret file key"},
41 {"file key collides with service env", func(s *domain.AppSpec) {
42 s.Services[0].Env = map[string]string{"DATABASE_URL_FILE": "x"}
43 }, "collides with a secret file key"},
44 {"file key collides with app env", func(s *domain.AppSpec) {
45 s.Env["DATABASE_URL_FILE"] = "x"
46 }, "collides with a secret file key"},
47 {"bind at secret dir", func(s *domain.AppSpec) {
48 s.Services[0].Binds = []domain.AppBind{{Name: "conf", Path: "/run/secrets", ReadOnly: true}}
49 }, "/run/secrets"},
50 {"bind under secret dir", func(s *domain.AppSpec) {
51 s.Services[0].Binds = []domain.AppBind{{Name: "conf", Path: "/run/secrets/x", ReadOnly: true}}
52 }, "/run/secrets"},
53 {"volume under secret dir", func(s *domain.AppSpec) {
54 s.Services[0].Volumes = []domain.AppVolume{{Name: "data", Path: "/run/secrets/data"}}
55 }, "/run/secrets"},
56 }
57 for _, tc := range cases {
58 t.Run(tc.name, func(t *testing.T) {
59 spec := fileSecretSpec()
60 tc.mutate(&spec)
61 err := spec.Validate()
62 require.Error(t, err)
63 assert.ErrorIs(t, err, domain.ErrInvalidAppSpec)
64 assert.Contains(t, err.Error(), tc.wantErr)
65 })
66 }
67}
68
69func TestAppSpec_SecretDirBindWithoutFileSecretsOK(t *testing.T) {
70 spec := validSpec()
71 spec.Services[0].Binds = []domain.AppBind{{Name: "conf", Path: "/run/secrets", ReadOnly: true}}
72 require.NoError(t, spec.Validate())
73}
74
75func TestDiffAppSpec_SecretFileModeSwitch(t *testing.T) {
76 envSpec := validSpec()
77 fileSpec := fileSecretSpec()
78
79 diff := domain.DiffAppSpec(fileSpec, envSpec)
80 assert.Equal(t, []string{"service/web/secret/database-url"}, diff.Changed)
81 assert.False(t, domain.SameAppService(fileSpec.Services[0], envSpec.Services[0]))
82
83 back := domain.DiffAppSpec(envSpec, fileSpec)
84 assert.Equal(t, []string{"service/web/secret/database-url"}, back.Changed)
85
86 same := domain.DiffAppSpec(fileSpec, fileSecretSpec())
87 assert.Empty(t, same.Changed)
88 assert.True(t, domain.SameAppService(fileSpec.Services[0], fileSecretSpec().Services[0]))
89
90 old := validSpec()
91 require.Nil(t, old.Services[0].SecretFiles)
92 assert.Empty(t, domain.DiffAppSpec(old, validSpec()).Changed)
93 assert.True(t, domain.SameAppService(old.Services[0], validSpec().Services[0]))
94}