v2.30.0-pre4 15d9fe3

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 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}

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