Skip to content

Commit 1fc90f6

Browse files
author
Yehudit Kerido
committed
test(ws): Notebooks 2.0 // Backend // Add tests
Signed-off-by: Yehudit Kerido <[email protected]>
1 parent 2c3e75e commit 1fc90f6

File tree

5 files changed

+805
-0
lines changed

5 files changed

+805
-0
lines changed

workspaces/backend/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/kubeflow/notebooks/workspaces/controller v0.0.0
1010
github.com/onsi/ginkgo/v2 v2.19.0
1111
github.com/onsi/gomega v1.33.1
12+
github.com/stretchr/testify v1.9.0
1213
github.com/swaggo/http-swagger/v2 v2.0.2
1314
github.com/swaggo/swag v1.16.4
1415
k8s.io/api v0.31.0
@@ -60,6 +61,7 @@ require (
6061
github.com/modern-go/reflect2 v1.0.2 // indirect
6162
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
6263
github.com/pkg/errors v0.9.1 // indirect
64+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
6365
github.com/prometheus/client_golang v1.19.1 // indirect
6466
github.com/prometheus/client_model v0.6.1 // indirect
6567
github.com/prometheus/common v0.55.0 // indirect
@@ -93,6 +95,7 @@ require (
9395
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
9496
google.golang.org/grpc v1.65.0 // indirect
9597
google.golang.org/protobuf v1.34.2 // indirect
98+
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
9699
gopkg.in/inf.v0 v0.9.1 // indirect
97100
gopkg.in/yaml.v2 v2.4.0 // indirect
98101
gopkg.in/yaml.v3 v3.0.1 // indirect
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
Copyright 2024.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package auth
18+
19+
import (
20+
"net/http"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/runtime/schema"
28+
"k8s.io/apiserver/pkg/authentication/user"
29+
)
30+
31+
func TestNewRequestAuthenticator(t *testing.T) {
32+
const (
33+
userHeader = "X-User"
34+
groupsHeader = "X-Groups"
35+
userPrefix = "service-account:"
36+
)
37+
38+
t.Run("should authenticate user without prefix", func(t *testing.T) {
39+
// Create an authenticator with an empty prefix
40+
authn, err := NewRequestAuthenticator(userHeader, "", groupsHeader)
41+
require.NoError(t, err)
42+
43+
// Create a request with user and group headers
44+
req, _ := http.NewRequest("GET", "/", http.NoBody)
45+
req.Header.Set(userHeader, "test-user")
46+
req.Header.Set(groupsHeader, "group-a,group-b")
47+
48+
// Authenticate the request
49+
resp, ok, err := authn.AuthenticateRequest(req)
50+
51+
// Assert the results
52+
assert.NoError(t, err)
53+
assert.True(t, ok)
54+
require.NotNil(t, resp)
55+
assert.Equal(t, "test-user", resp.User.GetName())
56+
assert.ElementsMatch(t, []string{"group-a,group-b"}, resp.User.GetGroups())
57+
})
58+
59+
t.Run("should authenticate user and trim prefix", func(t *testing.T) {
60+
// Create an authenticator with a prefix
61+
authn, err := NewRequestAuthenticator(userHeader, userPrefix, groupsHeader)
62+
require.NoError(t, err)
63+
64+
// Create a request where the user header has the prefix
65+
req, _ := http.NewRequest("GET", "/", http.NoBody)
66+
req.Header.Set(userHeader, userPrefix+"test-user")
67+
req.Header.Set(groupsHeader, "group-c")
68+
69+
// Authenticate the request
70+
resp, ok, err := authn.AuthenticateRequest(req)
71+
72+
// Assert the results
73+
assert.NoError(t, err)
74+
assert.True(t, ok)
75+
require.NotNil(t, resp)
76+
// The key assertion: the prefix should be trimmed
77+
assert.Equal(t, "test-user", resp.User.GetName())
78+
assert.Equal(t, []string{"group-c"}, resp.User.GetGroups())
79+
})
80+
81+
t.Run("should authenticate user when prefix is configured but not present", func(t *testing.T) {
82+
// Create an authenticator with a prefix
83+
authn, err := NewRequestAuthenticator(userHeader, userPrefix, groupsHeader)
84+
require.NoError(t, err)
85+
86+
// Create a request where the user header does NOT have the prefix
87+
req, _ := http.NewRequest("GET", "/", http.NoBody)
88+
req.Header.Set(userHeader, "another-user")
89+
90+
// Authenticate the request
91+
resp, ok, err := authn.AuthenticateRequest(req)
92+
93+
// Assert the results
94+
assert.NoError(t, err)
95+
assert.True(t, ok)
96+
require.NotNil(t, resp)
97+
// The username should be unchanged
98+
assert.Equal(t, "another-user", resp.User.GetName())
99+
})
100+
101+
t.Run("should handle unauthenticated request", func(t *testing.T) {
102+
// Create an authenticator
103+
authn, err := NewRequestAuthenticator(userHeader, userPrefix, groupsHeader)
104+
require.NoError(t, err)
105+
106+
// Create a request WITHOUT the required user header
107+
req, _ := http.NewRequest("GET", "/", http.NoBody)
108+
req.Header.Set(groupsHeader, "some-group")
109+
110+
// Authenticate the request
111+
resp, ok, err := authn.AuthenticateRequest(req)
112+
113+
// Assert the results
114+
assert.NoError(t, err)
115+
// The key assertion: ok should be false for an unauthenticated request
116+
assert.False(t, ok)
117+
assert.Nil(t, resp)
118+
})
119+
}
120+
121+
// mockObject is a helper struct that implements client.Object for testing.
122+
// It allows us to create fake Kubernetes objects to pass to our functions.
123+
type mockObject struct {
124+
metav1.ObjectMeta
125+
metav1.TypeMeta
126+
}
127+
128+
// GetObjectKind is required to implement the runtime.Object interface.
129+
func (m *mockObject) GetObjectKind() schema.ObjectKind { return &m.TypeMeta }
130+
131+
// DeepCopyObject is required to implement the runtime.Object interface.
132+
func (m *mockObject) DeepCopyObject() runtime.Object {
133+
return &mockObject{
134+
ObjectMeta: *m.ObjectMeta.DeepCopy(),
135+
TypeMeta: m.TypeMeta,
136+
}
137+
}
138+
139+
func TestNewResourcePolicy(t *testing.T) {
140+
t.Run("should create policy for a namespaced resource", func(t *testing.T) {
141+
// Arrange: Create a mock namespaced object
142+
mock := &mockObject{}
143+
mock.SetName("my-deployment")
144+
mock.SetNamespace("my-namespace")
145+
mock.SetGroupVersionKind(schema.GroupVersionKind{
146+
Group: "apps",
147+
Version: "v1",
148+
Kind: "Deployment",
149+
})
150+
151+
// Act: Create the policy
152+
policy := NewResourcePolicy(ResourceVerbGet, mock)
153+
154+
// Assert: Verify the policy fields are correct
155+
require.NotNil(t, policy)
156+
assert.Equal(t, ResourceVerbGet, policy.Verb)
157+
assert.Equal(t, "apps", policy.Group)
158+
assert.Equal(t, "v1", policy.Version)
159+
assert.Equal(t, "Deployment", policy.Kind)
160+
assert.Equal(t, "my-namespace", policy.Namespace)
161+
assert.Equal(t, "my-deployment", policy.Name)
162+
})
163+
164+
t.Run("should create policy for a cluster-scoped resource", func(t *testing.T) {
165+
// Arrange: Create a mock cluster-scoped object (no namespace)
166+
mock := &mockObject{}
167+
mock.SetName("my-cluster-role")
168+
mock.SetGroupVersionKind(schema.GroupVersionKind{
169+
Group: "rbac.authorization.k8s.io",
170+
Version: "v1",
171+
Kind: "ClusterRole",
172+
})
173+
174+
// Act: Create the policy
175+
policy := NewResourcePolicy(ResourceVerbDelete, mock)
176+
177+
// Assert: Verify the policy fields, ensuring namespace is empty
178+
require.NotNil(t, policy)
179+
assert.Equal(t, ResourceVerbDelete, policy.Verb)
180+
assert.Equal(t, "rbac.authorization.k8s.io", policy.Group)
181+
assert.Equal(t, "ClusterRole", policy.Kind)
182+
assert.Equal(t, "my-cluster-role", policy.Name)
183+
assert.Empty(t, policy.Namespace, "Namespace should be empty for cluster-scoped resources")
184+
})
185+
}
186+
187+
func TestAttributesFor(t *testing.T) {
188+
userInfo := &user.DefaultInfo{
189+
Name: "test-user",
190+
Groups: []string{"group-a", "system:authenticated"},
191+
}
192+
193+
t.Run("should create attributes for a specific resource", func(t *testing.T) {
194+
// Arrange: Create a policy for a specific resource
195+
policy := &ResourcePolicy{
196+
Verb: ResourceVerbUpdate,
197+
Group: "kubeflow.org",
198+
Version: "v1beta1",
199+
Kind: "Workspace",
200+
Namespace: "user-namespace",
201+
Name: "my-workspace",
202+
}
203+
204+
// Act: Generate attributes from the policy
205+
attrs := policy.AttributesFor(userInfo)
206+
207+
// Assert: Verify all attributes are correctly set
208+
require.NotNil(t, attrs)
209+
assert.Equal(t, userInfo, attrs.GetUser())
210+
assert.Equal(t, "update", attrs.GetVerb())
211+
assert.Equal(t, "user-namespace", attrs.GetNamespace())
212+
assert.Equal(t, "kubeflow.org", attrs.GetAPIGroup())
213+
assert.Equal(t, "v1beta1", attrs.GetAPIVersion())
214+
assert.Equal(t, "Workspace", attrs.GetResource())
215+
assert.Equal(t, "my-workspace", attrs.GetName())
216+
assert.True(t, attrs.IsResourceRequest())
217+
})
218+
219+
t.Run("should create attributes for a collection of resources", func(t *testing.T) {
220+
// Arrange: Create a policy for a 'list' operation, which doesn't have a specific name
221+
policy := &ResourcePolicy{
222+
Verb: ResourceVerbList,
223+
Group: "kubeflow.org",
224+
Version: "v1beta1",
225+
Kind: "Workspace",
226+
Namespace: "user-namespace",
227+
Name: "", // Name is empty for list operations
228+
}
229+
230+
// Act: Generate attributes
231+
attrs := policy.AttributesFor(userInfo)
232+
233+
// Assert: Verify attributes, ensuring name is empty
234+
require.NotNil(t, attrs)
235+
assert.Equal(t, userInfo, attrs.GetUser())
236+
assert.Equal(t, "list", attrs.GetVerb())
237+
assert.Equal(t, "user-namespace", attrs.GetNamespace())
238+
assert.Empty(t, attrs.GetName(), "Name should be empty for a list verb")
239+
assert.True(t, attrs.IsResourceRequest())
240+
})
241+
}

0 commit comments

Comments
 (0)