Updates for G Maintenance release
[nonrtric/plt/sme.git] / capifcore / internal / invokermanagement / invokermanagement_test.go
1 // -
2 //
3 //      ========================LICENSE_START=================================
4 //      O-RAN-SC
5 //      %%
6 //      Copyright (C) 2022: Nordix Foundation
7 //      %%
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
11 //
12 //           http://www.apache.org/licenses/LICENSE-2.0
13 //
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===================================
20 package invokermanagement
21
22 import (
23         "fmt"
24         "net/http"
25         "os"
26         "strings"
27         "testing"
28         "time"
29
30         "oransc.org/nonrtric/capifcore/internal/eventsapi"
31         "oransc.org/nonrtric/capifcore/internal/invokermanagementapi"
32
33         "github.com/labstack/echo/v4"
34
35         "oransc.org/nonrtric/capifcore/internal/common29122"
36         "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
37
38         "oransc.org/nonrtric/capifcore/internal/publishservice"
39         publishmocks "oransc.org/nonrtric/capifcore/internal/publishservice/mocks"
40
41         "github.com/deepmap/oapi-codegen/pkg/middleware"
42         "github.com/deepmap/oapi-codegen/pkg/testutil"
43         echomiddleware "github.com/labstack/echo/v4/middleware"
44         "github.com/stretchr/testify/assert"
45 )
46
47 func TestOnboardInvoker(t *testing.T) {
48         aefProfiles := []publishserviceapi.AefProfile{
49                 getAefProfile("aefId"),
50         }
51         apiId := "apiId"
52         publishedServices := []publishserviceapi.ServiceAPIDescription{
53                 {
54                         ApiId:       &apiId,
55                         AefProfiles: &aefProfiles,
56                 },
57         }
58         publishRegisterMock := publishmocks.PublishRegister{}
59         publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
60         invokerUnderTest, eventChannel, requestHandler := getEcho(&publishRegisterMock)
61
62         invokerInfo := "invoker a"
63         newInvoker := getInvoker(invokerInfo)
64
65         // Onboard a valid invoker
66         result := testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
67
68         assert.Equal(t, http.StatusCreated, result.Code())
69         var resultInvoker invokermanagementapi.APIInvokerEnrolmentDetails
70         err := result.UnmarshalBodyToObject(&resultInvoker)
71         assert.NoError(t, err, "error unmarshaling response")
72         wantedInvokerId := "api_invoker_id_" + strings.Replace(invokerInfo, " ", "_", 1)
73         assert.Equal(t, wantedInvokerId, *resultInvoker.ApiInvokerId)
74         assert.Equal(t, newInvoker.NotificationDestination, resultInvoker.NotificationDestination)
75         assert.Equal(t, newInvoker.OnboardingInformation.ApiInvokerPublicKey, resultInvoker.OnboardingInformation.ApiInvokerPublicKey)
76         wantedInvokerSecret := "onboarding_secret_" + strings.Replace(invokerInfo, " ", "_", 1)
77         assert.Equal(t, wantedInvokerSecret, *resultInvoker.OnboardingInformation.OnboardingSecret)
78         assert.Equal(t, "http://example.com/onboardedInvokers/"+*resultInvoker.ApiInvokerId, result.Recorder.Header().Get(echo.HeaderLocation))
79         assert.True(t, invokerUnderTest.IsInvokerRegistered(wantedInvokerId))
80         assert.True(t, invokerUnderTest.VerifyInvokerSecret(wantedInvokerId, wantedInvokerSecret))
81         publishRegisterMock.AssertCalled(t, "GetAllPublishedServices")
82         assert.Equal(t, invokermanagementapi.APIList(publishedServices), *resultInvoker.ApiList)
83         if invokerEvent, timeout := waitForEvent(eventChannel, 1*time.Second); timeout {
84                 assert.Fail(t, "No event sent")
85         } else {
86                 assert.Equal(t, *resultInvoker.ApiInvokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0])
87                 assert.Equal(t, eventsapi.CAPIFEventAPIINVOKERONBOARDED, invokerEvent.Events)
88         }
89
90         // Onboarding the same invoker should result in Forbidden
91         result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
92
93         assert.Equal(t, http.StatusForbidden, result.Code())
94         var problemDetails common29122.ProblemDetails
95         err = result.UnmarshalBodyToObject(&problemDetails)
96         assert.NoError(t, err, "error unmarshaling response")
97         assert.Equal(t, http.StatusForbidden, *problemDetails.Status)
98         assert.Contains(t, *problemDetails.Cause, "already onboarded")
99
100         // Onboard an invoker missing required NotificationDestination, should get 400 with problem details
101         invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
102                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
103                         ApiInvokerPublicKey: "newKey",
104                 },
105         }
106         result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
107
108         assert.Equal(t, http.StatusBadRequest, result.Code())
109         err = result.UnmarshalBodyToObject(&problemDetails)
110         assert.NoError(t, err, "error unmarshaling response")
111         assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
112         assert.Contains(t, *problemDetails.Cause, "missing")
113         assert.Contains(t, *problemDetails.Cause, "NotificationDestination")
114
115         // Onboard an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details
116         invalidInvoker = invokermanagementapi.APIInvokerEnrolmentDetails{
117                 NotificationDestination: "http://golang.cafe/",
118         }
119
120         result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
121
122         assert.Equal(t, http.StatusBadRequest, result.Code())
123         err = result.UnmarshalBodyToObject(&problemDetails)
124         assert.NoError(t, err, "error unmarshaling response")
125         assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
126         assert.Contains(t, *problemDetails.Cause, "missing")
127         assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey")
128 }
129
130 func TestDeleteInvoker(t *testing.T) {
131         invokerUnderTest, eventChannel, requestHandler := getEcho(nil)
132
133         invokerId := "invokerId"
134         newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
135                 ApiInvokerId:            &invokerId,
136                 NotificationDestination: "url",
137                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
138                         ApiInvokerPublicKey: "key",
139                 },
140         }
141         invokerUnderTest.onboardedInvokers[invokerId] = newInvoker
142         assert.True(t, invokerUnderTest.IsInvokerRegistered(invokerId))
143
144         // Delete the invoker
145         result := testutil.NewRequest().Delete("/onboardedInvokers/"+invokerId).Go(t, requestHandler)
146
147         assert.Equal(t, http.StatusNoContent, result.Code())
148         assert.False(t, invokerUnderTest.IsInvokerRegistered(invokerId))
149         if invokerEvent, timeout := waitForEvent(eventChannel, 1*time.Second); timeout {
150                 assert.Fail(t, "No event sent")
151         } else {
152                 assert.Equal(t, invokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0])
153                 assert.Equal(t, eventsapi.CAPIFEventAPIINVOKEROFFBOARDED, invokerEvent.Events)
154         }
155 }
156
157 func TestUpdateInvoker(t *testing.T) {
158         publishRegisterMock := publishmocks.PublishRegister{}
159         publishRegisterMock.On("GetAllPublishedServices").Return([]publishserviceapi.ServiceAPIDescription{})
160         serviceUnderTest, _, requestHandler := getEcho(&publishRegisterMock)
161
162         invokerId := "invokerId"
163         invoker := invokermanagementapi.APIInvokerEnrolmentDetails{
164                 ApiInvokerId:            &invokerId,
165                 NotificationDestination: "http://golang.cafe/",
166                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
167                         ApiInvokerPublicKey: "key",
168                 },
169         }
170         serviceUnderTest.onboardedInvokers[invokerId] = invoker
171
172         // Update the invoker with valid invoker, should return 200 with updated invoker details
173         newNotifURL := "http://golang.org/"
174         invoker.NotificationDestination = common29122.Uri(newNotifURL)
175         newPublicKey := "newPublicKey"
176         invoker.OnboardingInformation.ApiInvokerPublicKey = newPublicKey
177         result := testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invoker).Go(t, requestHandler)
178
179         var resultInvoker invokermanagementapi.APIInvokerEnrolmentDetails
180         assert.Equal(t, http.StatusOK, result.Code())
181         err := result.UnmarshalBodyToObject(&resultInvoker)
182         assert.NoError(t, err, "error unmarshaling response")
183         assert.Equal(t, invokerId, *resultInvoker.ApiInvokerId)
184         assert.Equal(t, newNotifURL, string(resultInvoker.NotificationDestination))
185         assert.Equal(t, newPublicKey, resultInvoker.OnboardingInformation.ApiInvokerPublicKey)
186
187         // Update with an invoker missing required NotificationDestination, should get 400 with problem details
188         validOnboardingInfo := invokermanagementapi.OnboardingInformation{
189                 ApiInvokerPublicKey: "key",
190         }
191         invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
192                 ApiInvokerId:          &invokerId,
193                 OnboardingInformation: validOnboardingInfo,
194         }
195         result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler)
196
197         assert.Equal(t, http.StatusBadRequest, result.Code())
198         var problemDetails common29122.ProblemDetails
199         err = result.UnmarshalBodyToObject(&problemDetails)
200         assert.NoError(t, err, "error unmarshaling response")
201         assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
202         assert.Contains(t, *problemDetails.Cause, "missing")
203         assert.Contains(t, *problemDetails.Cause, "NotificationDestination")
204
205         // Update with an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details
206         invalidInvoker.NotificationDestination = "http://golang.org/"
207         invalidInvoker.OnboardingInformation = invokermanagementapi.OnboardingInformation{}
208         result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler)
209
210         assert.Equal(t, http.StatusBadRequest, result.Code())
211         err = result.UnmarshalBodyToObject(&problemDetails)
212         assert.NoError(t, err, "error unmarshaling response")
213         assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
214         assert.Contains(t, *problemDetails.Cause, "missing")
215         assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey")
216
217         // Update with an invoker with other ApiInvokerId than the one provided in the URL, should get 400 with problem details
218         invalidId := "1"
219         invalidInvoker.ApiInvokerId = &invalidId
220         invalidInvoker.OnboardingInformation = validOnboardingInfo
221         result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler)
222
223         assert.Equal(t, http.StatusBadRequest, result.Code())
224         err = result.UnmarshalBodyToObject(&problemDetails)
225         assert.NoError(t, err, "error unmarshaling response")
226         assert.Equal(t, http.StatusBadRequest, *problemDetails.Status)
227         assert.Contains(t, *problemDetails.Cause, "not matching")
228         assert.Contains(t, *problemDetails.Cause, "ApiInvokerId")
229
230         // Update an invoker that has not been onboarded, should get 404 with problem details
231         missingId := "1"
232         invoker.ApiInvokerId = &missingId
233         result = testutil.NewRequest().Put("/onboardedInvokers/"+missingId).WithJsonBody(invoker).Go(t, requestHandler)
234
235         assert.Equal(t, http.StatusNotFound, result.Code())
236         err = result.UnmarshalBodyToObject(&problemDetails)
237         assert.NoError(t, err, "error unmarshaling response")
238         assert.Equal(t, http.StatusNotFound, *problemDetails.Status)
239         assert.Contains(t, *problemDetails.Cause, "not been onboarded")
240         assert.Contains(t, *problemDetails.Cause, "invoker")
241 }
242
243 func TestGetInvokerApiList(t *testing.T) {
244         aefProfiles1 := []publishserviceapi.AefProfile{
245                 getAefProfile("aefId"),
246         }
247         apiId := "apiId"
248         apiList := []publishserviceapi.ServiceAPIDescription{
249                 {
250                         ApiId:       &apiId,
251                         AefProfiles: &aefProfiles1,
252                 },
253         }
254         aefProfiles2 := []publishserviceapi.AefProfile{
255                 getAefProfile("aefId2"),
256         }
257         apiId2 := "apiId2"
258         apiList = append(apiList, publishserviceapi.ServiceAPIDescription{
259                 ApiId:       &apiId2,
260                 AefProfiles: &aefProfiles2,
261         })
262         publishRegisterMock := publishmocks.PublishRegister{}
263         publishRegisterMock.On("GetAllPublishedServices").Return(apiList)
264         invokerUnderTest, _, _ := getEcho(&publishRegisterMock)
265
266         invokerInfo := "invoker a"
267         newInvoker := getInvoker(invokerInfo)
268         invokerAId := "api_invoker_id_" + strings.ReplaceAll(invokerInfo, " ", "_")
269         newInvoker.ApiInvokerId = &invokerAId
270         invokerUnderTest.onboardedInvokers[invokerAId] = newInvoker
271         invokerInfo = "invoker b"
272         newInvoker = getInvoker(invokerInfo)
273         invokerId := "api_invoker_id_" + strings.ReplaceAll(invokerInfo, " ", "_")
274         newInvoker.ApiInvokerId = &invokerId
275         invokerUnderTest.onboardedInvokers[invokerId] = newInvoker
276
277         wantedApiList := invokerUnderTest.GetInvokerApiList(invokerAId)
278         assert.NotNil(t, wantedApiList)
279         assert.Len(t, *wantedApiList, 2)
280         assert.Equal(t, apiId, *(*wantedApiList)[0].ApiId)
281 }
282
283 func getEcho(publishRegister publishservice.PublishRegister) (*InvokerManager, chan eventsapi.EventNotification, *echo.Echo) {
284         swagger, err := invokermanagementapi.GetSwagger()
285         if err != nil {
286                 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
287                 os.Exit(1)
288         }
289
290         swagger.Servers = nil
291
292         eventChannel := make(chan eventsapi.EventNotification)
293         im := NewInvokerManager(publishRegister, eventChannel)
294
295         e := echo.New()
296         e.Use(echomiddleware.Logger())
297         e.Use(middleware.OapiRequestValidator(swagger))
298
299         invokermanagementapi.RegisterHandlers(e, im)
300         return im, eventChannel, e
301 }
302
303 func getAefProfile(aefId string) publishserviceapi.AefProfile {
304         return publishserviceapi.AefProfile{
305                 AefId: aefId,
306                 Versions: []publishserviceapi.Version{
307                         {
308                                 Resources: &[]publishserviceapi.Resource{
309                                         {
310                                                 CommType: "REQUEST_RESPONSE",
311                                         },
312                                 },
313                         },
314                 },
315         }
316 }
317
318 func getInvoker(invokerInfo string) invokermanagementapi.APIInvokerEnrolmentDetails {
319         newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
320                 ApiInvokerInformation:   &invokerInfo,
321                 NotificationDestination: "http://golang.cafe/",
322                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
323                         ApiInvokerPublicKey: "key",
324                 },
325                 ApiList: nil,
326         }
327         return newInvoker
328 }
329
330 // waitForEvent waits for the channel to receive an event for the specified max timeout.
331 // Returns true if waiting timed out.
332 func waitForEvent(ch chan eventsapi.EventNotification, timeout time.Duration) (*eventsapi.EventNotification, bool) {
333         select {
334         case event := <-ch:
335                 return &event, false // completed normally
336         case <-time.After(timeout):
337                 return nil, true // timed out
338         }
339 }