This is an automated email from the ASF dual-hosted git repository.

ronething pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 501b4e89 test: add e2e test case for webhook (#2585)
501b4e89 is described below

commit 501b4e89f2567a8166660131d36511824cf13375
Author: Ashing Zheng <[email protected]>
AuthorDate: Tue Sep 30 14:24:03 2025 +0800

    test: add e2e test case for webhook (#2585)
    
    Signed-off-by: Ashing Zheng <[email protected]>
---
 test/e2e/apisix/e2e_test.go                        |   1 +
 test/e2e/framework/manifests/webhook.yaml          | 194 +++++++++++++++++++--
 test/e2e/webhook/apisixconsumer.go                 |  88 ++++++++++
 test/e2e/webhook/apisixroute.go                    | 117 +++++++++++++
 test/e2e/webhook/apisixtls.go                      | 111 ++++++++++++
 test/e2e/webhook/consumer.go                       |  93 ++++++++++
 .../{gatewayapi/webhook.go => webhook/gateway.go}  |   2 +-
 test/e2e/webhook/gatewayproxy.go                   | 129 ++++++++++++++
 .../{apisix/e2e_test.go => webhook/grpcroute.go}   |  40 ++---
 test/e2e/webhook/helpers.go                        | 116 ++++++++++++
 .../{apisix/e2e_test.go => webhook/httproute.go}   |  40 ++---
 .../e2e/{ingress/webhook.go => webhook/ingress.go} |   2 +-
 .../ingressclass.go}                               |   2 +-
 test/e2e/webhook/tcproute.go                       | 121 +++++++++++++
 14 files changed, 1000 insertions(+), 56 deletions(-)

diff --git a/test/e2e/apisix/e2e_test.go b/test/e2e/apisix/e2e_test.go
index 03fe0ca6..fde91636 100644
--- a/test/e2e/apisix/e2e_test.go
+++ b/test/e2e/apisix/e2e_test.go
@@ -30,6 +30,7 @@ import (
        _ "github.com/apache/apisix-ingress-controller/test/e2e/gatewayapi"
        _ "github.com/apache/apisix-ingress-controller/test/e2e/ingress"
        "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+       _ "github.com/apache/apisix-ingress-controller/test/e2e/webhook"
 )
 
 // TestAPISIXE2E runs e2e tests using the APISIX standalone mode
diff --git a/test/e2e/framework/manifests/webhook.yaml 
b/test/e2e/framework/manifests/webhook.yaml
index 432873c9..954ce72d 100644
--- a/test/e2e/framework/manifests/webhook.yaml
+++ b/test/e2e/framework/manifests/webhook.yaml
@@ -20,6 +20,174 @@ kind: ValidatingWebhookConfiguration
 metadata:
   name: test-webhook-{{ .Namespace }}
 webhooks:
+- name: vapisixconsumer-v2.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-apisix-apache-org-v2-apisixconsumer
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - apisix.apache.org
+      apiVersions:
+        - v2
+      resources:
+        - apisixconsumers
+  failurePolicy: Fail
+  sideEffects: None
+- name: vapisixroute-v2.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-apisix-apache-org-v2-apisixroute
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - apisix.apache.org
+      apiVersions:
+        - v2
+      resources:
+        - apisixroutes
+  failurePolicy: Fail
+  sideEffects: None
+- name: vapisixtls-v2.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-apisix-apache-org-v2-apisixtls
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - apisix.apache.org
+      apiVersions:
+        - v2
+      resources:
+        - apisixtlses
+  failurePolicy: Fail
+  sideEffects: None
+- name: vconsumer-v1alpha1.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-apisix-apache-org-v1alpha1-consumer
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - apisix.apache.org
+      apiVersions:
+        - v1alpha1
+      resources:
+        - consumers
+  failurePolicy: Fail
+  sideEffects: None
+- name: vgateway-v1.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-gateway-networking-k8s-io-v1-gateway
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - gateway.networking.k8s.io
+      apiVersions:
+        - v1
+      resources:
+        - gateways
+  failurePolicy: Fail
+  sideEffects: None
+- name: vgatewayproxy-v1alpha1.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-apisix-apache-org-v1alpha1-gatewayproxy
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - apisix.apache.org
+      apiVersions:
+        - v1alpha1
+      resources:
+        - gatewayproxies
+  failurePolicy: Fail
+  sideEffects: None
+- name: vgrpcroute-v1.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-gateway-networking-k8s-io-v1-grpcroute
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - gateway.networking.k8s.io
+      apiVersions:
+        - v1
+      resources:
+        - grpcroutes
+  failurePolicy: Fail
+  sideEffects: None
+- name: vhttproute-v1.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-gateway-networking-k8s-io-v1-httproute
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - gateway.networking.k8s.io
+      apiVersions:
+        - v1
+      resources:
+        - httproutes
+  failurePolicy: Fail
+  sideEffects: None
 - name: vingress-v1.kb.io
   clientConfig:
     service:
@@ -30,15 +198,15 @@ webhooks:
   admissionReviewVersions:
     - v1
   rules:
-  - operations:
-      - CREATE
-      - UPDATE
-    apiGroups:
-      - networking.k8s.io
-    apiVersions:
-      - v1
-    resources:
-      - ingresses
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - networking.k8s.io
+      apiVersions:
+        - v1
+      resources:
+        - ingresses
   failurePolicy: Fail
   sideEffects: None
 - name: vingressclass-v1.kb.io
@@ -62,12 +230,12 @@ webhooks:
         - ingressclasses
   failurePolicy: Fail
   sideEffects: None
-- name: vgateway-v1.kb.io
+- name: vtcproute-v1alpha2.kb.io
   clientConfig:
     service:
       name: webhook-service
       namespace: {{ .Namespace }}
-      path: /validate-gateway-networking-k8s-io-v1-gateway
+      path: /validate-gateway-networking-k8s-io-v1alpha2-tcproute
     caBundle: {{ .CABundle }}
   admissionReviewVersions:
     - v1
@@ -78,8 +246,8 @@ webhooks:
       apiGroups:
         - gateway.networking.k8s.io
       apiVersions:
-        - v1
+        - v1alpha2
       resources:
-        - gateways
+        - tcproutes
   failurePolicy: Fail
   sideEffects: None
diff --git a/test/e2e/webhook/apisixconsumer.go 
b/test/e2e/webhook/apisixconsumer.go
new file mode 100644
index 00000000..7aa1a256
--- /dev/null
+++ b/test/e2e/webhook/apisixconsumer.go
@@ -0,0 +1,88 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package webhook
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test ApisixConsumer Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "apisixconsumer-webhook-test",
+               EnableWebhook: true,
+       })
+
+       BeforeEach(func() {
+               By("creating GatewayProxy")
+               err := s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("creating IngressClass")
+               err = 
s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
+               Expect(err).NotTo(HaveOccurred(), "creating IngressClass")
+               time.Sleep(5 * time.Second)
+       })
+
+       It("should warn on missing authentication secrets", func() {
+               missingSecret := "missing-basic-secret"
+               consumerName := "webhook-apisixconsumer"
+               consumerYAML := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixConsumer
+metadata:
+  name: %s
+  namespace: %s
+spec:
+  ingressClassName: %s
+  authParameter:
+    basicAuth:
+      secretRef:
+        name: %s
+`
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(consumerYAML, consumerName, 
s.Namespace(), s.Namespace(), missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
+
+               By("creating referenced secret")
+               secretYAML := fmt.Sprintf(`
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+stringData:
+  username: demo
+  password: demo
+`, missingSecret)
+               err = s.CreateResourceFromString(secretYAML)
+               Expect(err).NotTo(HaveOccurred(), "creating basic auth secret")
+
+               time.Sleep(2 * time.Second)
+
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(consumerYAML, consumerName, 
s.Namespace(), s.Namespace(), missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
+       })
+})
diff --git a/test/e2e/webhook/apisixroute.go b/test/e2e/webhook/apisixroute.go
new file mode 100644
index 00000000..51904f43
--- /dev/null
+++ b/test/e2e/webhook/apisixroute.go
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package webhook
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test ApisixRoute Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "apisixroute-webhook-test",
+               EnableWebhook: true,
+       })
+
+       BeforeEach(func() {
+               By("creating GatewayProxy")
+               err := s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("creating IngressClass")
+               err = 
s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
+               Expect(err).NotTo(HaveOccurred(), "creating IngressClass")
+               time.Sleep(5 * time.Second)
+       })
+
+       It("should warn on missing service or secret references", func() {
+               missingService := "missing-backend"
+               missingSecret := "missing-plugin-secret"
+               routeName := "webhook-apisixroute"
+               routeYAML := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+  name: %s
+  namespace: %s
+spec:
+  ingressClassName: %s
+  http:
+  - name: rule-webhook
+    match:
+      hosts:
+      - webhook.example.com
+      paths:
+      - /webhook
+    backends:
+    - serviceName: %s
+      servicePort: 80
+    plugins:
+    - name: echo
+      enable: true
+      secretRef: %s
+`
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(routeYAML, routeName, 
s.Namespace(), s.Namespace(), missingService, missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
+
+               By("creating referenced Service and Secret")
+               serviceYAML := fmt.Sprintf(`
+apiVersion: v1
+kind: Service
+metadata:
+  name: %s
+spec:
+  selector:
+    app: placeholder
+  ports:
+  - name: http
+    port: 80
+    targetPort: 80
+  type: ClusterIP
+`, missingService)
+               err = s.CreateResourceFromString(serviceYAML)
+               Expect(err).NotTo(HaveOccurred(), "creating backend service 
placeholder")
+
+               secretYAML := fmt.Sprintf(`
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+stringData:
+  config: enabled
+`, missingSecret)
+               err = s.CreateResourceFromString(secretYAML)
+               Expect(err).NotTo(HaveOccurred(), "creating plugin secret 
placeholder")
+
+               time.Sleep(2 * time.Second)
+
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(routeYAML, routeName, 
s.Namespace(), s.Namespace(), missingService, missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
+       })
+})
diff --git a/test/e2e/webhook/apisixtls.go b/test/e2e/webhook/apisixtls.go
new file mode 100644
index 00000000..08defed9
--- /dev/null
+++ b/test/e2e/webhook/apisixtls.go
@@ -0,0 +1,111 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package webhook
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test ApisixTls Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "apisixtls-webhook-test",
+               EnableWebhook: true,
+       })
+
+       BeforeEach(func() {
+               By("creating GatewayProxy")
+               err := s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("creating IngressClass")
+               err = 
s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
+               Expect(err).NotTo(HaveOccurred(), "creating IngressClass")
+               time.Sleep(5 * time.Second)
+       })
+
+       It("should warn on missing TLS secrets", func() {
+               serverSecret := "missing-server-tls"
+               clientSecret := "missing-client-ca"
+               tlsName := "webhook-apisixtls"
+               tlsYAML := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixTls
+metadata:
+  name: %s
+  namespace: %s
+spec:
+  ingressClassName: %s
+  hosts:
+  - webhook.example.com
+  secret:
+    name: %s
+    namespace: %s
+  client:
+    caSecret:
+      name: %s
+      namespace: %s
+`
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(tlsYAML, tlsName, 
s.Namespace(), s.Namespace(), serverSecret, s.Namespace(), clientSecret, 
s.Namespace()))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), serverSecret)))
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), clientSecret)))
+
+               By("creating referenced TLS secrets")
+               serverSecretYAML := fmt.Sprintf(`
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+  namespace: %s
+type: kubernetes.io/tls
+stringData:
+  tls.crt: dummy-cert
+  tls.key: dummy-key
+`, serverSecret, s.Namespace())
+               err = s.CreateResourceFromString(serverSecretYAML)
+               Expect(err).NotTo(HaveOccurred(), "creating server TLS secret")
+
+               clientSecretYAML := fmt.Sprintf(`
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+  namespace: %s
+type: Opaque
+stringData:
+  ca.crt: dummy-ca
+`, clientSecret, s.Namespace())
+               err = s.CreateResourceFromString(clientSecretYAML)
+               Expect(err).NotTo(HaveOccurred(), "creating client CA secret")
+
+               time.Sleep(2 * time.Second)
+
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(tlsYAML, tlsName, 
s.Namespace(), s.Namespace(), serverSecret, s.Namespace(), clientSecret, 
s.Namespace()))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), serverSecret)))
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), clientSecret)))
+       })
+})
diff --git a/test/e2e/webhook/consumer.go b/test/e2e/webhook/consumer.go
new file mode 100644
index 00000000..676adbb8
--- /dev/null
+++ b/test/e2e/webhook/consumer.go
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package webhook
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test Consumer Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "consumer-webhook-test",
+               EnableWebhook: true,
+       })
+
+       BeforeEach(func() {
+               By("creating GatewayProxy")
+               err := s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("creating GatewayClass")
+               err = s.CreateResourceFromString(s.GetGatewayClassYaml())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayClass")
+               time.Sleep(2 * time.Second)
+
+               By("creating Gateway")
+               err = s.CreateResourceFromString(s.GetGatewayYaml())
+               Expect(err).NotTo(HaveOccurred(), "creating Gateway")
+               time.Sleep(5 * time.Second)
+       })
+
+       It("should warn on missing secret references", func() {
+               missingSecret := "missing-consumer-secret"
+               consumerName := "webhook-consumer"
+               gatewayName := s.Namespace()
+               consumerYAML := `
+apiVersion: apisix.apache.org/v1alpha1
+kind: Consumer
+metadata:
+  name: %s
+spec:
+  gatewayRef:
+    name: %s
+  credentials:
+  - type: jwt-auth
+    secretRef:
+      name: %s
+`
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(consumerYAML, consumerName, 
gatewayName, missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
+
+               By("creating referenced secret")
+               secretYAML := fmt.Sprintf(`
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+stringData:
+  token: %s
+`, missingSecret, s.AdminKey())
+               err = s.CreateResourceFromString(secretYAML)
+               Expect(err).NotTo(HaveOccurred(), "creating consumer secret")
+
+               time.Sleep(2 * time.Second)
+
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(consumerYAML, consumerName, 
gatewayName, missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
+       })
+})
diff --git a/test/e2e/gatewayapi/webhook.go b/test/e2e/webhook/gateway.go
similarity index 99%
rename from test/e2e/gatewayapi/webhook.go
rename to test/e2e/webhook/gateway.go
index 92f65f93..09bfc3ce 100644
--- a/test/e2e/gatewayapi/webhook.go
+++ b/test/e2e/webhook/gateway.go
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package gatewayapi
+package webhook
 
 import (
        "fmt"
diff --git a/test/e2e/webhook/gatewayproxy.go b/test/e2e/webhook/gatewayproxy.go
new file mode 100644
index 00000000..6b1f6189
--- /dev/null
+++ b/test/e2e/webhook/gatewayproxy.go
@@ -0,0 +1,129 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package webhook
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test GatewayProxy Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "gatewayproxy-webhook-test",
+               EnableWebhook: true,
+       })
+
+       It("should warn on missing service or secret references", func() {
+               missingService := "missing-control-plane"
+               missingSecret := "missing-admin-secret"
+               gpName := "webhook-gateway-proxy"
+               gpWithSecrets := `
+apiVersion: apisix.apache.org/v1alpha1
+kind: GatewayProxy
+metadata:
+  name: %s
+spec:
+  provider:
+    type: ControlPlane
+    controlPlane:
+      service:
+        name: %s
+        port: 9180
+      auth:
+        type: AdminKey
+        adminKey:
+          valueFrom:
+            secretKeyRef:
+              name: %s
+              key: token
+`
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName, 
missingService, missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Secret '%s/%s' not found", s.Namespace(), missingSecret)))
+
+               By("creating the referenced Service and Secret without the 
required key")
+               serviceYAML := fmt.Sprintf(`
+apiVersion: v1
+kind: Service
+metadata:
+  name: %s
+spec:
+  selector:
+    app: placeholder
+  ports:
+  - name: admin
+    port: 9180
+    targetPort: 9180
+  type: ClusterIP
+`, missingService)
+               err = s.CreateResourceFromString(serviceYAML)
+               Expect(err).NotTo(HaveOccurred(), "creating placeholder 
service")
+
+               secretWithoutKey := fmt.Sprintf(`
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+stringData:
+  another: value
+`, missingSecret)
+               err = s.CreateResourceFromString(secretWithoutKey)
+               Expect(err).NotTo(HaveOccurred(), "creating placeholder secret 
without token key")
+
+               time.Sleep(2 * time.Second)
+
+               By("delete and reapply the GatewayProxy, because gatewayproxy 
has no change")
+               err = s.DeleteResource("GatewayProxy", gpName)
+               Expect(err).ShouldNot(HaveOccurred())
+
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName, 
missingService, missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: Secret 
key 'token' not found in Secret '%s/%s'", s.Namespace(), missingSecret)))
+
+               By("updating the Secret to include the expected key")
+               secretWithKey := fmt.Sprintf(`
+apiVersion: v1
+kind: Secret
+metadata:
+  name: %s
+stringData:
+  token: %s
+`, missingSecret, s.AdminKey())
+               err = s.CreateResourceFromString(secretWithKey)
+               Expect(err).NotTo(HaveOccurred(), "adding token key to secret")
+
+               time.Sleep(2 * time.Second)
+
+               By("delete and reapply the GatewayProxy, because gatewayproxy 
has no change")
+               err = s.DeleteResource("GatewayProxy", gpName)
+               Expect(err).ShouldNot(HaveOccurred())
+
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gpWithSecrets, gpName, 
missingService, missingSecret))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Secret key 'token' not found in Secret '%s/%s'", s.Namespace(), missingSecret)))
+       })
+})
diff --git a/test/e2e/apisix/e2e_test.go b/test/e2e/webhook/grpcroute.go
similarity index 53%
copy from test/e2e/apisix/e2e_test.go
copy to test/e2e/webhook/grpcroute.go
index 03fe0ca6..0954c969 100644
--- a/test/e2e/apisix/e2e_test.go
+++ b/test/e2e/webhook/grpcroute.go
@@ -15,32 +15,32 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package apisix
+package webhook
 
 import (
-       "fmt"
-       "testing"
-
        . "github.com/onsi/ginkgo/v2"
-       . "github.com/onsi/gomega"
 
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/crds/v1alpha1"
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/crds/v2"
-       "github.com/apache/apisix-ingress-controller/test/e2e/framework"
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/gatewayapi"
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/ingress"
        "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
 )
 
-// TestAPISIXE2E runs e2e tests using the APISIX standalone mode
-func TestAPISIXE2E(t *testing.T) {
-       RegisterFailHandler(Fail)
-       // init framework
-       _ = framework.NewFramework()
+var _ = Describe("Test GRPCRoute Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "grpcroute-webhook-test",
+               EnableWebhook: true,
+       })
 
-       // init newDeployer function
-       scaffold.NewDeployer = scaffold.NewAPISIXDeployer
+       BeforeEach(func() {
+               setupGatewayResources(s)
+       })
 
-       _, _ = fmt.Fprintf(GinkgoWriter, "Starting APISIX standalone e2e 
suite\n")
-       RunSpecs(t, "apisix standalone e2e suite")
-}
+       It("should warn on missing backend services", func() {
+               verifyMissingBackendWarnings(s, routeWebhookTestCase{
+                       routeKind:       "GRPCRoute",
+                       routeName:       "webhook-grpcroute",
+                       missingService:  "missing-grpc-backend",
+                       mirrorService:   "missing-grpc-mirror",
+                       servicePortName: "grpc",
+                       servicePort:     8080,
+               })
+       })
+})
diff --git a/test/e2e/webhook/helpers.go b/test/e2e/webhook/helpers.go
new file mode 100644
index 00000000..696e81df
--- /dev/null
+++ b/test/e2e/webhook/helpers.go
@@ -0,0 +1,116 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package webhook
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+type routeWebhookTestCase struct {
+       routeKind       string
+       routeName       string
+       missingService  string
+       mirrorService   string
+       servicePortName string
+       servicePort     int
+}
+
+func setupGatewayResources(s *scaffold.Scaffold) {
+       By("creating GatewayProxy")
+       err := s.CreateResourceFromString(s.GetGatewayProxySpec())
+       Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+       time.Sleep(5 * time.Second)
+
+       By("creating GatewayClass")
+       err = s.CreateResourceFromString(s.GetGatewayClassYaml())
+       Expect(err).NotTo(HaveOccurred(), "creating GatewayClass")
+       time.Sleep(2 * time.Second)
+
+       By("creating Gateway")
+       err = s.CreateResourceFromString(s.GetGatewayYaml())
+       Expect(err).NotTo(HaveOccurred(), "creating Gateway")
+       time.Sleep(5 * time.Second)
+}
+
+func verifyMissingBackendWarnings(s *scaffold.Scaffold, tc 
routeWebhookTestCase) {
+       gatewayName := s.Namespace()
+       routeYAML := fmt.Sprintf(`
+apiVersion: gateway.networking.k8s.io/v1
+kind: %s
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+  rules:
+  - backendRefs:
+    - name: %s
+      port: %d
+    filters:
+    - type: RequestMirror
+      requestMirror:
+        backendRef:
+          name: %s
+          port: %d
+`, tc.routeKind, tc.routeName, gatewayName, tc.missingService, tc.servicePort, 
tc.mirrorService, tc.servicePort)
+
+       missingBackendWarning := fmt.Sprintf("Warning: Referenced Service 
'%s/%s' not found", gatewayName, tc.missingService)
+       mirrorBackendWarning := fmt.Sprintf("Warning: Referenced Service 
'%s/%s' not found", gatewayName, tc.mirrorService)
+
+       output, err := s.CreateResourceFromStringAndGetOutput(routeYAML)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(output).To(ContainSubstring(missingBackendWarning))
+       Expect(output).To(ContainSubstring(mirrorBackendWarning))
+
+       By(fmt.Sprintf("creating referenced backend services for %s", 
tc.routeKind))
+       serviceYAML := `
+apiVersion: v1
+kind: Service
+metadata:
+  name: %s
+spec:
+  selector:
+    app: placeholder
+  ports:
+  - name: %s
+    port: %d
+    targetPort: %d
+  type: ClusterIP
+`
+
+       backendService := fmt.Sprintf(serviceYAML, tc.missingService, 
tc.servicePortName, tc.servicePort, tc.servicePort)
+       err = s.CreateResourceFromString(backendService)
+       Expect(err).NotTo(HaveOccurred(), "creating primary backend service")
+
+       mirrorService := fmt.Sprintf(serviceYAML, tc.mirrorService, 
tc.servicePortName, tc.servicePort, tc.servicePort)
+       err = s.CreateResourceFromString(mirrorService)
+       Expect(err).NotTo(HaveOccurred(), "creating mirror backend service")
+
+       time.Sleep(2 * time.Second)
+
+       output, err = s.CreateResourceFromStringAndGetOutput(routeYAML)
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(output).NotTo(ContainSubstring(missingBackendWarning))
+       Expect(output).NotTo(ContainSubstring(mirrorBackendWarning))
+}
diff --git a/test/e2e/apisix/e2e_test.go b/test/e2e/webhook/httproute.go
similarity index 53%
copy from test/e2e/apisix/e2e_test.go
copy to test/e2e/webhook/httproute.go
index 03fe0ca6..240d7686 100644
--- a/test/e2e/apisix/e2e_test.go
+++ b/test/e2e/webhook/httproute.go
@@ -15,32 +15,32 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package apisix
+package webhook
 
 import (
-       "fmt"
-       "testing"
-
        . "github.com/onsi/ginkgo/v2"
-       . "github.com/onsi/gomega"
 
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/crds/v1alpha1"
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/crds/v2"
-       "github.com/apache/apisix-ingress-controller/test/e2e/framework"
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/gatewayapi"
-       _ "github.com/apache/apisix-ingress-controller/test/e2e/ingress"
        "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
 )
 
-// TestAPISIXE2E runs e2e tests using the APISIX standalone mode
-func TestAPISIXE2E(t *testing.T) {
-       RegisterFailHandler(Fail)
-       // init framework
-       _ = framework.NewFramework()
+var _ = Describe("Test HTTPRoute Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "httproute-webhook-test",
+               EnableWebhook: true,
+       })
 
-       // init newDeployer function
-       scaffold.NewDeployer = scaffold.NewAPISIXDeployer
+       BeforeEach(func() {
+               setupGatewayResources(s)
+       })
 
-       _, _ = fmt.Fprintf(GinkgoWriter, "Starting APISIX standalone e2e 
suite\n")
-       RunSpecs(t, "apisix standalone e2e suite")
-}
+       It("should warn on missing backend services", func() {
+               verifyMissingBackendWarnings(s, routeWebhookTestCase{
+                       routeKind:       "HTTPRoute",
+                       routeName:       "webhook-httproute",
+                       missingService:  "missing-http-backend",
+                       mirrorService:   "missing-http-mirror",
+                       servicePortName: "http",
+                       servicePort:     80,
+               })
+       })
+})
diff --git a/test/e2e/ingress/webhook.go b/test/e2e/webhook/ingress.go
similarity index 99%
rename from test/e2e/ingress/webhook.go
rename to test/e2e/webhook/ingress.go
index 310b6629..37608fb2 100644
--- a/test/e2e/ingress/webhook.go
+++ b/test/e2e/webhook/ingress.go
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package ingress
+package webhook
 
 import (
        "fmt"
diff --git a/test/e2e/ingress/ingressclass_webhook.go 
b/test/e2e/webhook/ingressclass.go
similarity index 99%
rename from test/e2e/ingress/ingressclass_webhook.go
rename to test/e2e/webhook/ingressclass.go
index 9c1f3174..8192a51b 100644
--- a/test/e2e/ingress/ingressclass_webhook.go
+++ b/test/e2e/webhook/ingressclass.go
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package ingress
+package webhook
 
 import (
        "fmt"
diff --git a/test/e2e/webhook/tcproute.go b/test/e2e/webhook/tcproute.go
new file mode 100644
index 00000000..de226a33
--- /dev/null
+++ b/test/e2e/webhook/tcproute.go
@@ -0,0 +1,121 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package webhook
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test TCPRoute Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "tcproute-webhook-test",
+               EnableWebhook: true,
+       })
+
+       const tcpGateway = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: %s
+spec:
+  gatewayClassName: %s
+  listeners:
+  - name: tcp
+    protocol: TCP
+    port: 9000
+    allowedRoutes:
+      kinds:
+      - kind: TCPRoute
+  infrastructure:
+    parametersRef:
+      group: apisix.apache.org
+      kind: GatewayProxy
+      name: apisix-proxy-config
+`
+
+       BeforeEach(func() {
+               By("creating GatewayProxy")
+               err := s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("creating GatewayClass")
+               err = s.CreateResourceFromString(s.GetGatewayClassYaml())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayClass")
+               time.Sleep(2 * time.Second)
+
+               By("creating Gateway with TCP listener")
+               err = s.CreateResourceFromString(fmt.Sprintf(tcpGateway, 
s.Namespace(), s.Namespace()))
+               Expect(err).NotTo(HaveOccurred(), "creating TCP-capable 
Gateway")
+               time.Sleep(5 * time.Second)
+       })
+
+       It("should warn on missing backend services", func() {
+               missingService := "missing-tcp-backend"
+               routeName := "webhook-tcproute"
+               gatewayName := s.Namespace()
+               routeYAML := `
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: TCPRoute
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+    sectionName: tcp
+  rules:
+  - backendRefs:
+    - name: %s
+      port: 80
+`
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(routeYAML, routeName, 
gatewayName, missingService))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
+
+               By("creating referenced backend service")
+               backendService := fmt.Sprintf(`
+apiVersion: v1
+kind: Service
+metadata:
+  name: %s
+spec:
+  selector:
+    app: placeholder
+  ports:
+  - name: tcp
+    port: 80
+    targetPort: 80
+  type: ClusterIP
+`, missingService)
+               err = s.CreateResourceFromString(backendService)
+               Expect(err).NotTo(HaveOccurred(), "creating tcp backend 
service")
+
+               time.Sleep(2 * time.Second)
+
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(routeYAML, routeName, 
gatewayName, missingService))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced Service '%s/%s' not found", s.Namespace(), missingService)))
+       })
+})


Reply via email to