Improve error messages and tests
[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         "path"
27         "strings"
28         "testing"
29
30         "oransc.org/nonrtric/capifcore/internal/invokermanagementapi"
31
32         "github.com/labstack/echo/v4"
33
34         "oransc.org/nonrtric/capifcore/internal/common29122"
35         "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
36
37         "oransc.org/nonrtric/capifcore/internal/publishservice"
38         publishmocks "oransc.org/nonrtric/capifcore/internal/publishservice/mocks"
39
40         "github.com/deepmap/oapi-codegen/pkg/middleware"
41         "github.com/deepmap/oapi-codegen/pkg/testutil"
42         echomiddleware "github.com/labstack/echo/v4/middleware"
43         "github.com/stretchr/testify/assert"
44         "github.com/stretchr/testify/mock"
45 )
46
47 func TestOnboardInvoker(t *testing.T) {
48         publishRegisterMock := publishmocks.PublishRegister{}
49         publishRegisterMock.On("AreAPIsPublished", mock.Anything).Return(true)
50         invokerUnderTest, requestHandler := getEcho(&publishRegisterMock)
51
52         aefProfiles := []publishserviceapi.AefProfile{
53                 getAefProfile("aefId"),
54         }
55         apiId := "apiId"
56         var apiList invokermanagementapi.APIList = []publishserviceapi.ServiceAPIDescription{
57                 {
58                         ApiId:       &apiId,
59                         AefProfiles: &aefProfiles,
60                 },
61         }
62         invokerInfo := "invoker a"
63         newInvoker := getInvoker(invokerInfo, apiList)
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, "AreAPIsPublished", mock.Anything)
82
83         // Onboard an invoker missing required NotificationDestination, should get 400 with problem details
84         invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
85                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
86                         ApiInvokerPublicKey: "key",
87                 },
88         }
89         result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
90
91         assert.Equal(t, http.StatusBadRequest, result.Code())
92         var problemDetails common29122.ProblemDetails
93         err = result.UnmarshalBodyToObject(&problemDetails)
94         assert.NoError(t, err, "error unmarshaling response")
95         badRequest := http.StatusBadRequest
96         assert.Equal(t, &badRequest, problemDetails.Status)
97         assert.Contains(t, *problemDetails.Cause, "missing")
98         assert.Contains(t, *problemDetails.Cause, "NotificationDestination")
99
100         // Onboard an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details
101         invalidInvoker = invokermanagementapi.APIInvokerEnrolmentDetails{
102                 NotificationDestination: "url",
103         }
104
105         result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
106
107         assert.Equal(t, http.StatusBadRequest, result.Code())
108         err = result.UnmarshalBodyToObject(&problemDetails)
109         assert.NoError(t, err, "error unmarshaling response")
110         assert.Equal(t, &badRequest, problemDetails.Status)
111         assert.Contains(t, *problemDetails.Cause, "missing")
112         assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey")
113 }
114
115 func TestDeleteInvoker(t *testing.T) {
116         invokerUnderTest, requestHandler := getEcho(nil)
117
118         newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
119                 NotificationDestination: "url",
120                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
121                         ApiInvokerPublicKey: "key",
122                 },
123         }
124
125         // Onboard an invoker
126         result := testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
127
128         invokerUrl := result.Recorder.Header().Get(echo.HeaderLocation)
129         assert.True(t, invokerUnderTest.IsInvokerRegistered(path.Base(invokerUrl)))
130
131         // Delete the invoker
132         result = testutil.NewRequest().Delete(invokerUrl).Go(t, requestHandler)
133
134         assert.Equal(t, http.StatusNoContent, result.Code())
135         assert.False(t, invokerUnderTest.IsInvokerRegistered(path.Base(invokerUrl)))
136 }
137
138 func TestUpdateInvoker(t *testing.T) {
139         _, requestHandler := getEcho(nil)
140
141         newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
142                 NotificationDestination: "url",
143                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
144                         ApiInvokerPublicKey: "key",
145                 },
146         }
147
148         // Onboard an invoker
149         result := testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
150         var resultInvoker invokermanagementapi.APIInvokerEnrolmentDetails
151         result.UnmarshalBodyToObject(&resultInvoker)
152
153         invokerId := resultInvoker.ApiInvokerId
154         invokerUrl := result.Recorder.Header().Get(echo.HeaderLocation)
155
156         // Update the invoker with valid invoker, should return 200 with invoker details
157         result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(resultInvoker).Go(t, requestHandler)
158
159         assert.Equal(t, http.StatusOK, result.Code())
160         err := result.UnmarshalBodyToObject(&resultInvoker)
161         assert.NoError(t, err, "error unmarshaling response")
162         assert.Equal(t, invokerId, resultInvoker.ApiInvokerId)
163         assert.Equal(t, newInvoker.NotificationDestination, resultInvoker.NotificationDestination)
164         assert.Equal(t, newInvoker.OnboardingInformation.ApiInvokerPublicKey, resultInvoker.OnboardingInformation.ApiInvokerPublicKey)
165
166         // Update with an invoker missing required NotificationDestination, should get 400 with problem details
167         validOnboardingInfo := invokermanagementapi.OnboardingInformation{
168                 ApiInvokerPublicKey: "key",
169         }
170         invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
171                 ApiInvokerId:          invokerId,
172                 OnboardingInformation: validOnboardingInfo,
173         }
174         result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(invalidInvoker).Go(t, requestHandler)
175
176         assert.Equal(t, http.StatusBadRequest, result.Code())
177         var problemDetails common29122.ProblemDetails
178         err = result.UnmarshalBodyToObject(&problemDetails)
179         assert.NoError(t, err, "error unmarshaling response")
180         badRequest := http.StatusBadRequest
181         assert.Equal(t, &badRequest, problemDetails.Status)
182         assert.Contains(t, *problemDetails.Cause, "missing")
183         assert.Contains(t, *problemDetails.Cause, "NotificationDestination")
184
185         // Update with an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details
186         invalidInvoker.NotificationDestination = "url"
187         invalidInvoker.OnboardingInformation = invokermanagementapi.OnboardingInformation{}
188         result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(invalidInvoker).Go(t, requestHandler)
189
190         assert.Equal(t, http.StatusBadRequest, result.Code())
191         err = result.UnmarshalBodyToObject(&problemDetails)
192         assert.NoError(t, err, "error unmarshaling response")
193         assert.Equal(t, &badRequest, problemDetails.Status)
194         assert.Contains(t, *problemDetails.Cause, "missing")
195         assert.Contains(t, *problemDetails.Cause, "OnboardingInformation.ApiInvokerPublicKey")
196
197         // Update with an invoker with other ApiInvokerId than the one provided in the URL, should get 400 with problem details
198         invalidId := "1"
199         invalidInvoker.ApiInvokerId = &invalidId
200         invalidInvoker.OnboardingInformation = validOnboardingInfo
201         result = testutil.NewRequest().Put(invokerUrl).WithJsonBody(invalidInvoker).Go(t, requestHandler)
202
203         assert.Equal(t, http.StatusBadRequest, result.Code())
204         err = result.UnmarshalBodyToObject(&problemDetails)
205         assert.NoError(t, err, "error unmarshaling response")
206         assert.Equal(t, &badRequest, problemDetails.Status)
207         assert.Contains(t, *problemDetails.Cause, "not matching")
208         assert.Contains(t, *problemDetails.Cause, "ApiInvokerId")
209
210         // Update an invoker that has not been onboarded, shold get 404 with problem details
211         missingId := "1"
212         newInvoker.ApiInvokerId = &missingId
213         result = testutil.NewRequest().Put("/onboardedInvokers/"+missingId).WithJsonBody(newInvoker).Go(t, requestHandler)
214
215         assert.Equal(t, http.StatusNotFound, result.Code())
216         err = result.UnmarshalBodyToObject(&problemDetails)
217         assert.NoError(t, err, "error unmarshaling response")
218         notFound := http.StatusNotFound
219         assert.Equal(t, &notFound, problemDetails.Status)
220         assert.Contains(t, *problemDetails.Cause, "not been onboarded")
221         assert.Contains(t, *problemDetails.Cause, "invoker")
222 }
223
224 func TestGetInvokerApiList(t *testing.T) {
225         publishRegisterMock := publishmocks.PublishRegister{}
226         publishRegisterMock.On("AreAPIsPublished", mock.Anything).Return(true)
227         invokerUnderTest, requestHandler := getEcho(&publishRegisterMock)
228
229         // Onboard two invokers
230         aefProfiles := []publishserviceapi.AefProfile{
231                 getAefProfile("aefId"),
232         }
233         apiId := "apiId"
234         var apiList invokermanagementapi.APIList = []publishserviceapi.ServiceAPIDescription{
235                 {
236                         ApiId:       &apiId,
237                         AefProfiles: &aefProfiles,
238                 },
239         }
240         invokerInfo := "invoker a"
241         newInvoker := getInvoker(invokerInfo, apiList)
242         testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
243         aefProfiles = []publishserviceapi.AefProfile{
244                 getAefProfile("aefId2"),
245         }
246         apiId2 := "apiId2"
247         apiList = []publishserviceapi.ServiceAPIDescription{
248                 {
249                         ApiId:       &apiId2,
250                         AefProfiles: &aefProfiles,
251                 },
252         }
253         newInvoker = getInvoker("invoker b", apiList)
254         testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
255
256         wantedApiList := invokerUnderTest.GetInvokerApiList("api_invoker_id_" + strings.Replace(invokerInfo, " ", "_", 1))
257         assert.NotNil(t, wantedApiList)
258         assert.Equal(t, apiId, *(*wantedApiList)[0].ApiId)
259 }
260
261 func getEcho(publishRegister publishservice.PublishRegister) (*InvokerManager, *echo.Echo) {
262         swagger, err := invokermanagementapi.GetSwagger()
263         if err != nil {
264                 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
265                 os.Exit(1)
266         }
267
268         swagger.Servers = nil
269
270         im := NewInvokerManager(publishRegister)
271
272         e := echo.New()
273         e.Use(echomiddleware.Logger())
274         e.Use(middleware.OapiRequestValidator(swagger))
275
276         invokermanagementapi.RegisterHandlers(e, im)
277         return im, e
278 }
279
280 func getAefProfile(aefId string) publishserviceapi.AefProfile {
281         return publishserviceapi.AefProfile{
282                 AefId: aefId,
283                 Versions: []publishserviceapi.Version{
284                         {
285                                 Resources: &[]publishserviceapi.Resource{
286                                         {
287                                                 CommType: "REQUEST_RESPONSE",
288                                         },
289                                 },
290                         },
291                 },
292         }
293 }
294
295 func getInvoker(invokerInfo string, apiList invokermanagementapi.APIList) invokermanagementapi.APIInvokerEnrolmentDetails {
296         newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
297                 ApiInvokerInformation:   &invokerInfo,
298                 NotificationDestination: "url",
299                 OnboardingInformation: invokermanagementapi.OnboardingInformation{
300                         ApiInvokerPublicKey: "key",
301                 },
302                 ApiList: &apiList,
303         }
304         return newInvoker
305 }