2 // ========================LICENSE_START=================================
5 // Copyright (C) 2022: Nordix Foundation
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
30 "oransc.org/nonrtric/capifcore/internal/common29122"
31 "oransc.org/nonrtric/capifcore/internal/eventsapi"
32 "oransc.org/nonrtric/capifcore/internal/providermanagement"
34 "github.com/labstack/echo/v4"
36 publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
38 "oransc.org/nonrtric/capifcore/internal/helmmanagement"
39 helmMocks "oransc.org/nonrtric/capifcore/internal/helmmanagement/mocks"
40 serviceMocks "oransc.org/nonrtric/capifcore/internal/providermanagement/mocks"
42 "github.com/deepmap/oapi-codegen/pkg/middleware"
43 "github.com/deepmap/oapi-codegen/pkg/testutil"
44 echomiddleware "github.com/labstack/echo/v4/middleware"
45 "github.com/stretchr/testify/assert"
46 "github.com/stretchr/testify/mock"
49 func TestPublishUnpublishService(t *testing.T) {
53 serviceRegisterMock := serviceMocks.ServiceRegister{}
54 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId"})
55 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
56 helmManagerMock := helmMocks.HelmManager{}
57 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
58 serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
60 // Check no services published for provider
61 result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
63 assert.Equal(t, http.StatusNotFound, result.Code())
65 apiName := "app-management"
66 namespace := "namespace"
67 repoName := "repoName"
68 chartName := "chartName"
69 releaseName := "releaseName"
70 description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
71 newServiceDescription := getServiceAPIDescription(aefId, apiName, description)
73 // Publish a service for provider
74 result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
75 assert.Equal(t, http.StatusCreated, result.Code())
76 var resultService publishapi.ServiceAPIDescription
77 err := result.UnmarshalBodyToObject(&resultService)
78 assert.NoError(t, err, "error unmarshaling response")
79 newApiId := "api_id_" + apiName
80 assert.Equal(t, newApiId, *resultService.ApiId)
81 assert.Equal(t, "http://example.com/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
82 newServiceDescription.ApiId = &newApiId
83 assert.True(t, serviceUnderTest.IsAPIPublished(aefId, apiName))
84 serviceRegisterMock.AssertCalled(t, "GetAefsForPublisher", apfId)
85 helmManagerMock.AssertCalled(t, "InstallHelmChart", namespace, repoName, chartName, releaseName)
86 assert.ElementsMatch(t, []string{aefId}, serviceUnderTest.getAllAefIds())
87 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
88 assert.Fail(t, "No event sent")
90 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
91 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIAVAILABLE, publishEvent.Events)
94 // Check that the service is published for the provider
95 result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
97 assert.Equal(t, http.StatusOK, result.Code())
98 err = result.UnmarshalBodyToObject(&resultService)
99 assert.NoError(t, err, "error unmarshaling response")
100 assert.Equal(t, *resultService.ApiId, newApiId)
102 // Publish the same service again should result in Forbidden
103 result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
105 assert.Equal(t, http.StatusForbidden, result.Code())
106 var resultError common29122.ProblemDetails
107 err = result.UnmarshalBodyToObject(&resultError)
108 assert.NoError(t, err, "error unmarshaling response")
109 assert.Contains(t, *resultError.Cause, "already published")
110 assert.Equal(t, http.StatusForbidden, *resultError.Status)
112 // Delete the service
113 helmManagerMock.On("UninstallHelmChart", mock.Anything, mock.Anything).Return(nil)
115 result = testutil.NewRequest().Delete("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
117 assert.Equal(t, http.StatusNoContent, result.Code())
118 helmManagerMock.AssertCalled(t, "UninstallHelmChart", namespace, chartName)
119 assert.Empty(t, serviceUnderTest.getAllAefIds())
121 // Check no services published
122 result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
124 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
125 assert.Fail(t, "No event sent")
127 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
128 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUNAVAILABLE, publishEvent.Events)
131 assert.Equal(t, http.StatusNotFound, result.Code())
134 func TestPostUnpublishedServiceWithUnregisteredFunction(t *testing.T) {
137 serviceRegisterMock := serviceMocks.ServiceRegister{}
138 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{"otherAefId"})
139 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
140 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
142 newServiceDescription := getServiceAPIDescription(aefId, "apiName", "description")
145 result := testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
147 assert.Equal(t, http.StatusNotFound, result.Code())
148 var resultError common29122.ProblemDetails
149 err := result.UnmarshalBodyToObject(&resultError)
150 assert.NoError(t, err, "error unmarshaling response")
151 assert.Contains(t, *resultError.Cause, aefId)
152 assert.Contains(t, *resultError.Cause, "not registered")
153 assert.Equal(t, http.StatusNotFound, *resultError.Status)
156 func TestGetServices(t *testing.T) {
159 serviceRegisterMock := serviceMocks.ServiceRegister{}
160 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId})
161 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
162 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
164 // Check no services published for provider
165 result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
167 assert.Equal(t, http.StatusNotFound, result.Code())
169 serviceDescription1 := getServiceAPIDescription(aefId, "api1", "Description")
170 serviceDescription2 := getServiceAPIDescription(aefId, "api2", "Description")
172 // Publish a service for provider
173 testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription1).Go(t, requestHandler)
174 testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription2).Go(t, requestHandler)
176 // Get all services for provider
177 result = testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
178 assert.Equal(t, http.StatusOK, result.Code())
179 var resultServices []publishapi.ServiceAPIDescription
180 err := result.UnmarshalBodyToObject(&resultServices)
181 assert.NoError(t, err, "error unmarshaling response")
182 assert.Len(t, resultServices, 2)
183 apiId1 := "api_id_api1"
184 serviceDescription1.ApiId = &apiId1
185 apiId2 := "api_id_api2"
186 serviceDescription2.ApiId = &apiId2
187 assert.Contains(t, resultServices, serviceDescription1)
188 assert.Contains(t, resultServices, serviceDescription2)
191 func TestGetPublishedServices(t *testing.T) {
192 serviceUnderTest := NewPublishService(nil, nil, nil)
194 profiles := make([]publishapi.AefProfile, 1)
195 serviceDescription := publishapi.ServiceAPIDescription{
196 AefProfiles: &profiles,
198 serviceUnderTest.publishedServices["publisher1"] = []publishapi.ServiceAPIDescription{
201 serviceUnderTest.publishedServices["publisher2"] = []publishapi.ServiceAPIDescription{
204 result := serviceUnderTest.GetAllPublishedServices()
205 assert.Len(t, result, 2)
208 func TestUpdateDescription(t *testing.T) {
210 serviceApiId := "serviceApiId"
213 description := "description"
215 serviceRegisterMock := serviceMocks.ServiceRegister{}
216 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
217 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
218 helmManagerMock := helmMocks.HelmManager{}
219 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
220 serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
221 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
222 serviceDescription.ApiId = &serviceApiId
223 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
224 (*serviceDescription.AefProfiles)[0].AefId = aefId
227 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
228 updatedServiceDescription.ApiId = &serviceApiId
229 (*updatedServiceDescription.AefProfiles)[0].AefId = aefId
230 newDescription := "new description"
231 updatedServiceDescription.Description = &newDescription
232 newDomainName := "new domainName"
233 (*updatedServiceDescription.AefProfiles)[0].DomainName = &newDomainName
235 newProfileDomain := "new profile Domain name"
236 var protocol publishapi.Protocol = "HTTP_1_1"
237 test := make([]publishapi.AefProfile, 1)
238 test = *updatedServiceDescription.AefProfiles
239 test = append(test, publishapi.AefProfile{
242 DomainName: &newProfileDomain,
244 Versions: []publishapi.Version{
247 Resources: &[]publishapi.Resource{
249 CommType: "REQUEST_RESPONSE",
250 Operations: &[]publishapi.Operation{
262 updatedServiceDescription.AefProfiles = &test
264 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
266 var resultService publishapi.ServiceAPIDescription
267 assert.Equal(t, http.StatusOK, result.Code())
268 err := result.UnmarshalBodyToObject(&resultService)
269 assert.NoError(t, err, "error unmarshaling response")
270 assert.Equal(t, newDescription, *resultService.Description)
271 assert.Equal(t, newDomainName, *(*resultService.AefProfiles)[0].DomainName)
272 assert.Equal(t, "aefIdNew", (*resultService.AefProfiles)[1].AefId)
273 assert.True(t, serviceUnderTest.IsAPIPublished("aefIdNew", "path"))
275 if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
276 assert.Fail(t, "No event sent")
278 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
279 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUPDATE, publishEvent.Events)
283 func TestUpdateValidServiceWithDeletedFunction(t *testing.T) {
285 serviceApiId := "serviceApiId"
288 description := "description"
290 serviceRegisterMock := serviceMocks.ServiceRegister{}
291 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
292 serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
293 helmManagerMock := helmMocks.HelmManager{}
294 helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
295 serviceUnderTest, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
297 serviceDescription := getServiceAPIDescription(aefId, apiName, description)
298 serviceDescription.ApiId = &serviceApiId
299 (*serviceDescription.AefProfiles)[0].AefId = aefId
301 newProfileDomain := "new profile Domain name"
302 var protocol publishapi.Protocol = "HTTP_1_1"
303 test := make([]publishapi.AefProfile, 1)
304 test = *serviceDescription.AefProfiles
305 test = append(test, publishapi.AefProfile{
308 DomainName: &newProfileDomain,
310 Versions: []publishapi.Version{
313 Resources: &[]publishapi.Resource{
315 CommType: "REQUEST_RESPONSE",
316 Operations: &[]publishapi.Operation{
327 serviceDescription.AefProfiles = &test
328 serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
331 updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
332 updatedServiceDescription.ApiId = &serviceApiId
333 test1 := make([]publishapi.AefProfile, 1)
334 test1 = *updatedServiceDescription.AefProfiles
335 test1 = append(test1, publishapi.AefProfile{
338 DomainName: &newProfileDomain,
340 Versions: []publishapi.Version{
343 Resources: &[]publishapi.Resource{
345 CommType: "REQUEST_RESPONSE",
346 Operations: &[]publishapi.Operation{
357 updatedServiceDescription.AefProfiles = &test1
358 testFunc := []publishapi.AefProfile{
359 (*updatedServiceDescription.AefProfiles)[1],
362 updatedServiceDescription.AefProfiles = &testFunc
363 result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
364 var resultService publishapi.ServiceAPIDescription
365 assert.Equal(t, http.StatusOK, result.Code())
366 err := result.UnmarshalBodyToObject(&resultService)
367 assert.NoError(t, err, "error unmarshaling response")
368 assert.Len(t, (*resultService.AefProfiles), 1)
369 assert.False(t, serviceUnderTest.IsAPIPublished("aefId", "path"))
373 func TestPublishInvalidService(t *testing.T) {
375 serviceRegisterMock := serviceMocks.ServiceRegister{}
376 serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
378 _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
379 newServiceDescription := getServiceAPIDescription("aefId", " ", "description")
382 result := testutil.NewRequest().Post("/apfId/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
384 assert.Equal(t, http.StatusBadRequest, result.Code())
385 var resultError common29122.ProblemDetails
386 err := result.UnmarshalBodyToObject(&resultError)
387 assert.NoError(t, err, "error unmarshaling response")
388 assert.Contains(t, *resultError.Cause, "missing")
389 assert.Contains(t, *resultError.Cause, "apiName")
390 assert.Equal(t, http.StatusBadRequest, *resultError.Status)
393 func getEcho(serviceRegister providermanagement.ServiceRegister, helmManager helmmanagement.HelmManager) (*PublishService, chan eventsapi.EventNotification, *echo.Echo) {
394 swagger, err := publishapi.GetSwagger()
396 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
400 swagger.Servers = nil
402 eventChannel := make(chan eventsapi.EventNotification)
403 ps := NewPublishService(serviceRegister, helmManager, eventChannel)
406 e.Use(echomiddleware.Logger())
407 e.Use(middleware.OapiRequestValidator(swagger))
409 publishapi.RegisterHandlers(e, ps)
410 return ps, eventChannel, e
413 func getServiceAPIDescription(aefId, apiName, description string) publishapi.ServiceAPIDescription {
414 domainName := "domainName"
415 var protocol publishapi.Protocol = "HTTP_1_1"
416 return publishapi.ServiceAPIDescription{
417 AefProfiles: &[]publishapi.AefProfile{
420 DomainName: &domainName,
422 Versions: []publishapi.Version{
425 Resources: &[]publishapi.Resource{
427 CommType: "REQUEST_RESPONSE",
428 Operations: &[]publishapi.Operation{
440 Description: &description,
444 // waitForEvent waits for the channel to receive an event for the specified max timeout.
445 // Returns true if waiting timed out.
446 func waitForEvent(ch chan eventsapi.EventNotification, timeout time.Duration) (*eventsapi.EventNotification, bool) {
449 return &event, false // completed normally
450 case <-time.After(timeout):
451 return nil, true // timed out