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 "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_IPV4": mockKongHost,
112 "KONG_DATA_PLANE_PORT": "32080",
113 "KONG_CONTROL_PLANE_PORT": mockKongControlPlanePort,
114 "CAPIF_PROTOCOL": "http",
115 "CAPIF_IPV4": capifHost,
116 "CAPIF_PORT": capifPort,
118 "SERVICE_MANAGER_PORT": "8095",
119 "TEST_SERVICE_IPV4": "10.101.1.101",
120 "TEST_SERVICE_PORT": "30951",
124 // Use the mock implementation for testing
125 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
127 log.Fatalf("error reading mock config: %v", err)
130 eServiceManager = echo.New()
131 err = registerHandlers(eServiceManager, myEnv, myPorts)
133 log.Fatal("registerHandlers fatal error on setupTest")
136 serviceManagerServer = httptest.NewServer(eServiceManager)
141 func getProvider() provapi.APIProviderEnrolmentDetails {
144 funcInfoAPF = "rApp Kong as APF"
145 funcInfoAEF = "rApp Kong as AEF"
148 testFuncs := []provapi.APIProviderFunctionDetails{
150 ApiProvFuncInfo: &funcInfoAPF,
151 ApiProvFuncRole: provapi.ApiProviderFuncRoleAPF,
152 RegInfo: provapi.RegistrationInformation{
153 ApiProvPubKey: "APF-PublicKey",
157 ApiProvFuncInfo: &funcInfoAEF,
158 ApiProvFuncRole: provapi.ApiProviderFuncRoleAEF,
159 RegInfo: provapi.RegistrationInformation{
160 ApiProvPubKey: "AEF-PublicKey",
164 return provapi.APIProviderEnrolmentDetails{
166 ApiProvDomInfo: &domainInfo,
167 ApiProvFuncs: &testFuncs,
171 func capifCleanUp() {
172 t := new(testing.T) // Create a new testing.T instance for capifCleanUp
174 // Delete the invoker
175 invokerInfo := "invoker a"
176 invokerId := "api_invoker_id_" + strings.Replace(invokerInfo, " ", "_", 1)
178 result := testutil.NewRequest().Delete("/api-invoker-management/v1/onboardedInvokers/"+invokerId).Go(t, eServiceManager)
179 assert.Equal(t, http.StatusNoContent, result.Code())
181 // Delete the original published service
182 apfId := "APF_id_rApp_Kong_as_APF"
184 apiId := "api_id_" + apiName
186 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
187 assert.Equal(t, http.StatusNoContent, result.Code())
189 // Delete the first published service
190 apfId = "APF_id_rApp_Kong_as_APF"
192 apiId = "api_id_" + apiName
194 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
195 assert.Equal(t, http.StatusNoContent, result.Code())
197 // Delete the second published service
199 apiId = "api_id_" + apiName
201 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
202 assert.Equal(t, http.StatusNoContent, result.Code())
204 // Delete the provider
205 domainID := "domain_id_Kong"
206 result = testutil.NewRequest().Delete("/api-provider-management/v1/registrations/"+domainID).Go(t, eServiceManager)
207 assert.Equal(t, http.StatusNoContent, result.Code())
210 func teardown() error {
211 log.Trace("entering teardown")
213 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
215 log.Fatal("error loading environment file")
219 err = kongclear.KongClear(myEnv, myPorts)
221 log.Fatal("error clearing Kong on teardown")
224 mockKongServer.Close()
226 serviceManagerServer.Close()
231 func TestPostUnpublishedServiceWithUnregisteredPublisher(t *testing.T) {
234 apfId := "APF_id_rApp_Kong_as_APF"
236 // Check no services published
237 result := testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis").Go(t, eServiceManager)
238 assert.Equal(t, http.StatusNotFound, result.Code())
240 var resultError common29122.ProblemDetails
241 err := result.UnmarshalBodyToObject(&resultError)
242 assert.NoError(t, err, "error unmarshaling response")
244 assert.Contains(t, *resultError.Cause, apfId)
245 assert.Contains(t, *resultError.Cause, "api is only available for publishers")
247 aefId := "AEF_id_rApp_Kong_as_AEF"
248 namespace := "namespace"
249 repoName := "repoName"
250 chartName := "chartName"
251 releaseName := "releaseName"
252 description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
254 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
255 assert.Nil(t, err, "error reading env file")
257 testServiceIpv4 := common29122.Ipv4Addr(myEnv["TEST_SERVICE_IPV4"])
258 testServicePort := common29122.Port(myPorts["TEST_SERVICE_PORT"])
260 assert.NotEmpty(t, testServiceIpv4, "TEST_SERVICE_IPV4 is required in .env file for unit testing")
261 assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
264 newServiceDescription := getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort)
266 // Attempt to publish a service for provider
267 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
268 assert.Equal(t, http.StatusForbidden, result.Code())
270 // var resultError common29122.ProblemDetails
271 err = result.UnmarshalBodyToObject(&resultError)
272 assert.NoError(t, err, "error unmarshaling response")
274 assert.Contains(t, *resultError.Cause, apfId)
275 assert.Contains(t, *resultError.Cause, "Unable to publish the service due to api is only available for publishers")
278 func TestRegisterValidProvider(t *testing.T) {
279 newProvider := getProvider()
281 // Register a valid provider
282 result := testutil.NewRequest().Post("/api-provider-management/v1/registrations").WithJsonBody(newProvider).Go(t, eServiceManager)
283 assert.Equal(t, http.StatusCreated, result.Code())
285 var resultProvider provapi.APIProviderEnrolmentDetails
286 err := result.UnmarshalBodyToObject(&resultProvider)
287 assert.NoError(t, err, "error unmarshaling response")
290 func TestPublishUnpublishService(t *testing.T) {
291 apfId := "APF_id_rApp_Kong_as_APF"
294 myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
295 assert.Nil(t, err, "error reading env file")
297 testServiceIpv4 := common29122.Ipv4Addr(myEnv["TEST_SERVICE_IPV4"])
298 testServicePort := common29122.Port(myPorts["TEST_SERVICE_PORT"])
300 assert.NotEmpty(t, testServiceIpv4, "TEST_SERVICE_IPV4 is required in .env file for unit testing")
301 assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
303 // Check no services published
304 result := testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis").Go(t, eServiceManager)
305 assert.Equal(t, http.StatusOK, result.Code())
307 // Parse JSON from the response body
308 var resultServices []publishapi.ServiceAPIDescription
309 err = result.UnmarshalJsonToObject(&resultServices)
310 assert.NoError(t, err, "error unmarshaling response")
312 // Check if the parsed array is empty
313 assert.Zero(t, len(resultServices))
315 aefId := "AEF_id_rApp_Kong_as_AEF"
316 namespace := "namespace"
317 repoName := "repoName"
318 chartName := "chartName"
319 releaseName := "releaseName"
320 description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
322 newServiceDescription := getServiceAPIDescriptionMissingInterface(aefId, apiName, description)
324 // Publish a service for provider
325 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
326 assert.Equal(t, http.StatusBadRequest, result.Code())
328 var resultError common29122.ProblemDetails
329 err = result.UnmarshalJsonToObject(&resultError)
330 assert.NoError(t, err, "error unmarshaling response")
332 assert.Contains(t, *resultError.Cause, "cannot read interfaceDescription")
334 newServiceDescription = getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort)
336 // Publish a service for provider
337 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
338 assert.Equal(t, http.StatusCreated, result.Code())
340 if result.Code() != http.StatusCreated {
341 log.Fatalf("failed to publish the service with HTTP result code %d", result.Code())
345 var resultService publishapi.ServiceAPIDescription
346 err = result.UnmarshalJsonToObject(&resultService)
347 assert.NoError(t, err, "error unmarshaling response")
348 newApiId := "api_id_" + apiName
349 assert.Equal(t, newApiId, *resultService.ApiId)
351 assert.Equal(t, "http://example.com/published-apis/v1/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
353 // Check that the service is published for the provider
354 result = testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis/"+newApiId).Go(t, eServiceManager)
355 assert.Equal(t, http.StatusOK, result.Code())
357 err = result.UnmarshalJsonToObject(&resultService)
358 assert.NoError(t, err, "error unmarshaling response")
359 assert.Equal(t, newApiId, *resultService.ApiId)
361 aefProfile := (*resultService.AefProfiles)[0]
362 interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
364 resultServiceIpv4 := *interfaceDescription.Ipv4Addr
365 resultServicePort := *interfaceDescription.Port
367 kongIPv4 := common29122.Ipv4Addr(myEnv["KONG_IPV4"])
368 kongDataPlanePort := common29122.Port(myPorts["KONG_DATA_PLANE_PORT"])
370 assert.NotEmpty(t, kongIPv4, "KONG_IPV4 is required in .env file for unit testing")
371 assert.NotZero(t, kongDataPlanePort, "KONG_DATA_PLANE_PORT is required in .env file for unit testing")
373 assert.Equal(t, kongIPv4, resultServiceIpv4)
374 assert.Equal(t, kongDataPlanePort, resultServicePort)
376 // Publish the same service again should result in Forbidden
377 newServiceDescription.ApiId = &newApiId
378 result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
379 assert.Equal(t, http.StatusForbidden, result.Code())
381 err = result.UnmarshalBodyToObject(&resultError)
382 assert.NoError(t, err, "error unmarshaling response")
383 assert.Contains(t, *resultError.Cause, "already published")
384 assert.Equal(t, http.StatusForbidden, *resultError.Status)
386 // Delete the service
387 result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+newApiId).Go(t, eServiceManager)
388 assert.Equal(t, http.StatusNoContent, result.Code())
390 // Check no services published
391 result = testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis").Go(t, eServiceManager)
392 assert.Equal(t, http.StatusOK, result.Code())
394 // Parse JSON from the response body
395 err = result.UnmarshalJsonToObject(&resultServices)
396 assert.NoError(t, err, "error unmarshaling response")
398 // Check if the parsed array is empty
399 assert.Zero(t, len(resultServices))
403 func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]int) (err error) {
404 capifProtocol := myEnv["CAPIF_PROTOCOL"]
405 capifIPv4 := common29122.Ipv4Addr(myEnv["CAPIF_IPV4"])
406 capifPort := common29122.Port(myPorts["CAPIF_PORT"])
407 kongDomain := myEnv["KONG_DOMAIN"]
408 kongProtocol := myEnv["KONG_PROTOCOL"]
409 kongIPv4 := common29122.Ipv4Addr(myEnv["KONG_IPV4"])
410 kongDataPlanePort := common29122.Port(myPorts["KONG_DATA_PLANE_PORT"])
411 kongControlPlanePort := common29122.Port(myPorts["KONG_CONTROL_PLANE_PORT"])
413 // Register ProviderManagement
414 providerManagerSwagger, err := provapi.GetSwagger()
416 log.Fatalf("error loading ProviderManagement swagger spec\n: %s", err)
419 providerManagerSwagger.Servers = nil
420 providerManager := providermanagement.NewProviderManager(capifProtocol, capifIPv4, capifPort)
422 var group *echo.Group
424 group = e.Group("/api-provider-management/v1")
425 group.Use(middleware.OapiRequestValidator(providerManagerSwagger))
426 provapi.RegisterHandlersWithBaseURL(e, providerManager, "/api-provider-management/v1")
428 publishServiceSwagger, err := publishapi.GetSwagger()
430 fmt.Fprintf(os.Stderr, "Error loading PublishService swagger spec\n: %s", err)
434 publishServiceSwagger.Servers = nil
436 ps := NewPublishService(kongDomain, kongProtocol, kongIPv4, kongDataPlanePort, kongControlPlanePort, capifProtocol, capifIPv4, capifPort)
438 group = e.Group("/published-apis/v1")
439 group.Use(echomiddleware.Logger())
440 group.Use(middleware.OapiRequestValidator(publishServiceSwagger))
441 publishapi.RegisterHandlersWithBaseURL(e, ps, "/published-apis/v1")
446 func getServiceAPIDescription(aefId, apiName, description string, testServiceIpv4 common29122.Ipv4Addr, testServicePort common29122.Port) publishapi.ServiceAPIDescription {
448 var protocol publishapi.Protocol = "HTTP_1_1"
450 return publishapi.ServiceAPIDescription{
451 AefProfiles: &[]publishapi.AefProfile{
454 InterfaceDescriptions: &[]publishapi.InterfaceDescription{
456 Ipv4Addr: &testServiceIpv4,
457 Port: &testServicePort,
458 SecurityMethods: &[]publishapi.SecurityMethod{
463 DomainName: &domainName,
465 Versions: []publishapi.Version{
468 Resources: &[]publishapi.Resource{
470 CommType: "REQUEST_RESPONSE",
471 Operations: &[]publishapi.Operation{
474 ResourceName: "helloworld",
483 Description: &description,
487 func getServiceAPIDescriptionMissingInterface(aefId, apiName, description string) publishapi.ServiceAPIDescription {
489 var protocol publishapi.Protocol = "HTTP_1_1"
491 return publishapi.ServiceAPIDescription{
492 AefProfiles: &[]publishapi.AefProfile{
495 DomainName: &domainName,
497 Versions: []publishapi.Version{
500 Resources: &[]publishapi.Resource{
502 CommType: "REQUEST_RESPONSE",
503 Operations: &[]publishapi.Operation{
506 ResourceName: "helloworld",
515 Description: &description,