v2.10.1 43db1bb

env_test.go RawHistoryFolder

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

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