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)
72 func TestPublishUnpublishService(t *testing.T) {
76 serviceRegisterMock := serviceMocks.ServiceRegister{}
77 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId"})
78 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
79 helmManagerMock := helmMocks.HelmManager{}
80 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
81 serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
83 // Check no services published
84 var resultServices []publishapi.ServiceAPIDescription
85 result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
86 assert.Equal(t, http.StatusOK, result.Code())
88 // Parse JSON from the response body
89 err := result.UnmarshalJsonToObject(&resultServices)
90 assert.NoError(t, err, "error unmarshaling response")
92 // Check if the parsed array is empty
93 assert.Zero(t, len(resultServices))
95 apiName := "app-management"
96 namespace := "namespace"
97 repoName := "repoName"
98 chartName := "chartName"
99 releaseName := "releaseName"
100 description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
101 newServiceDescription := getServiceAPIDescription(aefId, apiName, description)
103 // Publish a service for provider
104 result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
105 assert.Equal(t, http.StatusCreated, result.Code())
107 var resultService publishapi.ServiceAPIDescription
109 err = result.UnmarshalJsonToObject(&resultService)
110 assert.NoError(t, err, "error unmarshaling response")
111 newApiId := "api_id_" + apiName
112 assert.Equal(t, newApiId, *resultService.ApiId)
113 assert.Equal(t, "http://example.com/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
114 newServiceDescription.ApiId = &newApiId
115 assert.True(t, serviceUnderTest.IsAPIPublished(aefId, apiName))
116 serviceRegisterMock.AssertCalled(t, "GetAefsForPublisher", apfId)
117 helmManagerMock.AssertCalled(t, "InstallHelmChart", namespace, repoName, chartName, releaseName)
118 assert.ElementsMatch(t, []string{aefId}, serviceUnderTest.getAllAefIds())
119 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
120 assert.Fail(t, "No event sent")
122 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
123 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIAVAILABLE, publishEvent.Events)
126 // Check that the service is published for the provider
127 result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
129 assert.Equal(t, http.StatusOK, result.Code())
130 err = result.UnmarshalJsonToObject(&resultService)
131 assert.NoError(t, err, "error unmarshaling response")
132 assert.Equal(t, *resultService.ApiId, newApiId)
134 // Publish the same service again should result in Forbidden
135 result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
137 assert.Equal(t, http.StatusForbidden, result.Code())
138 var resultError common29122.ProblemDetails
139 err = result.UnmarshalJsonToObject(&resultError)
140 assert.NoError(t, err, "error unmarshaling response")
141 assert.Contains(t, *resultError.Cause, "already published")
142 assert.Equal(t, http.StatusForbidden, *resultError.Status)
144 // Delete the service
145 helmManagerMock.On("UninstallHelmChart", mock.Anything, mock.Anything).Return(nil)
147 result = testutil.NewRequest().Delete("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
149 assert.Equal(t, http.StatusNoContent, result.Code())
150 helmManagerMock.AssertCalled(t, "UninstallHelmChart", namespace, chartName)
151 assert.Empty(t, serviceUnderTest.getAllAefIds())
153 // Check no services published for a provider
154 result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
156 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
157 assert.Fail(t, "No event sent")
159 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
160 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUNAVAILABLE, publishEvent.Events)
163 assert.Equal(t, http.StatusNotFound, result.Code())
165 // Check no services published
166 result = testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
167 assert.Equal(t, http.StatusOK, result.Code())
169 // Parse JSON from the response body
170 var responseArray []publishapi.ServiceAPIDescription
171 err = result.UnmarshalJsonToObject(&responseArray)
172 assert.NoError(t, err, "error unmarshaling response")
174 // Check if the parsed array is empty
175 assert.Zero(t, len(responseArray))
178 func TestPostUnpublishedServiceWithUnregisteredFunction(t *testing.T) {
181 serviceRegisterMock := serviceMocks.ServiceRegister{}
182 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{"otherAefId"})
183 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
184 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
186 newServiceDescription := getServiceAPIDescription(aefId, "apiName", "description")
189 result := testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
191 assert.Equal(t, http.StatusNotFound, result.Code())
192 var resultError common29122.ProblemDetails
193 err := result.UnmarshalJsonToObject(&resultError)
194 assert.NoError(t, err, "error unmarshaling response")
195 assert.Contains(t, *resultError.Cause, aefId)
196 assert.Contains(t, *resultError.Cause, "not registered")
197 assert.Equal(t, http.StatusNotFound, *resultError.Status)
200 func TestGetServices(t *testing.T) {
203 serviceRegisterMock := serviceMocks.ServiceRegister{}
204 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId})
205 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
206 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
208 // Check no services published
209 var resultServices []publishapi.ServiceAPIDescription
211 result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
212 assert.Equal(t, http.StatusOK, result.Code())
214 // Parse JSON from the response body
215 err := result.UnmarshalJsonToObject(&resultServices)
216 assert.NoError(t, err, "error unmarshaling response")
218 // Check if the parsed array is empty
219 assert.Zero(t, len(resultServices))
221 serviceDescription1 := getServiceAPIDescription(aefId, "api1", "Description")
222 serviceDescription2 := getServiceAPIDescription(aefId, "api2", "Description")
224 // Publish a service for provider
225 testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription1).Go(t, requestHandler)
226 testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription2).Go(t, requestHandler)
228 // Get all services for provider
229 result = testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
230 assert.Equal(t, http.StatusOK, result.Code())
232 err = result.UnmarshalJsonToObject(&resultServices)
233 assert.NoError(t, err, "error unmarshaling response")
235 assert.Len(t, resultServices, 2)
236 apiId1 := "api_id_api1"
237 serviceDescription1.ApiId = &apiId1
238 apiId2 := "api_id_api2"
239 serviceDescription2.ApiId = &apiId2
241 assert.Contains(t, resultServices, serviceDescription1)
242 assert.Contains(t, resultServices, serviceDescription2)
245 func TestGetPublishedServices(t *testing.T) {
246 serviceUnderTest := NewPublishService(nil, nil, nil)
248 profiles := make([]publishapi.AefProfile, 1)
249 serviceDescription := publishapi.ServiceAPIDescription{
250 AefProfiles: &profiles,
252 serviceUnderTest.publishedServices["publisher1"] = []publishapi.ServiceAPIDescription{
255 serviceUnderTest.publishedServices["publisher2"] = []publishapi.ServiceAPIDescription{
258 result := serviceUnderTest.GetAllPublishedServices()
259 assert.Len(t, result, 2)
262 func TestGetAllowedServices(t *testing.T) {
263 serviceUnderTest := NewPublishService(nil, nil, nil)
265 aefProfiles1 := []publishapi.AefProfile{
268 aefProfiles2 := []publishapi.AefProfile{
271 aefProfiles3 := []publishapi.AefProfile{
274 aefProfiles4 := []publishapi.AefProfile{
278 serviceUnderTest.publishedServices["publisher1"] = []publishapi.ServiceAPIDescription{
281 AefProfiles: &aefProfiles1,
285 AefProfiles: &aefProfiles2,
289 AefProfiles: &aefProfiles3,
293 AefProfiles: &aefProfiles4,
297 serviceDescription := publishapi.ServiceAPIDescription{
299 AefProfiles: &aefProfiles4,
301 serviceUnderTest.publishedServices["publisher2"] = []publishapi.ServiceAPIDescription{
305 allowedApiList := []publishapi.ServiceAPIDescription{
308 AefProfiles: &aefProfiles2,
312 AefProfiles: &aefProfiles3,
316 result := serviceUnderTest.GetAllowedPublishedServices(allowedApiList)
317 assert.Len(t, result, 2)
320 func TestUpdateDescription(t *testing.T) {
322 serviceApiId := "serviceApiId"
325 description := "description"
327 serviceRegisterMock := serviceMocks.ServiceRegister{}
328 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
329 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
330 helmManagerMock := helmMocks.HelmManager{}
331 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
332 serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
333 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
334 serviceDescription.ApiId = &serviceApiId
335 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
336 (*serviceDescription.AefProfiles)[0].AefId = aefId
339 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
340 updatedServiceDescription.ApiId = &serviceApiId
341 (*updatedServiceDescription.AefProfiles)[0].AefId = aefId
342 newDescription := "new description"
343 updatedServiceDescription.Description = &newDescription
344 newDomainName := "new domainName"
345 (*updatedServiceDescription.AefProfiles)[0].DomainName = &newDomainName
347 newProfileDomain := "new profile Domain name"
348 var protocol publishapi.Protocol = "HTTP_1_1"
350 test := append(*updatedServiceDescription.AefProfiles, publishapi.AefProfile{
352 DomainName: &newProfileDomain,
354 Versions: []publishapi.Version{
357 Resources: &[]publishapi.Resource{
359 CommType: "REQUEST_RESPONSE",
360 Operations: &[]publishapi.Operation{
372 updatedServiceDescription.AefProfiles = &test
374 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
376 var resultService publishapi.ServiceAPIDescription
377 assert.Equal(t, http.StatusOK, result.Code())
378 err := result.UnmarshalJsonToObject(&resultService)
379 assert.NoError(t, err, "error unmarshaling response")
380 assert.Equal(t, newDescription, *resultService.Description)
381 assert.Equal(t, newDomainName, *(*resultService.AefProfiles)[0].DomainName)
382 assert.Equal(t, "aefIdNew", (*resultService.AefProfiles)[1].AefId)
383 assert.True(t, serviceUnderTest.IsAPIPublished("aefIdNew", "path"))
385 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
386 assert.Fail(t, "No event sent")
388 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
389 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUPDATE, publishEvent.Events)
393 func TestFailedUpdateDescription(t *testing.T) {
395 serviceApiId := "serviceApiId"
396 // Trying to update a different serviceApiId will cause a 400 error
397 updatedServiceApiId := "updatedServiceApiId"
400 description := "description"
402 serviceRegisterMock := serviceMocks.ServiceRegister{}
403 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
404 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
405 helmManagerMock := helmMocks.HelmManager{}
406 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
407 serviceUnderTest, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
408 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
409 serviceDescription.ApiId = &serviceApiId
410 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
411 (*serviceDescription.AefProfiles)[0].AefId = aefId
413 // Modify the service
414 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
415 updatedServiceDescription.ApiId = &updatedServiceApiId
416 (*updatedServiceDescription.AefProfiles)[0].AefId = aefId
417 newDescription := "new description"
418 updatedServiceDescription.Description = &newDescription
419 newDomainName := "new domainName"
420 (*updatedServiceDescription.AefProfiles)[0].DomainName = &newDomainName
422 newProfileDomain := "new profile Domain name"
423 var protocol publishapi.Protocol = "HTTP_1_1"
425 test := append(*updatedServiceDescription.AefProfiles, publishapi.AefProfile{
427 DomainName: &newProfileDomain,
429 Versions: []publishapi.Version{
432 Resources: &[]publishapi.Resource{
434 CommType: "REQUEST_RESPONSE",
435 Operations: &[]publishapi.Operation{
446 updatedServiceDescription.AefProfiles = &test
448 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
449 assert.Equal(t, http.StatusBadRequest, result.Code())
451 var resultError common29122.ProblemDetails
452 err := result.UnmarshalJsonToObject(&resultError)
453 assert.NoError(t, err, "error unmarshaling response")
455 assert.Contains(t, *resultError.Cause, "ServiceAPIDescription ApiId doesn't match path parameter")
456 assert.Equal(t, http.StatusBadRequest, *resultError.Status)
459 func TestUpdateValidServiceWithDeletedFunction(t *testing.T) {
461 serviceApiId := "serviceApiId"
464 description := "description"
466 serviceRegisterMock := serviceMocks.ServiceRegister{}
467 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
468 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
469 helmManagerMock := helmMocks.HelmManager{}
470 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
471 serviceUnderTest, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
473 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
474 serviceDescription.ApiId = &serviceApiId
475 (*serviceDescription.AefProfiles)[0].AefId = aefId
477 newProfileDomain := "new profile Domain name"
478 var protocol publishapi.Protocol = "HTTP_1_1"
479 test := append(*serviceDescription.AefProfiles, publishapi.AefProfile{
481 DomainName: &newProfileDomain,
483 Versions: []publishapi.Version{
486 Resources: &[]publishapi.Resource{
488 CommType: "REQUEST_RESPONSE",
489 Operations: &[]publishapi.Operation{
500 serviceDescription.AefProfiles = &test
501 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
504 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
505 updatedServiceDescription.ApiId = &serviceApiId
506 test1 := append(*updatedServiceDescription.AefProfiles, publishapi.AefProfile{
508 DomainName: &newProfileDomain,
510 Versions: []publishapi.Version{
513 Resources: &[]publishapi.Resource{
515 CommType: "REQUEST_RESPONSE",
516 Operations: &[]publishapi.Operation{
527 updatedServiceDescription.AefProfiles = &test1
528 testFunc := []publishapi.AefProfile{
529 (*updatedServiceDescription.AefProfiles)[1],
532 updatedServiceDescription.AefProfiles = &testFunc
533 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
534 var resultService publishapi.ServiceAPIDescription
535 assert.Equal(t, http.StatusOK, result.Code())
536 err := result.UnmarshalJsonToObject(&resultService)
537 assert.NoError(t, err, "error unmarshaling response")
538 assert.Len(t, (*resultService.AefProfiles), 1)
539 assert.False(t, serviceUnderTest.IsAPIPublished("aefId", "path"))
543 func TestPublishInvalidService(t *testing.T) {
545 serviceRegisterMock := serviceMocks.ServiceRegister{}
546 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
548 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
549 newServiceDescription := getServiceAPIDescription("aefId", " ", "description")
552 result := testutil.NewRequest().Post("/apfId/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
554 assert.Equal(t, http.StatusBadRequest, result.Code())
555 var resultError common29122.ProblemDetails
556 err := result.UnmarshalJsonToObject(&resultError)
557 assert.NoError(t, err, "error unmarshaling response")
558 assert.Contains(t, *resultError.Cause, "missing")
559 assert.Contains(t, *resultError.Cause, "apiName")
560 assert.Equal(t, http.StatusBadRequest, *resultError.Status)
563 func getEcho(serviceRegister providermanagement.ServiceRegister, helmManager helmmanagement.HelmManager) (*PublishService, chan eventsapi.EventNotification, *echo.Echo) {
564 swagger, err := publishapi.GetSwagger()
566 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
570 swagger.Servers = nil
572 eventChannel := make(chan eventsapi.EventNotification)
573 ps := NewPublishService(serviceRegister, helmManager, eventChannel)
576 e.Use(echomiddleware.Logger())
577 e.Use(middleware.OapiRequestValidator(swagger))
579 publishapi.RegisterHandlers(e, ps)
580 return ps, eventChannel, e
583 func getServiceAPIDescription(aefId, apiName, description string) publishapi.ServiceAPIDescription {
584 domainName := "domainName"
585 var protocol publishapi.Protocol = "HTTP_1_1"
586 return publishapi.ServiceAPIDescription{
587 AefProfiles: &[]publishapi.AefProfile{
590 DomainName: &domainName,
592 Versions: []publishapi.Version{
595 Resources: &[]publishapi.Resource{
597 CommType: "REQUEST_RESPONSE",
598 Operations: &[]publishapi.Operation{
610 Description: &description,
614 // waitForEvent waits for the channel to receive an event for the specified max timeout.
615 // Returns true if waiting timed out.
616 func waitForEvent(ch chan eventsapi.EventNotification, timeout time.Duration) (*eventsapi.EventNotification, bool) {
619 return &event, false // completed normally
620 case <-time.After(timeout):
621 return nil, true // timed out