main bac21c6

app_bind_test.go RawHistoryFolder

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 TestAppSpec_ValidateBindsOK(t *testing.T) {
13	spec := validSpec()
14	spec.Services[0].Binds = []domain.AppBind{
15		{Name: "config", Path: "/etc/app.conf", ReadOnly: true},
16		{Name: "data", Path: "/var/lib/app"},
17	}
18	require.NoError(t, spec.Validate())
19}
20
21func TestAppSpec_ValidateBindErrors(t *testing.T) {
22	cases := []struct {
23		name    string
24		mutate  func(*domain.AppSpec)
25		wantErr string
26	}{
27		{"bad bind name", func(s *domain.AppSpec) {
28			s.Services[0].Binds = []domain.AppBind{{Name: "Bad_Name", Path: "/data"}}
29		}, "bind name"},
30		{"reserved separator name", func(s *domain.AppSpec) {
31			s.Services[0].Binds = []domain.AppBind{{Name: "a--b", Path: "/data"}}
32		}, "reserved separator"},
33		{"relative destination", func(s *domain.AppSpec) {
34			s.Services[0].Binds = []domain.AppBind{{Name: "data", Path: "relative/path"}}
35		}, "must be absolute"},
36		{"unnormalized destination", func(s *domain.AppSpec) {
37			s.Services[0].Binds = []domain.AppBind{{Name: "data", Path: "/var/../data"}}
38		}, "normalized clean path"},
39		{"trailing slash destination", func(s *domain.AppSpec) {
40			s.Services[0].Binds = []domain.AppBind{{Name: "data", Path: "/data/"}}
41		}, "normalized clean path"},
42		{"sensitive destination", func(s *domain.AppSpec) {
43			s.Services[0].Binds = []domain.AppBind{{Name: "dev", Path: "/dev"}}
44		}, "sensitive container path"},
45		{"duplicate bind name", func(s *domain.AppSpec) {
46			s.Services[0].Binds = []domain.AppBind{
47				{Name: "data", Path: "/data"},
48				{Name: "data", Path: "/other"},
49			}
50		}, "duplicate bind name"},
51		{"duplicate bind destination", func(s *domain.AppSpec) {
52			s.Services[0].Binds = []domain.AppBind{
53				{Name: "one", Path: "/data"},
54				{Name: "two", Path: "/data"},
55			}
56		}, "duplicate bind destination"},
57		{"collision with volume", func(s *domain.AppSpec) {
58			s.Services[0].Volumes = []domain.AppVolume{{Name: "data", Path: "/data"}}
59			s.Services[0].Binds = []domain.AppBind{{Name: "data-bind", Path: "/data"}}
60		}, "collides with a declared volume"},
61	}
62	for _, tc := range cases {
63		t.Run(tc.name, func(t *testing.T) {
64			spec := validSpec()
65			tc.mutate(&spec)
66			err := spec.Validate()
67			require.Error(t, err)
68			assert.ErrorIs(t, err, domain.ErrInvalidAppSpec)
69			assert.Contains(t, err.Error(), tc.wantErr)
70		})
71	}
72}
73
74func TestValidateBindName(t *testing.T) {
75	assert.NoError(t, domain.ValidateBindName("data"))
76	assert.NoError(t, domain.ValidateBindName("app.config"))
77	assert.ErrorIs(t, domain.ValidateBindName(""), domain.ErrInvalidAppSpec)
78	assert.ErrorIs(t, domain.ValidateBindName("Bad"), domain.ErrInvalidAppSpec)
79	assert.ErrorIs(t, domain.ValidateBindName("a--b"), domain.ErrInvalidAppSpec)
80}
81
82func TestValidateBindDestination(t *testing.T) {
83	valid := []string{"/data", "/etc/app.conf", "/var/lib/app"}
84	for _, dest := range valid {
85		assert.NoError(t, domain.ValidateBindDestination(dest), dest)
86	}
87	invalid := []string{"", "data", "/data/", "/data/../etc", "//data", "/./data", "/", "/proc", "/sys", "/dev", "/boot"}
88	for _, dest := range invalid {
89		err := domain.ValidateBindDestination(dest)
90		require.Error(t, err, dest)
91		assert.ErrorIs(t, err, domain.ErrInvalidAppSpec, dest)
92	}
93}
94
95func TestIsSensitiveBindDestination(t *testing.T) {
96	for _, dest := range []string{"/", "/proc", "/sys", "/dev", "/boot"} {
97		assert.True(t, domain.IsSensitiveBindDestination(dest), dest)
98	}
99	assert.False(t, domain.IsSensitiveBindDestination("/etc/app.conf"))
100	assert.True(t, domain.IsSensitiveBindDestination("/proc/self"))
101	assert.True(t, domain.IsSensitiveBindDestination("/dev/shm"))
102}
103
104func TestDiffAppSpec_Binds(t *testing.T) {
105	desired := validSpec()
106	desired.Services[0].Binds = []domain.AppBind{{Name: "data", Path: "/data", ReadOnly: true}}
107	effective := validSpec()
108	effective.Services[0].Binds = []domain.AppBind{{Name: "data", Path: "/data"}}
109	diff := domain.DiffAppSpec(desired, effective)
110	assert.Contains(t, diff.Changed, "service/web/binds")
111}

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