2 // ========================LICENSE_START=================================
5 // Copyright (C) 2022-2023: Nordix Foundation
6 // Copyright (C) 2024: OpenInfra Foundation Europe
8 // Licensed under the Apache License, Version 2.0 (the "License");
9 // you may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at
12 // http://www.apache.org/licenses/LICENSE-2.0
14 // Unless required by applicable law or agreed to in writing, software
15 // distributed under the License is distributed on an "AS IS" BASIS,
16 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 // See the License for the specific language governing permissions and
18 // limitations under the License.
19 // ========================LICENSE_END===================================
22 package publishservice
31 "oransc.org/nonrtric/capifcore/internal/common29122"
32 "oransc.org/nonrtric/capifcore/internal/eventsapi"
33 "oransc.org/nonrtric/capifcore/internal/providermanagement"
35 "github.com/labstack/echo/v4"
37 publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
39 "oransc.org/nonrtric/capifcore/internal/helmmanagement"
40 helmMocks "oransc.org/nonrtric/capifcore/internal/helmmanagement/mocks"
41 serviceMocks "oransc.org/nonrtric/capifcore/internal/providermanagement/mocks"
43 "github.com/deepmap/oapi-codegen/pkg/middleware"
44 "github.com/deepmap/oapi-codegen/pkg/testutil"
45 echomiddleware "github.com/labstack/echo/v4/middleware"
46 "github.com/stretchr/testify/assert"
47 "github.com/stretchr/testify/mock"
50 func TestUnregisteredService(t *testing.T) {
52 serviceRegisterMock := serviceMocks.ServiceRegister{}
53 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(false)
55 helmManagerMock := helmMocks.HelmManager{}
56 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
57 _, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
59 // Check no services published
60 result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
61 assert.Equal(t, http.StatusNotFound, result.Code())
63 var resultError common29122.ProblemDetails
64 err := result.UnmarshalJsonToObject(&resultError)
65 assert.NoError(t, err, "error unmarshaling response")
67 assert.Contains(t, *resultError.Cause, "api is only available for publishers")
68 assert.Equal(t, http.StatusNotFound, *resultError.Status)
71 func TestPublishUnpublishService(t *testing.T) {
75 serviceRegisterMock := serviceMocks.ServiceRegister{}
76 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId"})
77 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
78 helmManagerMock := helmMocks.HelmManager{}
79 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
80 serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
82 // Check no services published
83 var resultServices []publishapi.ServiceAPIDescription
84 result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
85 assert.Equal(t, http.StatusOK, result.Code())
87 // Parse JSON from the response body
88 err := result.UnmarshalJsonToObject(&resultServices)
89 assert.NoError(t, err, "error unmarshaling response")
91 // Check if the parsed array is empty
92 assert.Zero(t, len(resultServices))
94 apiName := "app-management"
95 namespace := "namespace"
96 repoName := "repoName"
97 chartName := "chartName"
98 releaseName := "releaseName"
99 description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
100 newServiceDescription := getServiceAPIDescription(aefId, apiName, description)
102 // Publish a service for provider
103 result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
104 assert.Equal(t, http.StatusCreated, result.Code())
106 var resultService publishapi.ServiceAPIDescription
108 err = result.UnmarshalJsonToObject(&resultService)
109 assert.NoError(t, err, "error unmarshaling response")
110 newApiId := "api_id_" + apiName
111 assert.Equal(t, newApiId, *resultService.ApiId)
112 assert.Equal(t, "http://example.com/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
113 newServiceDescription.ApiId = &newApiId
114 assert.True(t, serviceUnderTest.IsAPIPublished(aefId, apiName))
115 serviceRegisterMock.AssertCalled(t, "GetAefsForPublisher", apfId)
116 helmManagerMock.AssertCalled(t, "InstallHelmChart", namespace, repoName, chartName, releaseName)
117 assert.ElementsMatch(t, []string{aefId}, serviceUnderTest.getAllAefIds())
118 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
119 assert.Fail(t, "No event sent")
121 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
122 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIAVAILABLE, publishEvent.Events)
125 // Check that the service is published for the provider
126 result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
128 assert.Equal(t, http.StatusOK, result.Code())
129 err = result.UnmarshalJsonToObject(&resultService)
130 assert.NoError(t, err, "error unmarshaling response")
131 assert.Equal(t, *resultService.ApiId, newApiId)
133 // Publish the same service again should result in Forbidden
134 result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
136 assert.Equal(t, http.StatusForbidden, result.Code())
137 var resultError common29122.ProblemDetails
138 err = result.UnmarshalJsonToObject(&resultError)
139 assert.NoError(t, err, "error unmarshaling response")
140 assert.Contains(t, *resultError.Cause, "already published")
141 assert.Equal(t, http.StatusForbidden, *resultError.Status)
143 // Delete the service
144 helmManagerMock.On("UninstallHelmChart", mock.Anything, mock.Anything).Return(nil)
146 result = testutil.NewRequest().Delete("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
148 assert.Equal(t, http.StatusNoContent, result.Code())
149 helmManagerMock.AssertCalled(t, "UninstallHelmChart", namespace, chartName)
150 assert.Empty(t, serviceUnderTest.getAllAefIds())
152 // Check no services published for a provider
153 result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
155 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
156 assert.Fail(t, "No event sent")
158 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
159 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUNAVAILABLE, publishEvent.Events)
162 assert.Equal(t, http.StatusNotFound, result.Code())
164 // Check no services published
165 result = testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
166 assert.Equal(t, http.StatusOK, result.Code())
168 // Parse JSON from the response body
169 var responseArray []publishapi.ServiceAPIDescription
170 err = result.UnmarshalJsonToObject(&responseArray)
171 assert.NoError(t, err, "error unmarshaling response")
173 // Check if the parsed array is empty
174 assert.Zero(t, len(responseArray))
177 func TestPostUnpublishedServiceWithUnregisteredFunction(t *testing.T) {
180 serviceRegisterMock := serviceMocks.ServiceRegister{}
181 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{"otherAefId"})
182 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
183 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
185 newServiceDescription := getServiceAPIDescription(aefId, "apiName", "description")
188 result := testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
190 assert.Equal(t, http.StatusNotFound, result.Code())
191 var resultError common29122.ProblemDetails
192 err := result.UnmarshalJsonToObject(&resultError)
193 assert.NoError(t, err, "error unmarshaling response")
194 assert.Contains(t, *resultError.Cause, aefId)
195 assert.Contains(t, *resultError.Cause, "not registered")
196 assert.Equal(t, http.StatusNotFound, *resultError.Status)
199 func TestGetServices(t *testing.T) {
202 serviceRegisterMock := serviceMocks.ServiceRegister{}
203 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId})
204 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
205 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
207 // Check no services published
208 var resultServices []publishapi.ServiceAPIDescription
210 result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
211 assert.Equal(t, http.StatusOK, result.Code())
213 // Parse JSON from the response body
214 err := result.UnmarshalJsonToObject(&resultServices)
215 assert.NoError(t, err, "error unmarshaling response")
217 // Check if the parsed array is empty
218 assert.Zero(t, len(resultServices))
220 serviceDescription1 := getServiceAPIDescription(aefId, "api1", "Description")
221 serviceDescription2 := getServiceAPIDescription(aefId, "api2", "Description")
223 // Publish a service for provider
224 testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription1).Go(t, requestHandler)
225 testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription2).Go(t, requestHandler)
227 // Get all services for provider
228 result = testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
229 assert.Equal(t, http.StatusOK, result.Code())
231 err = result.UnmarshalJsonToObject(&resultServices)
232 assert.NoError(t, err, "error unmarshaling response")
234 assert.Len(t, resultServices, 2)
235 apiId1 := "api_id_api1"
236 serviceDescription1.ApiId = &apiId1
237 apiId2 := "api_id_api2"
238 serviceDescription2.ApiId = &apiId2
240 assert.Contains(t, resultServices, serviceDescription1)
241 assert.Contains(t, resultServices, serviceDescription2)
244 func TestGetPublishedServices(t *testing.T) {
245 serviceUnderTest := NewPublishService(nil, nil, nil)
247 profiles := make([]publishapi.AefProfile, 1)
248 serviceDescription := publishapi.ServiceAPIDescription{
249 AefProfiles: &profiles,
251 serviceUnderTest.publishedServices["publisher1"] = []publishapi.ServiceAPIDescription{
254 serviceUnderTest.publishedServices["publisher2"] = []publishapi.ServiceAPIDescription{
257 result := serviceUnderTest.GetAllPublishedServices()
258 assert.Len(t, result, 2)
261 func TestGetAllowedServices(t *testing.T) {
262 serviceUnderTest := NewPublishService(nil, nil, nil)
264 aefProfiles1 := []publishapi.AefProfile{}
265 apiName1 := "api Name 1"
266 aefProfiles2 := []publishapi.AefProfile{}
267 apiName2 := "api Name 2"
268 aefProfiles3 := []publishapi.AefProfile{}
269 apiName3 := "api Name 3"
270 aefProfiles4 := []publishapi.AefProfile{}
271 apiName4 := "api Name 4"
273 serviceUnderTest.publishedServices["publisher1"] = []publishapi.ServiceAPIDescription{
276 AefProfiles: &aefProfiles1,
280 AefProfiles: &aefProfiles2,
284 AefProfiles: &aefProfiles3,
288 AefProfiles: &aefProfiles4,
292 serviceDescription := publishapi.ServiceAPIDescription{
294 AefProfiles: &aefProfiles4,
296 serviceUnderTest.publishedServices["publisher2"] = []publishapi.ServiceAPIDescription{
300 allowedApiList := []publishapi.ServiceAPIDescription{
303 AefProfiles: &aefProfiles2,
307 AefProfiles: &aefProfiles3,
311 result := serviceUnderTest.GetAllowedPublishedServices(allowedApiList)
312 assert.Len(t, result, 2)
314 result = serviceUnderTest.GetAllowedPublishedServices(nil)
315 assert.Len(t, result, 0)
317 result = serviceUnderTest.GetAllowedPublishedServices([]publishapi.ServiceAPIDescription{})
318 assert.Len(t, result, 0)
320 // Create a list with no ApiNames
321 badApiList := []publishapi.ServiceAPIDescription{
327 result = serviceUnderTest.GetAllowedPublishedServices(badApiList)
328 assert.Len(t, result, 0)
331 func TestUpdateDescription(t *testing.T) {
333 serviceApiId := "serviceApiId"
336 description := "description"
338 serviceRegisterMock := serviceMocks.ServiceRegister{}
339 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
340 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
341 helmManagerMock := helmMocks.HelmManager{}
342 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
343 serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
344 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
345 serviceDescription.ApiId = &serviceApiId
346 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
347 (*serviceDescription.AefProfiles)[0].AefId = aefId
350 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
351 updatedServiceDescription.ApiId = &serviceApiId
352 (*updatedServiceDescription.AefProfiles)[0].AefId = aefId
353 newDescription := "new description"
354 updatedServiceDescription.Description = &newDescription
355 newDomainName := "new domainName"
356 (*updatedServiceDescription.AefProfiles)[0].DomainName = &newDomainName
358 newProfileDomain := "new profile Domain name"
359 var protocol publishapi.Protocol = "HTTP_1_1"
361 test := append(*updatedServiceDescription.AefProfiles, publishapi.AefProfile{
363 DomainName: &newProfileDomain,
365 Versions: []publishapi.Version{
368 Resources: &[]publishapi.Resource{
370 CommType: "REQUEST_RESPONSE",
371 Operations: &[]publishapi.Operation{
383 updatedServiceDescription.AefProfiles = &test
385 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
387 var resultService publishapi.ServiceAPIDescription
388 assert.Equal(t, http.StatusOK, result.Code())
389 err := result.UnmarshalJsonToObject(&resultService)
390 assert.NoError(t, err, "error unmarshaling response")
391 assert.Equal(t, newDescription, *resultService.Description)
392 assert.Equal(t, newDomainName, *(*resultService.AefProfiles)[0].DomainName)
393 assert.Equal(t, "aefIdNew", (*resultService.AefProfiles)[1].AefId)
394 assert.True(t, serviceUnderTest.IsAPIPublished("aefIdNew", "path"))
396 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
397 assert.Fail(t, "No event sent")
399 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
400 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUPDATE, publishEvent.Events)
404 func TestFailedUpdateDescription(t *testing.T) {
406 serviceApiId := "serviceApiId"
407 // Trying to update a different serviceApiId will cause a 400 error
408 updatedServiceApiId := "updatedServiceApiId"
411 description := "description"
413 serviceRegisterMock := serviceMocks.ServiceRegister{}
414 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
415 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
416 helmManagerMock := helmMocks.HelmManager{}
417 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
418 serviceUnderTest, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
419 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
420 serviceDescription.ApiId = &serviceApiId
421 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
422 (*serviceDescription.AefProfiles)[0].AefId = aefId
424 // Modify the service
425 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
426 updatedServiceDescription.ApiId = &updatedServiceApiId
427 (*updatedServiceDescription.AefProfiles)[0].AefId = aefId
428 newDescription := "new description"
429 updatedServiceDescription.Description = &newDescription
430 newDomainName := "new domainName"
431 (*updatedServiceDescription.AefProfiles)[0].DomainName = &newDomainName
433 newProfileDomain := "new profile Domain name"
434 var protocol publishapi.Protocol = "HTTP_1_1"
436 test := append(*updatedServiceDescription.AefProfiles, publishapi.AefProfile{
438 DomainName: &newProfileDomain,
440 Versions: []publishapi.Version{
443 Resources: &[]publishapi.Resource{
445 CommType: "REQUEST_RESPONSE",
446 Operations: &[]publishapi.Operation{
457 updatedServiceDescription.AefProfiles = &test
459 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
460 assert.Equal(t, http.StatusBadRequest, result.Code())
462 var resultError common29122.ProblemDetails
463 err := result.UnmarshalJsonToObject(&resultError)
464 assert.NoError(t, err, "error unmarshaling response")
466 assert.Contains(t, *resultError.Cause, "ServiceAPIDescription ApiId doesn't match path parameter")
467 assert.Equal(t, http.StatusBadRequest, *resultError.Status)
470 func TestUpdateValidServiceWithDeletedFunction(t *testing.T) {
472 serviceApiId := "serviceApiId"
475 description := "description"
477 serviceRegisterMock := serviceMocks.ServiceRegister{}
478 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
479 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
480 helmManagerMock := helmMocks.HelmManager{}
481 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
482 serviceUnderTest, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
484 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
485 serviceDescription.ApiId = &serviceApiId
486 (*serviceDescription.AefProfiles)[0].AefId = aefId
488 newProfileDomain := "new profile Domain name"
489 var protocol publishapi.Protocol = "HTTP_1_1"
490 test := append(*serviceDescription.AefProfiles, publishapi.AefProfile{
492 DomainName: &newProfileDomain,
494 Versions: []publishapi.Version{
497 Resources: &[]publishapi.Resource{
499 CommType: "REQUEST_RESPONSE",
500 Operations: &[]publishapi.Operation{
511 serviceDescription.AefProfiles = &test
512 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
515 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
516 updatedServiceDescription.ApiId = &serviceApiId
517 test1 := append(*updatedServiceDescription.AefProfiles, publishapi.AefProfile{
519 DomainName: &newProfileDomain,
521 Versions: []publishapi.Version{
524 Resources: &[]publishapi.Resource{
526 CommType: "REQUEST_RESPONSE",
527 Operations: &[]publishapi.Operation{
538 updatedServiceDescription.AefProfiles = &test1
539 testFunc := []publishapi.AefProfile{
540 (*updatedServiceDescription.AefProfiles)[1],
543 updatedServiceDescription.AefProfiles = &testFunc
544 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
545 var resultService publishapi.ServiceAPIDescription
546 assert.Equal(t, http.StatusOK, result.Code())
547 err := result.UnmarshalJsonToObject(&resultService)
548 assert.NoError(t, err, "error unmarshaling response")
549 assert.Len(t, (*resultService.AefProfiles), 1)
550 assert.False(t, serviceUnderTest.IsAPIPublished("aefId", "path"))
554 func TestPublishInvalidService(t *testing.T) {
556 serviceRegisterMock := serviceMocks.ServiceRegister{}
557 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
559 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
560 newServiceDescription := getServiceAPIDescription("aefId", " ", "description")
563 result := testutil.NewRequest().Post("/apfId/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
565 assert.Equal(t, http.StatusBadRequest, result.Code())
566 var resultError common29122.ProblemDetails
567 err := result.UnmarshalJsonToObject(&resultError)
568 assert.NoError(t, err, "error unmarshaling response")
569 assert.Contains(t, *resultError.Cause, "missing")
570 assert.Contains(t, *resultError.Cause, "apiName")
571 assert.Equal(t, http.StatusBadRequest, *resultError.Status)
574 func getEcho(serviceRegister providermanagement.ServiceRegister, helmManager helmmanagement.HelmManager) (*PublishService, chan eventsapi.EventNotification, *echo.Echo) {
575 swagger, err := publishapi.GetSwagger()
577 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
581 swagger.Servers = nil
583 eventChannel := make(chan eventsapi.EventNotification)
584 ps := NewPublishService(serviceRegister, helmManager, eventChannel)
587 e.Use(echomiddleware.Logger())
588 e.Use(middleware.OapiRequestValidator(swagger))
590 publishapi.RegisterHandlers(e, ps)
591 return ps, eventChannel, e
594 func getServiceAPIDescription(aefId, apiName, description string) publishapi.ServiceAPIDescription {
595 domainName := "domainName"
596 var protocol publishapi.Protocol = "HTTP_1_1"
597 return publishapi.ServiceAPIDescription{
598 AefProfiles: &[]publishapi.AefProfile{
601 DomainName: &domainName,
603 Versions: []publishapi.Version{
606 Resources: &[]publishapi.Resource{
608 CommType: "REQUEST_RESPONSE",
609 Operations: &[]publishapi.Operation{
621 Description: &description,
625 // waitForEvent waits for the channel to receive an event for the specified max timeout.
626 // Returns true if waiting timed out.
627 func waitForEvent(ch chan eventsapi.EventNotification, timeout time.Duration) (*eventsapi.EventNotification, bool) {
630 return &event, false // completed normally
631 case <-time.After(timeout):
632 return nil, true // timed out