2 // ========================LICENSE_START=================================
5 // Copyright (C) 2023-2024: OpenInfra Foundation Europe
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 // ========================LICENSE_END===================================
21 package publishservice
32 "github.com/deepmap/oapi-codegen/pkg/middleware"
33 "github.com/deepmap/oapi-codegen/pkg/testutil"
34 echo "github.com/labstack/echo/v4"
35 echomiddleware "github.com/labstack/echo/v4/middleware"
36 log "github.com/sirupsen/logrus"
37 "github.com/stretchr/testify/assert"
39 "oransc.org/nonrtric/servicemanager/internal/envreader"
40 "oransc.org/nonrtric/servicemanager/internal/kongclear"
42 "oransc.org/nonrtric/servicemanager/internal/common29122"
43 "oransc.org/nonrtric/servicemanager/internal/providermanagement"
44 provapi "oransc.org/nonrtric/servicemanager/internal/providermanagementapi"
45 publishapi "oransc.org/nonrtric/servicemanager/internal/publishserviceapi"
47 "oransc.org/nonrtric/capifcore"
48 mockKong "oransc.org/nonrtric/servicemanager/mockkong"
52 eServiceManager *echo.Echo
55 mockConfigReader *envreader.MockConfigReader
56 serviceManagerServer *httptest.Server
57 capifServer *httptest.Server
58 mockKongServer *httptest.Server
61 func TestMain(m *testing.M) {
74 func setupTest() error {
75 // Start the mock Kong server
77 mockKong.RegisterHandlers(eKong)
78 mockKongServer = httptest.NewServer(eKong)
80 // Parse the server URL
81 parsedMockKongURL, err := url.Parse(mockKongServer.URL)
83 log.Fatalf("error parsing mock Kong URL: %v", err)
87 // Extract the host and port
88 mockKongHost := parsedMockKongURL.Hostname()
89 mockKongControlPlanePort := parsedMockKongURL.Port()
91 eCapifWeb = echo.New()
92 capifcore.RegisterHandlers(eCapifWeb, nil, nil)
93 capifServer = httptest.NewServer(eCapifWeb)
95 // Parse the server URL
96 parsedCapifURL, err := url.Parse(capifServer.URL)
98 log.Fatalf("error parsing mock Kong URL: %v", err)
102 // Extract the host and port
103 capifHost := parsedCapifURL.Hostname()
104 capifPort := parsedCapifURL.Port()
106 // Set up the mock config reader with the desired configuration for testing
107 mockConfigReader = &envreader.MockConfigReader{
108 MockedConfig: map[string]string{
109 "KONG_DOMAIN": "kong",
110 "KONG_PROTOCOL": "http",
111 "KONG_CONTROL_PLANE_IPV4": mockKongHost,
112 "KONG_CONTROL_PLANE_PORT": mockKongControlPlanePort,
113 "KONG_DATA_PLANE_IPV4": "10.101.1.101",
114 "KONG_DATA_PLANE_PORT": "32080",
115 "CAPIF_PROTOCOL": "http",
116 "CAPIF_IPV4": capifHost,
117 "CAPIF_PORT": capifPort,
119 "SERVICE_MANAGER_PORT": "8095",
120 "TEST_SERVICE_IPV4": "10.101.1.101",
121 "TEST_SERVICE_PORT": "30951",
125 // Use the mock implementation for testing
126 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
128 log.Fatalf("error reading mock config: %v", err)
131 eServiceManager = echo.New()
132 err = registerHandlers(eServiceManager, myEnv, myPorts)
134 log.Fatal("registerHandlers fatal error on setupTest")
137 serviceManagerServer = httptest.NewServer(eServiceManager)
142 func getProvider() provapi.APIProviderEnrolmentDetails {
145 funcInfoAPF = "rApp Kong as APF"
146 funcInfoAEF = "rApp Kong as AEF"
149 testFuncs := []provapi.APIProviderFunctionDetails{
151 ApiProvFuncInfo: &funcInfoAPF,
152 ApiProvFuncRole: provapi.ApiProviderFuncRoleAPF,
153 RegInfo: provapi.RegistrationInformation{
154 ApiProvPubKey: "APF-PublicKey",
158 ApiProvFuncInfo: &funcInfoAEF,
159 ApiProvFuncRole: provapi.ApiProviderFuncRoleAEF,
160 RegInfo: provapi.RegistrationInformation{
161 ApiProvPubKey: "AEF-PublicKey",
165 return provapi.APIProviderEnrolmentDetails{
167 ApiProvDomInfo: &domainInfo,
168 ApiProvFuncs: &testFuncs,
172 func capifCleanUp() {
173 t := new(testing.T) // Create a new testing.T instance for capifCleanUp
175 // Delete the invoker
176 invokerInfo := "invoker a"
177 invokerId := "api_invoker_id_" + strings.Replace(invokerInfo, " ", "_", 1)
179 result := testutil.NewRequest().Delete("/api-invoker-management/v1/onboardedInvokers/"+invokerId).Go(t, eServiceManager)
180 assert.Equal(t, http.StatusNoContent, result.Code())
182 // Delete the original published service
183 apfId := "APF_id_rApp_Kong_as_APF"
185 apiId := "api_id_" + apiName
187 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
188 assert.Equal(t, http.StatusNoContent, result.Code())
190 // Delete the first published service
191 apfId = "APF_id_rApp_Kong_as_APF"
193 apiId = "api_id_" + apiName
195 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
196 assert.Equal(t, http.StatusNoContent, result.Code())
198 // Delete the second published service
200 apiId = "api_id_" + apiName
202 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
203 assert.Equal(t, http.StatusNoContent, result.Code())
205 // Delete the provider
206 domainID := "domain_id_Kong"
207 result = testutil.NewRequest().Delete("/api-provider-management/v1/registrations/"+domainID).Go(t, eServiceManager)
208 assert.Equal(t, http.StatusNoContent, result.Code())
211 func teardown() error {
212 log.Trace("entering teardown")
214 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
216 log.Fatal("error loading environment file")
220 err = kongclear.KongClear(myEnv, myPorts)
222 log.Fatal("error clearing Kong on teardown")
225 mockKongServer.Close()
227 serviceManagerServer.Close()
232 func TestPostUnpublishedServiceWithUnregisteredPublisher(t *testing.T) {
235 apfId := "APF_id_rApp_Kong_as_APF"
237 // Check no services published
238 result := testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis").Go(t, eServiceManager)
239 assert.Equal(t, http.StatusNotFound, result.Code())
241 var resultError common29122.ProblemDetails
242 err := result.UnmarshalBodyToObject(&resultError)
243 assert.NoError(t, err, "error unmarshaling response")
245 assert.Contains(t, *resultError.Cause, apfId)
246 assert.Contains(t, *resultError.Cause, "api is only available for publishers")
248 aefId := "AEF_id_rApp_Kong_as_AEF"
249 namespace := "namespace"
250 repoName := "repoName"
251 chartName := "chartName"
252 releaseName := "releaseName"
253 description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
255 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
256 assert.Nil(t, err, "error reading env file")
258 testServiceIpv4 := common29122.Ipv4Addr(myEnv["TEST_SERVICE_IPV4"])
259 testServicePort := common29122.Port(myPorts["TEST_SERVICE_PORT"])
261 assert.NotEmpty(t, testServiceIpv4, "TEST_SERVICE_IPV4 is required in .env file for unit testing")
262 assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
265 newServiceDescription := getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort)
267 // Attempt to publish a service for provider
268 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
269 assert.Equal(t, http.StatusForbidden, result.Code())
271 // var resultError common29122.ProblemDetails
272 err = result.UnmarshalBodyToObject(&resultError)
273 assert.NoError(t, err, "error unmarshaling response")
275 assert.Contains(t, *resultError.Cause, apfId)
276 assert.Contains(t, *resultError.Cause, "Unable to publish the service due to api is only available for publishers")
279 func TestRegisterValidProvider(t *testing.T) {
280 newProvider := getProvider()
282 // Register a valid provider
283 result := testutil.NewRequest().Post("/api-provider-management/v1/registrations").WithJsonBody(newProvider).Go(t, eServiceManager)
284 assert.Equal(t, http.StatusCreated, result.Code())
286 var resultProvider provapi.APIProviderEnrolmentDetails
287 err := result.UnmarshalBodyToObject(&resultProvider)
288 assert.NoError(t, err, "error unmarshaling response")
291 func TestPublishUnpublishService(t *testing.T) {
292 apfId := "APF_id_rApp_Kong_as_APF"
295 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
296 assert.Nil(t, err, "error reading env file")
298 testServiceIpv4 := common29122.Ipv4Addr(myEnv["TEST_SERVICE_IPV4"])
299 testServicePort := common29122.Port(myPorts["TEST_SERVICE_PORT"])
301 assert.NotEmpty(t, testServiceIpv4, "TEST_SERVICE_IPV4 is required in .env file for unit testing")
302 assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
304 // Check no services published
305 result := testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis").Go(t, eServiceManager)
306 assert.Equal(t, http.StatusOK, result.Code())
308 // Parse JSON from the response body
309 var resultServices []publishapi.ServiceAPIDescription
310 err = result.UnmarshalJsonToObject(&resultServices)
311 assert.NoError(t, err, "error unmarshaling response")
313 // Check if the parsed array is empty
314 assert.Zero(t, len(resultServices))
316 aefId := "AEF_id_rApp_Kong_as_AEF"
317 namespace := "namespace"
318 repoName := "repoName"
319 chartName := "chartName"
320 releaseName := "releaseName"
321 description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
323 newServiceDescription := getServiceAPIDescriptionMissingInterface(aefId, apiName, description)
325 // Publish a service for provider
326 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
327 assert.Equal(t, http.StatusBadRequest, result.Code())
329 var resultError common29122.ProblemDetails
330 err = result.UnmarshalJsonToObject(&resultError)
331 assert.NoError(t, err, "error unmarshaling response")
333 assert.Contains(t, *resultError.Cause, "cannot read interfaceDescription")
335 newServiceDescription = getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort)
337 // Publish a service for provider
338 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
339 assert.Equal(t, http.StatusCreated, result.Code())
341 if result.Code() != http.StatusCreated {
342 log.Fatalf("failed to publish the service with HTTP result code %d", result.Code())
346 var resultService publishapi.ServiceAPIDescription
347 err = result.UnmarshalJsonToObject(&resultService)
348 assert.NoError(t, err, "error unmarshaling response")
349 newApiId := "api_id_" + apiName
350 assert.Equal(t, newApiId, *resultService.ApiId)
352 assert.Equal(t, "http://example.com/published-apis/v1/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
354 // Check that the service is published for the provider
355 result = testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis/"+newApiId).Go(t, eServiceManager)
356 assert.Equal(t, http.StatusOK, result.Code())
358 err = result.UnmarshalJsonToObject(&resultService)
359 assert.NoError(t, err, "error unmarshaling response")
360 assert.Equal(t, newApiId, *resultService.ApiId)
362 aefProfile := (*resultService.AefProfiles)[0]
363 interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
365 resultServiceIpv4 := *interfaceDescription.Ipv4Addr
366 resultServicePort := *interfaceDescription.Port
368 kongDataPlaneIPv4 := common29122.Ipv4Addr(myEnv["KONG_DATA_PLANE_IPV4"])
369 kongDataPlanePort := common29122.Port(myPorts["KONG_DATA_PLANE_PORT"])
371 assert.NotEmpty(t, kongDataPlaneIPv4, "KONG_DATA_PLANE_IPV4 is required in .env file for unit testing")
372 assert.NotZero(t, kongDataPlanePort, "KONG_DATA_PLANE_PORT is required in .env file for unit testing")
374 assert.Equal(t, kongDataPlaneIPv4, resultServiceIpv4)
375 assert.Equal(t, kongDataPlanePort, resultServicePort)
377 // Publish the same service again should result in Forbidden
378 newServiceDescription.ApiId = &newApiId
379 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
380 assert.Equal(t, http.StatusForbidden, result.Code())
382 err = result.UnmarshalBodyToObject(&resultError)
383 assert.NoError(t, err, "error unmarshaling response")
384 assert.Contains(t, *resultError.Cause, "already published")
385 assert.Equal(t, http.StatusForbidden, *resultError.Status)
387 // Delete the service
388 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+newApiId).Go(t, eServiceManager)
389 assert.Equal(t, http.StatusNoContent, result.Code())
391 // Check no services published
392 result = testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis").Go(t, eServiceManager)
393 assert.Equal(t, http.StatusOK, result.Code())
395 // Parse JSON from the response body
396 err = result.UnmarshalJsonToObject(&resultServices)
397 assert.NoError(t, err, "error unmarshaling response")
399 // Check if the parsed array is empty
400 assert.Zero(t, len(resultServices))
404 func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]int) (err error) {
405 capifProtocol := myEnv["CAPIF_PROTOCOL"]
406 capifIPv4 := common29122.Ipv4Addr(myEnv["CAPIF_IPV4"])
407 capifPort := common29122.Port(myPorts["CAPIF_PORT"])
408 kongDomain := myEnv["KONG_DOMAIN"]
409 kongProtocol := myEnv["KONG_PROTOCOL"]
410 kongControlPlaneIPv4 := common29122.Ipv4Addr(myEnv["KONG_CONTROL_PLANE_IPV4"])
411 kongControlPlanePort := common29122.Port(myPorts["KONG_CONTROL_PLANE_PORT"])
412 kongDataPlaneIPv4 := common29122.Ipv4Addr(myEnv["KONG_DATA_PLANE_IPV4"])
413 kongDataPlanePort := common29122.Port(myPorts["KONG_DATA_PLANE_PORT"])
415 // Register ProviderManagement
416 providerManagerSwagger, err := provapi.GetSwagger()
418 log.Fatalf("error loading ProviderManagement swagger spec\n: %s", err)
421 providerManagerSwagger.Servers = nil
422 providerManager := providermanagement.NewProviderManager(capifProtocol, capifIPv4, capifPort)
424 var group *echo.Group
426 group = e.Group("/api-provider-management/v1")
427 group.Use(middleware.OapiRequestValidator(providerManagerSwagger))
428 provapi.RegisterHandlersWithBaseURL(e, providerManager, "/api-provider-management/v1")
430 publishServiceSwagger, err := publishapi.GetSwagger()
432 fmt.Fprintf(os.Stderr, "Error loading PublishService swagger spec\n: %s", err)
436 publishServiceSwagger.Servers = nil
438 ps := NewPublishService(
439 kongDomain, kongProtocol,
440 kongControlPlaneIPv4, kongControlPlanePort,
441 kongDataPlaneIPv4, kongDataPlanePort,
442 capifProtocol, capifIPv4, capifPort)
444 group = e.Group("/published-apis/v1")
445 group.Use(echomiddleware.Logger())
446 group.Use(middleware.OapiRequestValidator(publishServiceSwagger))
447 publishapi.RegisterHandlersWithBaseURL(e, ps, "/published-apis/v1")
452 func getServiceAPIDescription(aefId, apiName, description string, testServiceIpv4 common29122.Ipv4Addr, testServicePort common29122.Port) publishapi.ServiceAPIDescription {
454 var protocol publishapi.Protocol = "HTTP_1_1"
456 return publishapi.ServiceAPIDescription{
457 AefProfiles: &[]publishapi.AefProfile{
460 InterfaceDescriptions: &[]publishapi.InterfaceDescription{
462 Ipv4Addr: &testServiceIpv4,
463 Port: &testServicePort,
464 SecurityMethods: &[]publishapi.SecurityMethod{
469 DomainName: &domainName,
471 Versions: []publishapi.Version{
474 Resources: &[]publishapi.Resource{
476 CommType: "REQUEST_RESPONSE",
477 Operations: &[]publishapi.Operation{
480 ResourceName: "helloworld",
489 Description: &description,
493 func getServiceAPIDescriptionMissingInterface(aefId, apiName, description string) publishapi.ServiceAPIDescription {
495 var protocol publishapi.Protocol = "HTTP_1_1"
497 return publishapi.ServiceAPIDescription{
498 AefProfiles: &[]publishapi.AefProfile{
501 DomainName: &domainName,
503 Versions: []publishapi.Version{
506 Resources: &[]publishapi.Resource{
508 CommType: "REQUEST_RESPONSE",
509 Operations: &[]publishapi.Operation{
512 ResourceName: "helloworld",
521 Description: &description,