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