v2.31.0-dev20 020cc00

env_test.go RawHistoryFolder

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 TestContainsSecretReference(t *testing.T) {
183	tests := []struct {
184		name     string
185		value    string
186		expected bool
187	}{
188		{name: "simple pass reference", value: "${pass:myapp/db}", expected: true},
189		{name: "simple sops reference", value: "${sops:secrets.yaml#/key}", expected: true},
190		{name: "pass reference in text", value: "prefix-${pass:path}-suffix", expected: true},
191		{name: "sops reference in text", value: "${sops:file.yaml}", expected: true},
192		{name: "no reference - plain text", value: "hello world", expected: false},
193		{name: "no reference - dollar sign only", value: "$FOO", expected: false},
194		{name: "no reference - empty braces", value: "${}", expected: false},
195		{name: "no reference - missing colon", value: "${pass}", expected: false},
196		{name: "no reference - extra braces", value: "${{pass:path}}", expected: false},
197		{name: "normal env var", value: "${FOO}", expected: false},
198		{name: "value with equals and reference", value: "key=${pass:secret}", expected: true},
199		{name: "quoted reference", value: `"${pass:secret}"`, expected: true},
200		{name: "multiple references", value: "${pass:a}${sops:b}", expected: true},
201		{name: "case sensitive - uppercase", value: "${PASS:secret}", expected: false},
202		{name: "case sensitive - mixed", value: "${Pass:secret}", expected: false},
203	}
204
205	for _, tt := range tests {
206		t.Run(tt.name, func(t *testing.T) {
207			result := ContainsSecretReference(tt.value)
208			assert.Equal(t, tt.expected, result)
209		})
210	}
211}
212
213func TestValidateContainerName(t *testing.T) {
214	tests := []struct {
215		name        string
216		container   string
217		wantErr     bool
218		wantErrType error
219	}{
220		{name: "simple", container: "postgres", wantErr: false},
221		{name: "with hyphen", container: "gitea-postgres", wantErr: false},
222		{name: "with underscore", container: "gitea_postgres", wantErr: false},
223		{name: "complex real container", container: "gordon-git-example-com-gitea-postgres", wantErr: false},
224		{name: "starts with letter", container: "a-b", wantErr: false},
225		{name: "lowercase", container: "my-container", wantErr: false},
226		{name: "mixed case", container: "MyContainer-123", wantErr: false},
227		{name: "empty", container: "", wantErr: true, wantErrType: ErrInvalidContainerName},
228		{name: "starts with number", container: "1container", wantErr: true, wantErrType: ErrInvalidContainerName},
229		{name: "starts with hyphen", container: "-container", wantErr: true, wantErrType: ErrInvalidContainerName},
230		{name: "starts with underscore", container: "_container", wantErr: true, wantErrType: ErrInvalidContainerName},
231		{name: "contains slash", container: "container/name", wantErr: true, wantErrType: ErrPathTraversal},
232		{name: "contains backslash", container: `container\name`, wantErr: true, wantErrType: ErrPathTraversal},
233		{name: "path traversal", container: "..", wantErr: true, wantErrType: ErrPathTraversal},
234		{name: "contains dot", container: "container.name", wantErr: true, wantErrType: ErrInvalidContainerName},
235		{name: "contains space", container: "container name", wantErr: true, wantErrType: ErrInvalidContainerName},
236		{name: "contains special char", container: "container$name", wantErr: true, wantErrType: ErrInvalidContainerName},
237	}
238
239	for _, tt := range tests {
240		t.Run(tt.name, func(t *testing.T) {
241			err := ValidateContainerName(tt.container)
242			if tt.wantErr {
243				require.Error(t, err)
244				if tt.wantErrType != nil {
245					assert.ErrorIs(t, err, tt.wantErrType)
246				}
247			} else {
248				assert.NoError(t, err)
249			}
250		})
251	}
252}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close