Updates for G Maintenance release
[nonrtric/plt/sme.git] / capifcore / internal / publishservice / publishservice_test.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022: Nordix Foundation
6 //   %%
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
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
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===================================
19 //
20
21 package publishservice
22
23 import (
24         "fmt"
25         "net/http"
26         "os"
27         "testing"
28         "time"
29
30         "oransc.org/nonrtric/capifcore/internal/common29122"
31         "oransc.org/nonrtric/capifcore/internal/eventsapi"
32         "oransc.org/nonrtric/capifcore/internal/providermanagement"
33
34         "github.com/labstack/echo/v4"
35
36         publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
37
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"
41
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"
47 )
48
49 func TestPublishUnpublishService(t *testing.T) {
50
51         apfId := "apfId"
52         aefId := "aefId"
53         serviceRegisterMock := serviceMocks.ServiceRegister{}
54         serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId"})
55         helmManagerMock := helmMocks.HelmManager{}
56         helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
57         serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
58
59         // Check no services published for provider
60         result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
61
62         assert.Equal(t, http.StatusNotFound, result.Code())
63
64         apiName := "app-management"
65         namespace := "namespace"
66         repoName := "repoName"
67         chartName := "chartName"
68         releaseName := "releaseName"
69         description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
70         newServiceDescription := getServiceAPIDescription(aefId, apiName, description)
71
72         // Publish a service for provider
73         result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
74
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")
89         } else {
90                 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
91                 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIAVAILABLE, publishEvent.Events)
92         }
93
94         // Check that the service is published for the provider
95         result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
96
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)
101
102         // Publish the same service again should result in Forbidden
103         result = testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
104
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)
111
112         // Delete the service
113         helmManagerMock.On("UninstallHelmChart", mock.Anything, mock.Anything).Return(nil)
114
115         result = testutil.NewRequest().Delete("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
116
117         assert.Equal(t, http.StatusNoContent, result.Code())
118         helmManagerMock.AssertCalled(t, "UninstallHelmChart", namespace, chartName)
119         assert.Empty(t, serviceUnderTest.getAllAefIds())
120
121         // Check no services published
122         result = testutil.NewRequest().Get("/"+apfId+"/service-apis/"+newApiId).Go(t, requestHandler)
123
124         if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
125                 assert.Fail(t, "No event sent")
126         } else {
127                 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
128                 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUNAVAILABLE, publishEvent.Events)
129         }
130
131         assert.Equal(t, http.StatusNotFound, result.Code())
132 }
133
134 func TestPostUnpublishedServiceWithUnregisteredFunction(t *testing.T) {
135         apfId := "apfId"
136         aefId := "aefId"
137         serviceRegisterMock := serviceMocks.ServiceRegister{}
138         serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{"otherAefId"})
139         _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
140
141         newServiceDescription := getServiceAPIDescription(aefId, "apiName", "description")
142
143         // Publish a service
144         result := testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
145
146         assert.Equal(t, http.StatusNotFound, result.Code())
147         var resultError common29122.ProblemDetails
148         err := result.UnmarshalBodyToObject(&resultError)
149         assert.NoError(t, err, "error unmarshaling response")
150         assert.Contains(t, *resultError.Cause, aefId)
151         assert.Contains(t, *resultError.Cause, "not registered")
152         assert.Equal(t, http.StatusNotFound, *resultError.Status)
153 }
154
155 func TestGetServices(t *testing.T) {
156         apfId := "apfId"
157         aefId := "aefId"
158         serviceRegisterMock := serviceMocks.ServiceRegister{}
159         serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId})
160         _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
161
162         // Check no services published for provider
163         result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
164
165         assert.Equal(t, http.StatusNotFound, result.Code())
166
167         serviceDescription1 := getServiceAPIDescription(aefId, "api1", "Description")
168         serviceDescription2 := getServiceAPIDescription(aefId, "api2", "Description")
169
170         // Publish a service for provider
171         testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription1).Go(t, requestHandler)
172         testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(serviceDescription2).Go(t, requestHandler)
173
174         // Get all services for provider
175         result = testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
176         assert.Equal(t, http.StatusOK, result.Code())
177         var resultServices []publishapi.ServiceAPIDescription
178         err := result.UnmarshalBodyToObject(&resultServices)
179         assert.NoError(t, err, "error unmarshaling response")
180         assert.Len(t, resultServices, 2)
181         apiId1 := "api_id_api1"
182         serviceDescription1.ApiId = &apiId1
183         apiId2 := "api_id_api2"
184         serviceDescription2.ApiId = &apiId2
185         assert.Contains(t, resultServices, serviceDescription1)
186         assert.Contains(t, resultServices, serviceDescription2)
187 }
188
189 func TestGetPublishedServices(t *testing.T) {
190         serviceUnderTest := NewPublishService(nil, nil, nil)
191
192         profiles := make([]publishapi.AefProfile, 1)
193         serviceDescription := publishapi.ServiceAPIDescription{
194                 AefProfiles: &profiles,
195         }
196         serviceUnderTest.publishedServices["publisher1"] = []publishapi.ServiceAPIDescription{
197                 serviceDescription,
198         }
199         serviceUnderTest.publishedServices["publisher2"] = []publishapi.ServiceAPIDescription{
200                 serviceDescription,
201         }
202         result := serviceUnderTest.GetAllPublishedServices()
203         assert.Len(t, result, 2)
204 }
205
206 func TestUpdateDescription(t *testing.T) {
207         apfId := "apfId"
208         serviceApiId := "serviceApiId"
209         aefId := "aefId"
210         apiName := "apiName"
211         description := "description"
212
213         serviceRegisterMock := serviceMocks.ServiceRegister{}
214         serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
215         helmManagerMock := helmMocks.HelmManager{}
216         helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
217         serviceUnderTest, eventChannel, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
218         serviceDescription := getServiceAPIDescription(aefId, apiName, description)
219         serviceDescription.ApiId = &serviceApiId
220         serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
221         (*serviceDescription.AefProfiles)[0].AefId = aefId
222
223         //Modify the service
224         updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
225         updatedServiceDescription.ApiId = &serviceApiId
226         (*updatedServiceDescription.AefProfiles)[0].AefId = aefId
227         newDescription := "new description"
228         updatedServiceDescription.Description = &newDescription
229         newDomainName := "new domainName"
230         (*updatedServiceDescription.AefProfiles)[0].DomainName = &newDomainName
231
232         newProfileDomain := "new profile Domain name"
233         var protocol publishapi.Protocol = "HTTP_1_1"
234         test := make([]publishapi.AefProfile, 1)
235         test = *updatedServiceDescription.AefProfiles
236         test = append(test, publishapi.AefProfile{
237
238                 AefId:      "aefIdNew",
239                 DomainName: &newProfileDomain,
240                 Protocol:   &protocol,
241                 Versions: []publishapi.Version{
242                         {
243                                 ApiVersion: "v1",
244                                 Resources: &[]publishapi.Resource{
245                                         {
246                                                 CommType: "REQUEST_RESPONSE",
247                                                 Operations: &[]publishapi.Operation{
248                                                         "POST",
249                                                 },
250                                                 ResourceName: "app",
251                                                 Uri:          "app",
252                                         },
253                                 },
254                         },
255                 },
256         },
257         )
258
259         updatedServiceDescription.AefProfiles = &test
260
261         result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
262
263         var resultService publishapi.ServiceAPIDescription
264         assert.Equal(t, http.StatusOK, result.Code())
265         err := result.UnmarshalBodyToObject(&resultService)
266         assert.NoError(t, err, "error unmarshaling response")
267         assert.Equal(t, newDescription, *resultService.Description)
268         assert.Equal(t, newDomainName, *(*resultService.AefProfiles)[0].DomainName)
269         assert.Equal(t, "aefIdNew", (*resultService.AefProfiles)[1].AefId)
270         assert.True(t, serviceUnderTest.IsAPIPublished("aefIdNew", "path"))
271
272         if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
273                 assert.Fail(t, "No event sent")
274         } else {
275                 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
276                 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUPDATE, publishEvent.Events)
277         }
278 }
279
280 func TestUpdateValidServiceWithDeletedFunction(t *testing.T) {
281         apfId := "apfId"
282         serviceApiId := "serviceApiId"
283         aefId := "aefId"
284         apiName := "apiName"
285         description := "description"
286
287         serviceRegisterMock := serviceMocks.ServiceRegister{}
288         serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId, "otherAefId", "aefIdNew"})
289         helmManagerMock := helmMocks.HelmManager{}
290         helmManagerMock.On("InstallHelmChart", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
291         serviceUnderTest, _, requestHandler := getEcho(&serviceRegisterMock, &helmManagerMock)
292
293         serviceDescription := getServiceAPIDescription(aefId, apiName, description)
294         serviceDescription.ApiId = &serviceApiId
295         (*serviceDescription.AefProfiles)[0].AefId = aefId
296
297         newProfileDomain := "new profile Domain name"
298         var protocol publishapi.Protocol = "HTTP_1_1"
299         test := make([]publishapi.AefProfile, 1)
300         test = *serviceDescription.AefProfiles
301         test = append(test, publishapi.AefProfile{
302
303                 AefId:      "aefIdNew",
304                 DomainName: &newProfileDomain,
305                 Protocol:   &protocol,
306                 Versions: []publishapi.Version{
307                         {
308                                 ApiVersion: "v1",
309                                 Resources: &[]publishapi.Resource{
310                                         {
311                                                 CommType: "REQUEST_RESPONSE",
312                                                 Operations: &[]publishapi.Operation{
313                                                         "POST",
314                                                 },
315                                                 ResourceName: "app",
316                                                 Uri:          "app",
317                                         },
318                                 },
319                         },
320                 },
321         },
322         )
323         serviceDescription.AefProfiles = &test
324         serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
325
326         //Modify the service
327         updatedServiceDescription := getServiceAPIDescription(aefId, apiName, description)
328         updatedServiceDescription.ApiId = &serviceApiId
329         test1 := make([]publishapi.AefProfile, 1)
330         test1 = *updatedServiceDescription.AefProfiles
331         test1 = append(test1, publishapi.AefProfile{
332
333                 AefId:      "aefIdNew",
334                 DomainName: &newProfileDomain,
335                 Protocol:   &protocol,
336                 Versions: []publishapi.Version{
337                         {
338                                 ApiVersion: "v1",
339                                 Resources: &[]publishapi.Resource{
340                                         {
341                                                 CommType: "REQUEST_RESPONSE",
342                                                 Operations: &[]publishapi.Operation{
343                                                         "POST",
344                                                 },
345                                                 ResourceName: "app",
346                                                 Uri:          "app",
347                                         },
348                                 },
349                         },
350                 },
351         },
352         )
353         updatedServiceDescription.AefProfiles = &test1
354         testFunc := []publishapi.AefProfile{
355                 (*updatedServiceDescription.AefProfiles)[1],
356         }
357
358         updatedServiceDescription.AefProfiles = &testFunc
359         result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
360         var resultService publishapi.ServiceAPIDescription
361         assert.Equal(t, http.StatusOK, result.Code())
362         err := result.UnmarshalBodyToObject(&resultService)
363         assert.NoError(t, err, "error unmarshaling response")
364         assert.Len(t, (*resultService.AefProfiles), 1)
365         assert.False(t, serviceUnderTest.IsAPIPublished("aefId", "path"))
366
367 }
368
369 func TestPublishInvalidService(t *testing.T) {
370         _, _, requestHandler := getEcho(nil, nil)
371         newServiceDescription := getServiceAPIDescription("aefId", " ", "description")
372
373         // Publish a service
374         result := testutil.NewRequest().Post("/apfId/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
375
376         assert.Equal(t, http.StatusBadRequest, result.Code())
377         var resultError common29122.ProblemDetails
378         err := result.UnmarshalBodyToObject(&resultError)
379         assert.NoError(t, err, "error unmarshaling response")
380         assert.Contains(t, *resultError.Cause, "missing")
381         assert.Contains(t, *resultError.Cause, "apiName")
382         assert.Equal(t, http.StatusBadRequest, *resultError.Status)
383
384 }
385 func getEcho(serviceRegister providermanagement.ServiceRegister, helmManager helmmanagement.HelmManager) (*PublishService, chan eventsapi.EventNotification, *echo.Echo) {
386         swagger, err := publishapi.GetSwagger()
387         if err != nil {
388                 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
389                 os.Exit(1)
390         }
391
392         swagger.Servers = nil
393
394         eventChannel := make(chan eventsapi.EventNotification)
395         ps := NewPublishService(serviceRegister, helmManager, eventChannel)
396
397         e := echo.New()
398         e.Use(echomiddleware.Logger())
399         e.Use(middleware.OapiRequestValidator(swagger))
400
401         publishapi.RegisterHandlers(e, ps)
402         return ps, eventChannel, e
403 }
404
405 func getServiceAPIDescription(aefId, apiName, description string) publishapi.ServiceAPIDescription {
406         domainName := "domainName"
407         var protocol publishapi.Protocol = "HTTP_1_1"
408         return publishapi.ServiceAPIDescription{
409                 AefProfiles: &[]publishapi.AefProfile{
410                         {
411                                 AefId:      aefId,
412                                 DomainName: &domainName,
413                                 Protocol:   &protocol,
414                                 Versions: []publishapi.Version{
415                                         {
416                                                 ApiVersion: "v1",
417                                                 Resources: &[]publishapi.Resource{
418                                                         {
419                                                                 CommType: "REQUEST_RESPONSE",
420                                                                 Operations: &[]publishapi.Operation{
421                                                                         "POST",
422                                                                 },
423                                                                 ResourceName: "app",
424                                                                 Uri:          "app",
425                                                         },
426                                                 },
427                                         },
428                                 },
429                         },
430                 },
431                 ApiName:     apiName,
432                 Description: &description,
433         }
434 }
435
436 // waitForEvent waits for the channel to receive an event for the specified max timeout.
437 // Returns true if waiting timed out.
438 func waitForEvent(ch chan eventsapi.EventNotification, timeout time.Duration) (*eventsapi.EventNotification, bool) {
439         select {
440         case event := <-ch:
441                 return &event, false // completed normally
442         case <-time.After(timeout):
443                 return nil, true // timed out
444         }
445 }