Moving add client in keycloak from security to invoker api
[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         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)
59
60         // Check no services published for provider
61         result := testutil.NewRequest().Get("/"+apfId+"/service-apis").Go(t, requestHandler)
62
63         assert.Equal(t, http.StatusNotFound, result.Code())
64
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)
72
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")
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         serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
140         _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
141
142         newServiceDescription := getServiceAPIDescription(aefId, "apiName", "description")
143
144         // Publish a service
145         result := testutil.NewRequest().Post("/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
146
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)
154 }
155
156 func TestGetServices(t *testing.T) {
157         apfId := "apfId"
158         aefId := "aefId"
159         serviceRegisterMock := serviceMocks.ServiceRegister{}
160         serviceRegisterMock.On("GetAefsForPublisher", apfId).Return([]string{aefId})
161         serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
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         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
225
226         //Modify the service
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
234
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{
240
241                 AefId:      "aefIdNew",
242                 DomainName: &newProfileDomain,
243                 Protocol:   &protocol,
244                 Versions: []publishapi.Version{
245                         {
246                                 ApiVersion: "v1",
247                                 Resources: &[]publishapi.Resource{
248                                         {
249                                                 CommType: "REQUEST_RESPONSE",
250                                                 Operations: &[]publishapi.Operation{
251                                                         "POST",
252                                                 },
253                                                 ResourceName: "app",
254                                                 Uri:          "app",
255                                         },
256                                 },
257                         },
258                 },
259         },
260         )
261
262         updatedServiceDescription.AefProfiles = &test
263
264         result := testutil.NewRequest().Put("/"+apfId+"/service-apis/"+serviceApiId).WithJsonBody(updatedServiceDescription).Go(t, requestHandler)
265
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"))
274
275         if publishEvent, ok := waitForEvent(eventChannel, 1*time.Second); ok {
276                 assert.Fail(t, "No event sent")
277         } else {
278                 assert.Equal(t, *resultService.ApiId, (*publishEvent.EventDetail.ApiIds)[0])
279                 assert.Equal(t, eventsapi.CAPIFEventSERVICEAPIUPDATE, publishEvent.Events)
280         }
281 }
282
283 func TestUpdateValidServiceWithDeletedFunction(t *testing.T) {
284         apfId := "apfId"
285         serviceApiId := "serviceApiId"
286         aefId := "aefId"
287         apiName := "apiName"
288         description := "description"
289
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)
296
297         serviceDescription := getServiceAPIDescription(aefId, apiName, description)
298         serviceDescription.ApiId = &serviceApiId
299         (*serviceDescription.AefProfiles)[0].AefId = aefId
300
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{
306
307                 AefId:      "aefIdNew",
308                 DomainName: &newProfileDomain,
309                 Protocol:   &protocol,
310                 Versions: []publishapi.Version{
311                         {
312                                 ApiVersion: "v1",
313                                 Resources: &[]publishapi.Resource{
314                                         {
315                                                 CommType: "REQUEST_RESPONSE",
316                                                 Operations: &[]publishapi.Operation{
317                                                         "POST",
318                                                 },
319                                                 ResourceName: "app",
320                                                 Uri:          "app",
321                                         },
322                                 },
323                         },
324                 },
325         },
326         )
327         serviceDescription.AefProfiles = &test
328         serviceUnderTest.publishedServices[apfId] = []publishapi.ServiceAPIDescription{serviceDescription}
329
330         //Modify the service
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{
336
337                 AefId:      "aefIdNew",
338                 DomainName: &newProfileDomain,
339                 Protocol:   &protocol,
340                 Versions: []publishapi.Version{
341                         {
342                                 ApiVersion: "v1",
343                                 Resources: &[]publishapi.Resource{
344                                         {
345                                                 CommType: "REQUEST_RESPONSE",
346                                                 Operations: &[]publishapi.Operation{
347                                                         "POST",
348                                                 },
349                                                 ResourceName: "app",
350                                                 Uri:          "app",
351                                         },
352                                 },
353                         },
354                 },
355         },
356         )
357         updatedServiceDescription.AefProfiles = &test1
358         testFunc := []publishapi.AefProfile{
359                 (*updatedServiceDescription.AefProfiles)[1],
360         }
361
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"))
370
371 }
372
373 func TestPublishInvalidService(t *testing.T) {
374         apfId := "apfId"
375         serviceRegisterMock := serviceMocks.ServiceRegister{}
376         serviceRegisterMock.On("IsPublishingFunctionRegistered", apfId).Return(true)
377
378         _, _, requestHandler := getEcho(&serviceRegisterMock, nil)
379         newServiceDescription := getServiceAPIDescription("aefId", " ", "description")
380
381         // Publish a service
382         result := testutil.NewRequest().Post("/apfId/service-apis").WithJsonBody(newServiceDescription).Go(t, requestHandler)
383
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)
391
392 }
393 func getEcho(serviceRegister providermanagement.ServiceRegister, helmManager helmmanagement.HelmManager) (*PublishService, chan eventsapi.EventNotification, *echo.Echo) {
394         swagger, err := publishapi.GetSwagger()
395         if err != nil {
396                 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
397                 os.Exit(1)
398         }
399
400         swagger.Servers = nil
401
402         eventChannel := make(chan eventsapi.EventNotification)
403         ps := NewPublishService(serviceRegister, helmManager, eventChannel)
404
405         e := echo.New()
406         e.Use(echomiddleware.Logger())
407         e.Use(middleware.OapiRequestValidator(swagger))
408
409         publishapi.RegisterHandlers(e, ps)
410         return ps, eventChannel, e
411 }
412
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{
418                         {
419                                 AefId:      aefId,
420                                 DomainName: &domainName,
421                                 Protocol:   &protocol,
422                                 Versions: []publishapi.Version{
423                                         {
424                                                 ApiVersion: "v1",
425                                                 Resources: &[]publishapi.Resource{
426                                                         {
427                                                                 CommType: "REQUEST_RESPONSE",
428                                                                 Operations: &[]publishapi.Operation{
429                                                                         "POST",
430                                                                 },
431                                                                 ResourceName: "app",
432                                                                 Uri:          "app",
433                                                         },
434                                                 },
435                                         },
436                                 },
437                         },
438                 },
439                 ApiName:     apiName,
440                 Description: &description,
441         }
442 }
443
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) {
447         select {
448         case event := <-ch:
449                 return &event, false // completed normally
450         case <-time.After(timeout):
451                 return nil, true // timed out
452         }
453 }