3 // ========================LICENSE_START=================================
6 // Copyright (C) 2022: Nordix Foundation
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
12 // http://www.apache.org/licenses/LICENSE-2.0
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
30 "oransc.org/nonrtric/capifcore/internal/eventsapi"
31 "oransc.org/nonrtric/capifcore/internal/invokermanagementapi"
33 "github.com/labstack/echo/v4"
35 "oransc.org/nonrtric/capifcore/internal/common29122"
36 "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
38 "oransc.org/nonrtric/capifcore/internal/publishservice"
39 publishmocks "oransc.org/nonrtric/capifcore/internal/publishservice/mocks"
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"
47 func TestOnboardInvoker(t *testing.T) {
48 aefProfiles := []publishserviceapi.AefProfile{
49 getAefProfile("aefId"),
52 publishedServices := []publishserviceapi.ServiceAPIDescription{
55 AefProfiles: &aefProfiles,
58 publishRegisterMock := publishmocks.PublishRegister{}
59 publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
60 invokerUnderTest, eventChannel, requestHandler := getEcho(&publishRegisterMock)
62 invokerInfo := "invoker a"
63 newInvoker := getInvoker(invokerInfo)
65 // Onboard a valid invoker
66 result := testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
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")
86 assert.Equal(t, *resultInvoker.ApiInvokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0])
87 assert.Equal(t, eventsapi.CAPIFEventAPIINVOKERONBOARDED, invokerEvent.Events)
90 // Onboarding the same invoker should result in Forbidden
91 result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(newInvoker).Go(t, requestHandler)
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")
100 // Onboard an invoker missing required NotificationDestination, should get 400 with problem details
101 invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
102 OnboardingInformation: invokermanagementapi.OnboardingInformation{
103 ApiInvokerPublicKey: "newKey",
106 result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
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")
115 // Onboard an invoker missing required OnboardingInformation.ApiInvokerPublicKey, should get 400 with problem details
116 invalidInvoker = invokermanagementapi.APIInvokerEnrolmentDetails{
117 NotificationDestination: "http://golang.cafe/",
120 result = testutil.NewRequest().Post("/onboardedInvokers").WithJsonBody(invalidInvoker).Go(t, requestHandler)
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")
130 func TestDeleteInvoker(t *testing.T) {
131 invokerUnderTest, eventChannel, requestHandler := getEcho(nil)
133 invokerId := "invokerId"
134 newInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
135 ApiInvokerId: &invokerId,
136 NotificationDestination: "url",
137 OnboardingInformation: invokermanagementapi.OnboardingInformation{
138 ApiInvokerPublicKey: "key",
141 invokerUnderTest.onboardedInvokers[invokerId] = newInvoker
142 assert.True(t, invokerUnderTest.IsInvokerRegistered(invokerId))
144 // Delete the invoker
145 result := testutil.NewRequest().Delete("/onboardedInvokers/"+invokerId).Go(t, requestHandler)
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")
152 assert.Equal(t, invokerId, (*invokerEvent.EventDetail.ApiInvokerIds)[0])
153 assert.Equal(t, eventsapi.CAPIFEventAPIINVOKEROFFBOARDED, invokerEvent.Events)
157 func TestUpdateInvoker(t *testing.T) {
158 publishRegisterMock := publishmocks.PublishRegister{}
159 publishRegisterMock.On("GetAllPublishedServices").Return([]publishserviceapi.ServiceAPIDescription{})
160 serviceUnderTest, _, requestHandler := getEcho(&publishRegisterMock)
162 invokerId := "invokerId"
163 invoker := invokermanagementapi.APIInvokerEnrolmentDetails{
164 ApiInvokerId: &invokerId,
165 NotificationDestination: "http://golang.cafe/",
166 OnboardingInformation: invokermanagementapi.OnboardingInformation{
167 ApiInvokerPublicKey: "key",
170 serviceUnderTest.onboardedInvokers[invokerId] = invoker
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)
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)
187 // Update with an invoker missing required NotificationDestination, should get 400 with problem details
188 validOnboardingInfo := invokermanagementapi.OnboardingInformation{
189 ApiInvokerPublicKey: "key",
191 invalidInvoker := invokermanagementapi.APIInvokerEnrolmentDetails{
192 ApiInvokerId: &invokerId,
193 OnboardingInformation: validOnboardingInfo,
195 result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler)
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")
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)
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")
217 // Update with an invoker with other ApiInvokerId than the one provided in the URL, should get 400 with problem details
219 invalidInvoker.ApiInvokerId = &invalidId
220 invalidInvoker.OnboardingInformation = validOnboardingInfo
221 result = testutil.NewRequest().Put("/onboardedInvokers/"+invokerId).WithJsonBody(invalidInvoker).Go(t, requestHandler)
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")
230 // Update an invoker that has not been onboarded, should get 404 with problem details
232 invoker.ApiInvokerId = &missingId
233 result = testutil.NewRequest().Put("/onboardedInvokers/"+missingId).WithJsonBody(invoker).Go(t, requestHandler)
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")
243 func TestGetInvokerApiList(t *testing.T) {
244 aefProfiles1 := []publishserviceapi.AefProfile{
245 getAefProfile("aefId"),
248 apiList := []publishserviceapi.ServiceAPIDescription{
251 AefProfiles: &aefProfiles1,
254 aefProfiles2 := []publishserviceapi.AefProfile{
255 getAefProfile("aefId2"),
258 apiList = append(apiList, publishserviceapi.ServiceAPIDescription{
260 AefProfiles: &aefProfiles2,
262 publishRegisterMock := publishmocks.PublishRegister{}
263 publishRegisterMock.On("GetAllPublishedServices").Return(apiList)
264 invokerUnderTest, _, _ := getEcho(&publishRegisterMock)
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
277 wantedApiList := invokerUnderTest.GetInvokerApiList(invokerAId)
278 assert.NotNil(t, wantedApiList)
279 assert.Len(t, *wantedApiList, 2)
280 assert.Equal(t, apiId, *(*wantedApiList)[0].ApiId)
283 func getEcho(publishRegister publishservice.PublishRegister) (*InvokerManager, chan eventsapi.EventNotification, *echo.Echo) {
284 swagger, err := invokermanagementapi.GetSwagger()
286 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
290 swagger.Servers = nil
292 eventChannel := make(chan eventsapi.EventNotification)
293 im := NewInvokerManager(publishRegister, eventChannel)
296 e.Use(echomiddleware.Logger())
297 e.Use(middleware.OapiRequestValidator(swagger))
299 invokermanagementapi.RegisterHandlers(e, im)
300 return im, eventChannel, e
303 func getAefProfile(aefId string) publishserviceapi.AefProfile {
304 return publishserviceapi.AefProfile{
306 Versions: []publishserviceapi.Version{
308 Resources: &[]publishserviceapi.Resource{
310 CommType: "REQUEST_RESPONSE",
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",
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) {
335 return &event, false // completed normally
336 case <-time.After(timeout):
337 return nil, true // timed out