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