1package apps_test
2
3import (
4 "context"
5 "testing"
6
7 "github.com/bnema/zerowrap"
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10
11 outmocks "github.com/bnema/gordon/internal/boundaries/out/mocks"
12 "github.com/bnema/gordon/internal/domain"
13 "github.com/bnema/gordon/internal/usecase/apps"
14)
15
16func devicePolicy(apps, services []string) domain.AppDevicePolicy {
17 return domain.AppDevicePolicy{
18 Name: "test_gpu",
19 CDI: []string{"example.com/gpu=GPU-test-uuid"},
20 AllowedApps: apps,
21 AllowedServices: services,
22 }
23}
24
25func TestApply_AcceptsDevicesUnderAdministrativePolicy(t *testing.T) {
26 store := outmocks.NewMockAppState(t)
27 spec := testSpec("blog")
28 spec.Services[0].Devices = []string{"test_gpu"}
29 applySuccess(store, spec)
30
31 svc := apps.NewService(store, zerowrap.Default()).
32 WithDevicePolicies(map[string]domain.AppDevicePolicy{
33 "test_gpu": devicePolicy([]string{"blog"}, []string{"web"}),
34 })
35 _, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
36 require.NoError(t, err)
37}
38
39func TestApply_RejectsDeviceWithoutPolicyBeforeStoreAccess(t *testing.T) {
40 store := outmocks.NewMockAppState(t)
41 spec := testSpec("blog")
42 spec.Services[0].Devices = []string{"test_gpu"}
43 svc := apps.NewService(store, zerowrap.Default())
44
45 _, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
46
47 require.ErrorIs(t, err, domain.ErrDevicePolicy)
48 assert.Contains(t, err.Error(), "blog")
49 assert.Contains(t, err.Error(), "web")
50 assert.Contains(t, err.Error(), "test_gpu")
51}
52
53func TestApply_RejectsDeviceOutsideAllowlists(t *testing.T) {
54 spec := testSpec("blog")
55 spec.Services[0].Devices = []string{"test_gpu"}
56
57 t.Run("app not allowed", func(t *testing.T) {
58 store := outmocks.NewMockAppState(t)
59 svc := apps.NewService(store, zerowrap.Default()).
60 WithDevicePolicies(map[string]domain.AppDevicePolicy{
61 "test_gpu": devicePolicy([]string{"other"}, []string{"web"}),
62 })
63 _, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
64 require.ErrorIs(t, err, domain.ErrDevicePolicy)
65 assert.NotContains(t, err.Error(), "GPU-test-uuid")
66 })
67
68 t.Run("service not allowed", func(t *testing.T) {
69 store := outmocks.NewMockAppState(t)
70 svc := apps.NewService(store, zerowrap.Default()).
71 WithDevicePolicies(map[string]domain.AppDevicePolicy{
72 "test_gpu": devicePolicy([]string{"blog"}, []string{"other"}),
73 })
74 _, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
75 require.ErrorIs(t, err, domain.ErrDevicePolicy)
76 assert.NotContains(t, err.Error(), "GPU-test-uuid")
77 })
78}
79
80// TestApply_DevicePoliciesReplaceOnReload proves a reload-style replacement
81// takes effect immediately: the second apply is refused before any store
82// access.
83func TestApply_DevicePoliciesReplaceOnReload(t *testing.T) {
84 spec := testSpec("blog")
85 spec.Services[0].Devices = []string{"test_gpu"}
86
87 store := outmocks.NewMockAppState(t)
88 applySuccess(store, spec)
89 svc := apps.NewService(store, zerowrap.Default()).
90 WithDevicePolicies(map[string]domain.AppDevicePolicy{
91 "test_gpu": devicePolicy([]string{"blog"}, []string{"web"}),
92 })
93 _, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
94 require.NoError(t, err)
95
96 svc.SetDevicePolicies(map[string]domain.AppDevicePolicy{
97 "test_gpu": devicePolicy([]string{"other"}, []string{"web"}),
98 })
99 _, _, err = svc.Apply(context.Background(), spec, []byte("manifest"), false)
100 require.ErrorIs(t, err, domain.ErrDevicePolicy)
101}
102
103func TestApply_DeviceDryRunRefusedWithoutMutation(t *testing.T) {
104 store := outmocks.NewMockAppState(t)
105 spec := testSpec("blog")
106 spec.Services[0].Devices = []string{"test_gpu"}
107 svc := apps.NewService(store, zerowrap.Default())
108
109 _, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), true)
110 require.ErrorIs(t, err, domain.ErrDevicePolicy)
111 store.AssertNotCalled(t, "StageApply", context.Background(), spec.Name, uint64(0))
112}
113
114func TestApply_RejectsDuplicateCDIIDsAcrossDevices(t *testing.T) {
115 shared := "example.com/gpu=GPU-shared"
116 store := outmocks.NewMockAppState(t)
117 spec := testSpec("blog")
118 spec.Services[0].Devices = []string{"gpu-a", "gpu-b"}
119 svc := apps.NewService(store, zerowrap.Default()).
120 WithDevicePolicies(map[string]domain.AppDevicePolicy{
121 "gpu-a": {Name: "gpu-a", CDI: []string{shared}, AllowedApps: []string{"blog"}, AllowedServices: []string{"web"}},
122 "gpu-b": {Name: "gpu-b", CDI: []string{shared}, AllowedApps: []string{"blog"}, AllowedServices: []string{"web"}},
123 })
124
125 _, _, err := svc.Apply(context.Background(), spec, []byte("manifest"), false)
126
127 require.ErrorIs(t, err, domain.ErrDevicePolicy)
128 assert.NotContains(t, err.Error(), shared, "apply errors must never leak CDI IDs")
129}