v3.0.0 f19f453

bind_policy_test.go RawHistoryFolder

1package apps_test
2
3import (
4	"context"
5	"os"
6	"path/filepath"
7	"testing"
8
9	"github.com/bnema/zerowrap"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12
13	outmocks "github.com/bnema/gordon/internal/boundaries/out/mocks"
14	"github.com/bnema/gordon/internal/domain"
15	"github.com/bnema/gordon/internal/usecase/apps"
16)
17
18func bindPolicy(root, source string, apps, services []string) domain.AppBindPolicy {
19	return domain.AppBindPolicy{
20		Name:            "config",
21		Source:          source,
22		Root:            root,
23		AllowedApps:     apps,
24		AllowedServices: services,
25	}
26}
27
28func TestApply_AcceptsBindsUnderAdministrativePolicy(t *testing.T) {
29	root := t.TempDir()
30	source := filepath.Join(root, "binds")
31	require.NoError(t, os.MkdirAll(source, 0o755))
32
33	store := outmocks.NewMockAppState(t)
34	spec := testSpec("blog")
35	spec.Services[0].Binds = []domain.AppBind{{Name: "config", Path: "/etc/app.conf"}}
36	applySuccess(store, spec)
37
38	svc := apps.NewService(store, zerowrap.Default()).
39		WithBindPolicies(map[string]domain.AppBindPolicy{
40			"config": bindPolicy(root, source, []string{"blog"}, []string{"web"}),
41		})
42	_, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
43	require.NoError(t, err)
44}
45
46func TestApply_RejectsBindWithoutPolicyBeforeStoreAccess(t *testing.T) {
47	store := outmocks.NewMockAppState(t)
48	spec := testSpec("blog")
49	spec.Services[0].Binds = []domain.AppBind{{Name: "config", Path: "/etc/app.conf"}}
50	svc := apps.NewService(store, zerowrap.Default())
51
52	_, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
53
54	require.ErrorIs(t, err, domain.ErrBindPolicy)
55	assert.Contains(t, err.Error(), "blog")
56	assert.Contains(t, err.Error(), "web")
57	assert.Contains(t, err.Error(), "config")
58}
59
60func TestApply_RejectsBindOutsideAllowlists(t *testing.T) {
61	root := t.TempDir()
62	source := filepath.Join(root, "binds")
63	require.NoError(t, os.MkdirAll(source, 0o755))
64	spec := testSpec("blog")
65	spec.Services[0].Binds = []domain.AppBind{{Name: "config", Path: "/etc/app.conf"}}
66
67	t.Run("app not allowed", func(t *testing.T) {
68		store := outmocks.NewMockAppState(t)
69		svc := apps.NewService(store, zerowrap.Default()).
70			WithBindPolicies(map[string]domain.AppBindPolicy{
71				"config": bindPolicy(root, source, []string{"other"}, []string{"web"}),
72			})
73		_, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
74		require.ErrorIs(t, err, domain.ErrBindPolicy)
75		assert.NotContains(t, err.Error(), source)
76	})
77
78	t.Run("service not allowed", func(t *testing.T) {
79		store := outmocks.NewMockAppState(t)
80		svc := apps.NewService(store, zerowrap.Default()).
81			WithBindPolicies(map[string]domain.AppBindPolicy{
82				"config": bindPolicy(root, source, []string{"blog"}, []string{"other"}),
83			})
84		_, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
85		require.ErrorIs(t, err, domain.ErrBindPolicy)
86		assert.NotContains(t, err.Error(), source)
87	})
88}
89
90func TestApply_RejectsBindSourceOutsideRoot(t *testing.T) {
91	root := t.TempDir()
92	outside := t.TempDir()
93	source := filepath.Join(outside, "binds")
94	require.NoError(t, os.MkdirAll(source, 0o755))
95	store := outmocks.NewMockAppState(t)
96	spec := testSpec("blog")
97	spec.Services[0].Binds = []domain.AppBind{{Name: "config", Path: "/etc/app.conf"}}
98	svc := apps.NewService(store, zerowrap.Default()).
99		WithBindPolicies(map[string]domain.AppBindPolicy{
100			"config": bindPolicy(root, source, []string{"blog"}, []string{"web"}),
101		})
102
103	_, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
104
105	require.ErrorIs(t, err, domain.ErrBindPolicy)
106	assert.NotContains(t, err.Error(), source, "apply errors must never leak the policy source path")
107	assert.NotContains(t, err.Error(), outside)
108}
109
110// TestApply_BindPoliciesReplaceOnReload proves a reload-style replacement takes
111// effect immediately: the second apply is refused before any store access.
112func TestApply_BindPoliciesReplaceOnReload(t *testing.T) {
113	root := t.TempDir()
114	source := filepath.Join(root, "binds")
115	require.NoError(t, os.MkdirAll(source, 0o755))
116	spec := testSpec("blog")
117	spec.Services[0].Binds = []domain.AppBind{{Name: "config", Path: "/etc/app.conf"}}
118
119	store := outmocks.NewMockAppState(t)
120	applySuccess(store, spec)
121	svc := apps.NewService(store, zerowrap.Default()).
122		WithBindPolicies(map[string]domain.AppBindPolicy{
123			"config": bindPolicy(root, source, []string{"blog"}, []string{"web"}),
124		})
125	_, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
126	require.NoError(t, err)
127
128	svc.SetBindPolicies(map[string]domain.AppBindPolicy{
129		"config": bindPolicy(root, source, []string{"other"}, []string{"web"}),
130	})
131	_, _, err = svc.Apply(context.Background(), spec, []byte("manifest"), false)
132	require.ErrorIs(t, err, domain.ErrBindPolicy)
133}

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