1package domain
2
3import (
4 "encoding/base64"
5 "strings"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10)
11
12func TestSanitizeDomainForEnvFile(t *testing.T) {
13 tests := []struct {
14 name string
15 domain string
16 want string
17 wantErr bool
18 }{
19 {name: "simple domain", domain: "example.com", want: base64.RawURLEncoding.EncodeToString([]byte("example.com"))},
20 {name: "subdomain", domain: "app.example.com", want: base64.RawURLEncoding.EncodeToString([]byte("app.example.com"))},
21 {name: "domain with port", domain: "example.com:8080", want: base64.RawURLEncoding.EncodeToString([]byte("example.com:8080"))},
22 {name: "domain with path", domain: "example.com/path", want: base64.RawURLEncoding.EncodeToString([]byte("example.com/path"))},
23 {name: "single char", domain: "a", want: base64.RawURLEncoding.EncodeToString([]byte("a"))},
24 {name: "hyphenated", domain: "my-app.example.com", want: base64.RawURLEncoding.EncodeToString([]byte("my-app.example.com"))},
25 {name: "empty", domain: "", wantErr: true},
26 {name: "path traversal", domain: "../etc/passwd", wantErr: true},
27 {name: "double dots", domain: "foo..bar", wantErr: true},
28 {name: "starts with dot", domain: ".hidden", wantErr: true},
29 {name: "ends with dot", domain: "trailing.", wantErr: true},
30 {name: "space", domain: "has space", wantErr: true},
31 {name: "special chars", domain: "bad$domain", wantErr: true},
32 {name: "underscore rejected as invalid domain character", domain: "example_com", wantErr: true},
33 }
34
35 for _, tt := range tests {
36 t.Run(tt.name, func(t *testing.T) {
37 got, err := SanitizeDomainForEnvFile(tt.domain)
38 if tt.wantErr {
39 assert.Error(t, err)
40 return
41 }
42 require.NoError(t, err)
43 assert.Equal(t, tt.want, got)
44 })
45 }
46}
47
48func TestSanitizeDomainForEnvFile_DistinguishesSeparators(t *testing.T) {
49 gotDot, err := SanitizeDomainForEnvFile("app.example.com")
50 require.NoError(t, err)
51
52 gotPort, err := SanitizeDomainForEnvFile("app:example:com")
53 require.NoError(t, err)
54
55 gotPath, err := SanitizeDomainForEnvFile("app/example/com")
56 require.NoError(t, err)
57
58 assert.NotEqual(t, gotDot, gotPort)
59 assert.NotEqual(t, gotDot, gotPath)
60 assert.NotEqual(t, gotPort, gotPath)
61}
62
63func TestParseEnvData(t *testing.T) {
64 tests := []struct {
65 name string
66 data string
67 want map[string]string
68 wantErr bool
69 }{
70 {
71 name: "simple key-value",
72 data: "FOO=bar\nBAZ=qux",
73 want: map[string]string{"FOO": "bar", "BAZ": "qux"},
74 },
75 {
76 name: "double-quoted value",
77 data: `KEY="hello world"`,
78 want: map[string]string{"KEY": "hello world"},
79 },
80 {
81 name: "single-quoted value",
82 data: `KEY='hello world'`,
83 want: map[string]string{"KEY": "hello world"},
84 },
85 {
86 name: "comment lines",
87 data: "# this is a comment\nKEY=val",
88 want: map[string]string{"KEY": "val"},
89 },
90 {
91 name: "empty lines",
92 data: "\n\nKEY=val\n\n",
93 want: map[string]string{"KEY": "val"},
94 },
95 {
96 name: "value with equals",
97 data: "URL=postgres://host:5432/db?opt=1",
98 want: map[string]string{"URL": "postgres://host:5432/db?opt=1"},
99 },
100 {
101 name: "no value",
102 data: "NOVALUE",
103 want: map[string]string{},
104 },
105 {
106 name: "empty value",
107 data: "KEY=",
108 want: map[string]string{"KEY": ""},
109 },
110 {
111 name: "empty input",
112 data: "",
113 want: map[string]string{},
114 },
115 {
116 name: "large value within buffer",
117 data: "BIG=" + strings.Repeat("x", 100000),
118 want: map[string]string{"BIG": strings.Repeat("x", 100000)},
119 },
120 {
121 name: "quoted value with inner spaces",
122 data: `KEY=" hello "`,
123 want: map[string]string{"KEY": " hello "},
124 },
125 {
126 name: "unquoted value with outer spaces",
127 data: "KEY= hello ",
128 want: map[string]string{"KEY": "hello"},
129 },
130 }
131
132 for _, tt := range tests {
133 t.Run(tt.name, func(t *testing.T) {
134 got, err := ParseEnvData([]byte(tt.data))
135 if tt.wantErr {
136 assert.Error(t, err)
137 return
138 }
139 require.NoError(t, err)
140 assert.Equal(t, tt.want, got)
141 })
142 }
143}
144
145func TestValidateEnvKey(t *testing.T) {
146 tests := []struct {
147 name string
148 key string
149 wantErr bool
150 wantErrType error
151 }{
152 {name: "simple", key: "FOO", wantErr: false},
153 {name: "with underscore", key: "FOO_BAR", wantErr: false},
154 {name: "starts with underscore", key: "_PRIVATE", wantErr: false},
155 {name: "lowercase", key: "foo", wantErr: false},
156 {name: "mixed", key: "myApp_v2", wantErr: false},
157 {name: "empty", key: "", wantErr: true, wantErrType: ErrInvalidEnvKey},
158 {name: "starts with number", key: "1BAD", wantErr: true, wantErrType: ErrInvalidEnvKey},
159 {name: "contains dot", key: "FOO.BAR", wantErr: true, wantErrType: ErrInvalidEnvKey},
160 {name: "contains slash", key: "FOO/BAR", wantErr: true, wantErrType: ErrPathTraversal},
161 {name: "contains backslash", key: `FOO\BAR`, wantErr: true, wantErrType: ErrPathTraversal},
162 {name: "path traversal", key: "..", wantErr: true, wantErrType: ErrPathTraversal},
163 {name: "contains hyphen", key: "FOO-BAR", wantErr: true, wantErrType: ErrInvalidEnvKey},
164 {name: "contains space", key: "FOO BAR", wantErr: true, wantErrType: ErrInvalidEnvKey},
165 }
166
167 for _, tt := range tests {
168 t.Run(tt.name, func(t *testing.T) {
169 err := ValidateEnvKey(tt.key)
170 if tt.wantErr {
171 require.Error(t, err)
172 if tt.wantErrType != nil {
173 assert.ErrorIs(t, err, tt.wantErrType)
174 }
175 } else {
176 assert.NoError(t, err)
177 }
178 })
179 }
180}
181
182func TestValidateContainerName(t *testing.T) {
183 tests := []struct {
184 name string
185 container string
186 wantErr bool
187 wantErrType error
188 }{
189 {name: "simple", container: "postgres", wantErr: false},
190 {name: "with hyphen", container: "gitea-postgres", wantErr: false},
191 {name: "with underscore", container: "gitea_postgres", wantErr: false},
192 {name: "complex real container", container: "gordon-git-example-com-gitea-postgres", wantErr: false},
193 {name: "starts with letter", container: "a-b", wantErr: false},
194 {name: "lowercase", container: "my-container", wantErr: false},
195 {name: "mixed case", container: "MyContainer-123", wantErr: false},
196 {name: "empty", container: "", wantErr: true, wantErrType: ErrInvalidContainerName},
197 {name: "starts with number", container: "1container", wantErr: true, wantErrType: ErrInvalidContainerName},
198 {name: "starts with hyphen", container: "-container", wantErr: true, wantErrType: ErrInvalidContainerName},
199 {name: "starts with underscore", container: "_container", wantErr: true, wantErrType: ErrInvalidContainerName},
200 {name: "contains slash", container: "container/name", wantErr: true, wantErrType: ErrPathTraversal},
201 {name: "contains backslash", container: `container\name`, wantErr: true, wantErrType: ErrPathTraversal},
202 {name: "path traversal", container: "..", wantErr: true, wantErrType: ErrPathTraversal},
203 {name: "contains dot", container: "container.name", wantErr: true, wantErrType: ErrInvalidContainerName},
204 {name: "contains space", container: "container name", wantErr: true, wantErrType: ErrInvalidContainerName},
205 {name: "contains special char", container: "container$name", wantErr: true, wantErrType: ErrInvalidContainerName},
206 }
207
208 for _, tt := range tests {
209 t.Run(tt.name, func(t *testing.T) {
210 err := ValidateContainerName(tt.container)
211 if tt.wantErr {
212 require.Error(t, err)
213 if tt.wantErrType != nil {
214 assert.ErrorIs(t, err, tt.wantErrType)
215 }
216 } else {
217 assert.NoError(t, err)
218 }
219 })
220 }
221}