Updates for G Maintenance release
[nonrtric/plt/sme.git] / capifcore / internal / eventservice / eventservice_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 eventservice
22
23 import (
24         "bytes"
25         "encoding/json"
26         "fmt"
27         "io"
28         "net/http"
29         "os"
30         "path"
31         "sync"
32         "testing"
33         "time"
34
35         "github.com/deepmap/oapi-codegen/pkg/middleware"
36         "github.com/deepmap/oapi-codegen/pkg/testutil"
37         "github.com/labstack/echo/v4"
38         echomiddleware "github.com/labstack/echo/v4/middleware"
39         "github.com/stretchr/testify/assert"
40         "oransc.org/nonrtric/capifcore/internal/common29122"
41         "oransc.org/nonrtric/capifcore/internal/eventsapi"
42         "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
43         "oransc.org/nonrtric/capifcore/internal/restclient"
44 )
45
46 func TestRegisterSubscriptions(t *testing.T) {
47         subscription1 := eventsapi.EventSubscription{
48                 Events: []eventsapi.CAPIFEvent{
49                         eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
50                 },
51                 NotificationDestination: common29122.Uri("http://golang.cafe/"),
52         }
53         serviceUnderTest, requestHandler := getEcho(nil)
54         subscriberId := "subscriberId"
55
56         result := testutil.NewRequest().Post("/"+subscriberId+"/subscriptions").WithJsonBody(subscription1).Go(t, requestHandler)
57         assert.Equal(t, http.StatusCreated, result.Code())
58         var resultEvent eventsapi.EventSubscription
59         err := result.UnmarshalBodyToObject(&resultEvent)
60         assert.NoError(t, err, "error unmarshaling response")
61         assert.Equal(t, resultEvent, subscription1)
62         assert.Regexp(t, "http://example.com/"+subscriberId+"/subscriptions/"+subscriberId+"[0-9]+", result.Recorder.Header().Get(echo.HeaderLocation))
63         subscriptionId1 := path.Base(result.Recorder.Header().Get(echo.HeaderLocation))
64
65         subscription2 := subscription1
66         subscription2.Events = []eventsapi.CAPIFEvent{
67                 eventsapi.CAPIFEventAPIINVOKERUPDATED,
68         }
69         result = testutil.NewRequest().Post("/"+subscriberId+"/subscriptions").WithJsonBody(subscription2).Go(t, requestHandler)
70         assert.Regexp(t, "http://example.com/"+subscriberId+"/subscriptions/"+subscriberId+"[0-9]+", result.Recorder.Header().Get(echo.HeaderLocation))
71         subscriptionId2 := path.Base(result.Recorder.Header().Get(echo.HeaderLocation))
72
73         assert.NotEqual(t, subscriptionId1, subscriptionId2)
74         registeredSub1 := serviceUnderTest.getSubscription(subscriptionId1)
75         assert.Equal(t, subscription1, *registeredSub1)
76         registeredSub2 := serviceUnderTest.getSubscription(subscriptionId2)
77         assert.Equal(t, subscription2, *registeredSub2)
78 }
79
80 func TestRegisterInvalidSubscription(t *testing.T) {
81         subscription1 := eventsapi.EventSubscription{
82                 Events: []eventsapi.CAPIFEvent{eventsapi.CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE},
83         }
84         serviceUnderTest, requestHandler := getEcho(nil)
85         subscriberId := "subscriberId"
86
87         result := testutil.NewRequest().Post("/"+subscriberId+"/subscriptions").WithJsonBody(subscription1).Go(t, requestHandler)
88         assert.Equal(t, http.StatusBadRequest, result.Code())
89         var problemDetails common29122.ProblemDetails
90         err := result.UnmarshalBodyToObject(&problemDetails)
91         assert.NoError(t, err, "error unmarshaling response")
92         badRequest := http.StatusBadRequest
93         assert.Equal(t, &badRequest, problemDetails.Status)
94         assert.Contains(t, *problemDetails.Cause, "missing")
95         assert.Contains(t, *problemDetails.Cause, "notificationDestination")
96         subscriptionId := path.Base(result.Recorder.Header().Get(echo.HeaderLocation))
97         registeredSub := serviceUnderTest.getSubscription(subscriptionId)
98         assert.Nil(t, registeredSub)
99 }
100
101 func TestDeregisterSubscription(t *testing.T) {
102         subscription := eventsapi.EventSubscription{
103                 Events: []eventsapi.CAPIFEvent{
104                         eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
105                 },
106                 NotificationDestination: common29122.Uri(""),
107         }
108         serviceUnderTest, requestHandler := getEcho(nil)
109         subId := "sub1"
110         serviceUnderTest.addSubscription(subId, subscription)
111
112         result := testutil.NewRequest().Delete("/subscriberId/subscriptions/"+subId).Go(t, requestHandler)
113         assert.Equal(t, http.StatusNoContent, result.Code())
114         assert.Nil(t, serviceUnderTest.getSubscription(subId))
115 }
116
117 func TestSendEvent(t *testing.T) {
118         notificationUrl := "url"
119         apiIds := []string{"apiId"}
120         subId := "sub1"
121         newEvent := eventsapi.EventNotification{
122                 EventDetail: &eventsapi.CAPIFEventDetail{
123                         ApiIds: &apiIds,
124                 },
125                 Events: eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
126         }
127         wg := sync.WaitGroup{}
128         clientMock := NewTestClient(func(req *http.Request) *http.Response {
129                 if req.URL.String() == notificationUrl {
130                         assert.Equal(t, req.Method, "PUT")
131                         assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
132                         newEvent.SubscriptionId = subId
133                         assert.Equal(t, newEvent, getBodyAsEvent(req, t))
134                         wg.Done()
135                         return &http.Response{
136                                 StatusCode: 200,
137                                 Body:       io.NopCloser(bytes.NewBufferString(`OK`)),
138                                 Header:     make(http.Header), // Must be set to non-nil value or it panics
139                         }
140                 }
141                 t.Error("Wrong call to client: ", req)
142                 t.Fail()
143                 return nil
144         })
145         serviceUnderTest, _ := getEcho(clientMock)
146
147         serviceUnderTest.addSubscription(subId, eventsapi.EventSubscription{
148                 Events: []eventsapi.CAPIFEvent{
149                         eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
150                 },
151                 NotificationDestination: common29122.Uri(notificationUrl),
152         })
153         sub2 := eventsapi.EventSubscription{
154                 Events: []eventsapi.CAPIFEvent{
155                         eventsapi.CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE,
156                 },
157                 NotificationDestination: common29122.Uri(notificationUrl),
158         }
159         serviceUnderTest.addSubscription("other", sub2)
160
161         wg.Add(1)
162         go func() {
163                 serviceUnderTest.GetNotificationChannel() <- newEvent
164         }()
165
166         if waitTimeout(&wg, 1*time.Second) {
167                 t.Error("No event notification was sent")
168                 t.Fail()
169         }
170 }
171
172 func TestMatchEventType(t *testing.T) {
173         notificationUrl := "url"
174         subId := "sub1"
175         serviceUnderTest := NewEventService(nil)
176         serviceUnderTest.addSubscription(subId, eventsapi.EventSubscription{
177                 Events: []eventsapi.CAPIFEvent{
178                         eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
179                 },
180                 NotificationDestination: common29122.Uri(notificationUrl),
181                 EventFilters:            &[]eventsapi.CAPIFEventFilter{},
182         })
183         serviceUnderTest.addSubscription("other", eventsapi.EventSubscription{
184                 Events: []eventsapi.CAPIFEvent{
185                         eventsapi.CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE,
186                 },
187                 NotificationDestination: common29122.Uri(notificationUrl),
188         })
189
190         event := eventsapi.EventNotification{
191                 SubscriptionId: subId,
192                 Events:         eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
193         }
194
195         matchingSubs := serviceUnderTest.filterOnEventType(event)
196         assert.Len(t, matchingSubs, 1)
197         assert.Equal(t, subId, matchingSubs[0])
198 }
199
200 func TestMatchEventTypeAndFilters(t *testing.T) {
201         subId := "sub1"
202         apiIds := []string{"apiId"}
203         invokerIds := []string{"invokerId"}
204         aefId := "aefId"
205         aefIds := []string{aefId}
206         serviceUnderTest := NewEventService(nil)
207         serviceUnderTest.addSubscription(subId, eventsapi.EventSubscription{
208                 Events: []eventsapi.CAPIFEvent{
209                         eventsapi.CAPIFEventSERVICEAPIAVAILABLE,
210                 },
211                 EventFilters: &[]eventsapi.CAPIFEventFilter{
212                         {
213                                 ApiIds:        &apiIds,
214                                 ApiInvokerIds: &invokerIds,
215                                 AefIds:        &aefIds,
216                         },
217                 },
218         })
219         serviceUnderTest.addSubscription("otherSameType", eventsapi.EventSubscription{
220                 Events: []eventsapi.CAPIFEvent{
221                         eventsapi.CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE,
222                 },
223         })
224         serviceUnderTest.addSubscription("other", eventsapi.EventSubscription{
225                 Events: []eventsapi.CAPIFEvent{
226                         eventsapi.CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE,
227                 },
228         })
229
230         event := eventsapi.EventNotification{
231                 Events: eventsapi.CAPIFEventACCESSCONTROLPOLICYUNAVAILABLE,
232         }
233
234         // Only match type
235         matchingSubs := serviceUnderTest.getMatchingSubs(event)
236         assert.Len(t, matchingSubs, 2)
237
238         // Match with all filter ids
239         aefProfiles := []publishserviceapi.AefProfile{
240                 {
241                         AefId: aefId,
242                 },
243         }
244         serviceDescriptions := []publishserviceapi.ServiceAPIDescription{
245                 {
246                         AefProfiles: &aefProfiles,
247                 },
248         }
249         event.Events = eventsapi.CAPIFEventSERVICEAPIAVAILABLE
250         event.EventDetail = &eventsapi.CAPIFEventDetail{
251                 ApiIds:                 &apiIds,
252                 ApiInvokerIds:          &invokerIds,
253                 ServiceAPIDescriptions: &serviceDescriptions,
254         }
255         matchingSubs = serviceUnderTest.getMatchingSubs(event)
256         assert.Len(t, matchingSubs, 1)
257         assert.Equal(t, subId, matchingSubs[0])
258
259         // Un match apiId
260         otherApiIds := []string{"otherApiId"}
261         (*serviceUnderTest.subscriptions[subId].EventFilters)[0].ApiIds = &otherApiIds
262         matchingSubs = serviceUnderTest.getMatchingSubs(event)
263         assert.Len(t, matchingSubs, 0)
264
265         // Un match invokerId
266         otherInvokerIds := []string{"otherInvokerId"}
267         (*serviceUnderTest.subscriptions[subId].EventFilters)[0].ApiIds = nil
268         (*serviceUnderTest.subscriptions[subId].EventFilters)[0].ApiInvokerIds = &otherInvokerIds
269         matchingSubs = serviceUnderTest.getMatchingSubs(event)
270         assert.Len(t, matchingSubs, 0)
271
272         // Un match aefId
273         otherAefIds := []string{"otherAefId"}
274         (*serviceUnderTest.subscriptions[subId].EventFilters)[0].ApiInvokerIds = nil
275         (*serviceUnderTest.subscriptions[subId].EventFilters)[0].AefIds = &otherAefIds
276         matchingSubs = serviceUnderTest.getMatchingSubs(event)
277         assert.Len(t, matchingSubs, 0)
278
279         // Match with empty subscription filter id list
280         (*serviceUnderTest.subscriptions[subId].EventFilters)[0].AefIds = &[]string{}
281         matchingSubs = serviceUnderTest.getMatchingSubs(event)
282         assert.Len(t, matchingSubs, 1)
283
284         // Match with empty event id list
285         event.EventDetail.ApiIds = nil
286         event.EventDetail.ApiInvokerIds = nil
287         event.EventDetail.ServiceAPIDescriptions = &[]publishserviceapi.ServiceAPIDescription{}
288         matchingSubs = serviceUnderTest.getMatchingSubs(event)
289         assert.Len(t, matchingSubs, 1)
290 }
291
292 func getEcho(client restclient.HTTPClient) (*EventService, *echo.Echo) {
293         swagger, err := eventsapi.GetSwagger()
294         if err != nil {
295                 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
296                 os.Exit(1)
297         }
298
299         swagger.Servers = nil
300
301         es := NewEventService(client)
302
303         e := echo.New()
304         e.Use(echomiddleware.Logger())
305         e.Use(middleware.OapiRequestValidator(swagger))
306
307         eventsapi.RegisterHandlers(e, es)
308         return es, e
309 }
310
311 type RoundTripFunc func(req *http.Request) *http.Response
312
313 func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
314         return f(req), nil
315 }
316
317 // NewTestClient returns *http.Client with Transport replaced to avoid making real calls
318 func NewTestClient(fn RoundTripFunc) *http.Client {
319         return &http.Client{
320                 Transport: RoundTripFunc(fn),
321         }
322 }
323
324 // waitTimeout waits for the waitgroup for the specified max timeout.
325 // Returns true if waiting timed out.
326 func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
327         c := make(chan struct{})
328         go func() {
329                 defer close(c)
330                 wg.Wait()
331         }()
332         select {
333         case <-c:
334                 return false // completed normally
335         case <-time.After(timeout):
336                 return true // timed out
337         }
338 }
339
340 func getBodyAsEvent(req *http.Request, t *testing.T) eventsapi.EventNotification {
341         buf := new(bytes.Buffer)
342         if _, err := buf.ReadFrom(req.Body); err != nil {
343                 t.Fail()
344         }
345         var event eventsapi.EventNotification
346         err := json.Unmarshal(buf.Bytes(), &event)
347         if err != nil {
348                 t.Fail()
349         }
350         return event
351 }