Skip to content

Commit 365ff2f

Browse files
committed
Add tests for URLs
Signed-off-by: Nelo-T. Wallus <[email protected]> Signed-off-by: Nelo-T. Wallus <[email protected]>
1 parent 3420364 commit 365ff2f

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

test/e2e/server/url_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
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 server
18+
19+
import (
20+
"context"
21+
"io"
22+
"net/http"
23+
"net/url"
24+
"testing"
25+
"time"
26+
27+
"github.com/stretchr/testify/assert"
28+
"github.com/stretchr/testify/require"
29+
30+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33+
"k8s.io/apimachinery/pkg/runtime/schema"
34+
"k8s.io/apimachinery/pkg/util/wait"
35+
"k8s.io/client-go/dynamic"
36+
"k8s.io/client-go/rest"
37+
38+
kcpapiextensionsclientset "github.com/kcp-dev/client-go/apiextensions/client"
39+
kcpdynamic "github.com/kcp-dev/client-go/dynamic"
40+
41+
configcrds "github.com/kcp-dev/kcp/config/crds"
42+
"github.com/kcp-dev/kcp/sdk/apis/core"
43+
kcptesting "github.com/kcp-dev/kcp/sdk/testing"
44+
kcptestinghelpers "github.com/kcp-dev/kcp/sdk/testing/helpers"
45+
"github.com/kcp-dev/kcp/test/e2e/framework"
46+
)
47+
48+
func TestURLs(t *testing.T) {
49+
t.Parallel()
50+
framework.Suite(t, "control-plane")
51+
server := kcptesting.SharedKcpServer(t)
52+
53+
cfg := server.BaseConfig(t)
54+
55+
orgPath, _ := kcptesting.NewWorkspaceFixture(t, server, core.RootCluster.Path(), kcptesting.WithType(core.RootCluster.Path(), "organization"))
56+
57+
transport, err := rest.TransportFor(cfg)
58+
require.NoError(t, err)
59+
httpClient := http.Client{
60+
Transport: transport,
61+
}
62+
63+
testPaths := map[string]struct {
64+
path string
65+
expectedStatusCode int
66+
}{
67+
"a well-formed URL does not error": {
68+
path: orgPath.RequestPath() + "/apis/core.kcp.io/v1alpha1/logicalclusters/cluster/status",
69+
expectedStatusCode: http.StatusOK,
70+
},
71+
"a URL with two clusters does error": {
72+
path: orgPath.RequestPath() + orgPath.RequestPath() + "/apis/core.kcp.io/v1alpha1/logicalclusters/cluster/status",
73+
expectedStatusCode: http.StatusNotFound,
74+
},
75+
"a URL with three clusters does error": {
76+
path: orgPath.RequestPath() + orgPath.RequestPath() + orgPath.RequestPath() + "/apis/core.kcp.io/v1alpha1/logicalclusters/cluster/status",
77+
expectedStatusCode: http.StatusNotFound,
78+
},
79+
}
80+
81+
for testName, testPath := range testPaths {
82+
t.Run(testName, func(t *testing.T) {
83+
t.Parallel()
84+
85+
// TODO(ntnn): Replace with t.Context in go1.24
86+
ctx, cancel := context.WithCancel(context.Background())
87+
t.Cleanup(cancel)
88+
89+
u, err := url.Parse(cfg.Host)
90+
require.NoError(t, err)
91+
u.Path = testPath.path
92+
93+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
94+
require.NoError(t, err)
95+
96+
t.Logf("Testing URL: %s", req.URL.String())
97+
resp, err := httpClient.Do(req)
98+
require.NoError(t, err)
99+
defer resp.Body.Close()
100+
if !assert.Equal(t, testPath.expectedStatusCode, resp.StatusCode) {
101+
t.Logf("Expected status code %d, got %d", testPath.expectedStatusCode, resp.StatusCode)
102+
b, err := io.ReadAll(resp.Body)
103+
assert.NoError(t, err)
104+
t.Logf("Response body: %s", string(b))
105+
}
106+
})
107+
}
108+
}
109+
110+
func TestURLWithClusterKind(t *testing.T) {
111+
t.Parallel()
112+
framework.Suite(t, "control-plane")
113+
server := kcptesting.SharedKcpServer(t)
114+
cfg := server.BaseConfig(t)
115+
116+
for _, scope := range []apiextensionsv1.ResourceScope{
117+
apiextensionsv1.ClusterScoped,
118+
apiextensionsv1.NamespaceScoped,
119+
} {
120+
t.Run(string(scope), func(t *testing.T) {
121+
t.Parallel()
122+
123+
// TODO(ntnn): Replace with t.Context in go1.24
124+
ctx, cancel := context.WithCancel(context.Background())
125+
t.Cleanup(cancel)
126+
127+
orgPath, _ := kcptesting.NewWorkspaceFixture(t, server, core.RootCluster.Path(), kcptesting.WithType(core.RootCluster.Path(), "organization"))
128+
129+
crdClient := kcpapiextensionsclientset.NewForConfigOrDie(cfg)
130+
dynamicClient, err := kcpdynamic.NewForConfig(cfg)
131+
require.NoError(t, err)
132+
133+
t.Log("Install a CRD with kind Cluster")
134+
crd := &apiextensionsv1.CustomResourceDefinition{
135+
ObjectMeta: metav1.ObjectMeta{
136+
Name: "clusters.url.test",
137+
},
138+
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
139+
Group: "url.test",
140+
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
141+
{
142+
Name: "v1",
143+
Served: true,
144+
Storage: true,
145+
Schema: &apiextensionsv1.CustomResourceValidation{
146+
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
147+
Type: "object",
148+
},
149+
},
150+
Subresources: &apiextensionsv1.CustomResourceSubresources{
151+
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
152+
},
153+
},
154+
},
155+
Scope: scope,
156+
Names: apiextensionsv1.CustomResourceDefinitionNames{
157+
Plural: "clusters",
158+
Singular: "cluster",
159+
Kind: "Cluster",
160+
ListKind: "ClusterList",
161+
},
162+
},
163+
}
164+
err = configcrds.CreateSingle(ctx, crdClient.Cluster(orgPath).ApiextensionsV1().CustomResourceDefinitions(), crd)
165+
require.NoError(t, err)
166+
167+
t.Log("Create a resource to retrieve")
168+
gvr := schema.GroupVersionResource{
169+
Group: "url.test",
170+
Version: "v1",
171+
Resource: "clusters",
172+
}
173+
174+
obj := &unstructured.Unstructured{
175+
Object: map[string]interface{}{
176+
"apiVersion": gvr.Group + "/v1",
177+
"kind": "Cluster",
178+
"metadata": map[string]interface{}{
179+
"name": "test",
180+
},
181+
},
182+
}
183+
184+
var resourceInterface dynamic.ResourceInterface
185+
if scope == apiextensionsv1.ClusterScoped {
186+
resourceInterface = dynamicClient.Cluster(orgPath).Resource(gvr)
187+
} else {
188+
resourceInterface = dynamicClient.Cluster(orgPath).Resource(gvr).Namespace("default")
189+
}
190+
kcptestinghelpers.Eventually(t, func() (bool, string) {
191+
_, err = resourceInterface.Create(ctx, obj, metav1.CreateOptions{})
192+
if err != nil {
193+
return false, err.Error()
194+
}
195+
return true, ""
196+
}, wait.ForeverTestTimeout, time.Millisecond*100)
197+
198+
t.Log("Retrieve the resource")
199+
kcptestinghelpers.Eventually(t, func() (bool, string) {
200+
_, err = resourceInterface.Get(ctx, "test", metav1.GetOptions{})
201+
if err != nil {
202+
return false, err.Error()
203+
}
204+
return true, ""
205+
}, wait.ForeverTestTimeout, time.Millisecond*100)
206+
207+
t.Log("Retrieve the status subresource")
208+
kcptestinghelpers.Eventually(t, func() (bool, string) {
209+
_, err = resourceInterface.Get(ctx, "test", metav1.GetOptions{}, "status")
210+
if err != nil {
211+
return false, err.Error()
212+
}
213+
return true, ""
214+
}, wait.ForeverTestTimeout, time.Millisecond*100)
215+
})
216+
}
217+
}

0 commit comments

Comments
 (0)