e9ab68af5b1de0ad31b024fad4395945be85edd5
[ric-plt/appmgr.git] / pkg / resthooks / resthooks_test.go
1 /*
2 ==================================================================================
3   Copyright (c) 2019 AT&T Intellectual Property.
4   Copyright (c) 2019 Nokia
5
6    Licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17 ==================================================================================
18 */
19
20 package resthooks
21
22 import (
23         "encoding/json"
24         "errors"
25         "fmt"
26         "github.com/stretchr/testify/assert"
27         "github.com/stretchr/testify/mock"
28         "net"
29         "net/http"
30         "net/http/httptest"
31         "os"
32         "time"
33
34         "strconv"
35         "testing"
36
37         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
38         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/models"
39 )
40
41 var rh *Resthook
42 var resp models.SubscriptionResponse
43 var mockedSdl *SdlMock
44 var mockedSdl2 *SdlMock
45
46 // Test cases
47 func TestMain(m *testing.M) {
48         appmgr.Init()
49         appmgr.Logger.SetLevel(0)
50
51         mockedSdl = new(SdlMock)
52         mockedSdl2 = new(SdlMock)
53         rh = createResthook(false, mockedSdl,mockedSdl2)
54         code := m.Run()
55         os.Exit(code)
56 }
57
58 func TestAddSubscriptionSuccess(t *testing.T) {
59         var mockSdlRetOk error
60         subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
61
62         mockedSdl.expectDbSet(t, subsReq, mockSdlRetOk)
63         resp := rh.AddSubscription(subsReq)
64         assert.Equal(t, resp.Version, int64(0))
65         assert.Equal(t, resp.EventType, models.EventTypeCreated)
66 }
67
68 func TestAddSubscriptionExists(t *testing.T) {
69         resp := rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
70         assert.Equal(t, resp.Version, int64(0))
71         assert.Equal(t, resp.EventType, models.EventTypeCreated)
72 }
73
74 func TestDeletesubscriptionSuccess(t *testing.T) {
75         var mockSdlRetOk error
76
77         mockedSdl.On("Set", mock.Anything).Return(mockSdlRetOk)
78         resp := rh.AddSubscription(createSubscription(models.EventTypeDeleted, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
79         assert.Equal(t, resp.Version, int64(0))
80         assert.Equal(t, resp.EventType, models.EventTypeDeleted)
81
82         resp, ok := rh.DeleteSubscription(resp.ID)
83         assert.Equal(t, ok, true)
84         assert.Equal(t, resp.Version, int64(0))
85         assert.Equal(t, resp.EventType, models.EventTypeDeleted)
86 }
87
88 func TestDeletesubscriptionInvalid(t *testing.T) {
89         resp, ok := rh.DeleteSubscription("Non-existent-ID")
90         assert.Equal(t, ok, false)
91         assert.Equal(t, resp.Version, int64(0))
92         assert.Equal(t, resp.EventType, models.EventType(""))
93 }
94
95 func TestModifySubscriptionSuccess(t *testing.T) {
96         resp := rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
97         assert.Equal(t, resp.Version, int64(0))
98         assert.Equal(t, resp.EventType, models.EventTypeCreated)
99
100         resp, ok := rh.ModifySubscription(resp.ID, createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
101         assert.Equal(t, ok, true)
102         assert.Equal(t, resp.Version, int64(0))
103         assert.Equal(t, resp.EventType, models.EventTypeModified)
104 }
105
106 func TestModifySubscriptionForNonExistingSubscription(t *testing.T) {
107         resp, ok := rh.ModifySubscription("Non-existent-ID", createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
108         assert.Equal(t, ok, false)
109         assert.Equal(t, *resp, models.SubscriptionResponse{})
110 }
111
112 func TestDeleteSubscriptionForNonExistingSubscription(t *testing.T) {
113         resp, ok := rh.DeleteSubscription("Non-existent-ID")
114         assert.Equal(t, ok, false)
115         assert.Equal(t, resp.Version, int64(0))
116         assert.Equal(t, resp.EventType, models.EventType(""))
117 }
118
119 func TestGetAllSubscriptionSuccess(t *testing.T) {
120         flushExistingSubscriptions()
121         subscriptions := rh.GetAllSubscriptions()
122         assert.Equal(t, len(subscriptions), 0)
123
124         rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
125         rh.AddSubscription(createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
126
127         subscriptions = rh.GetAllSubscriptions()
128         assert.Equal(t, len(subscriptions), 2)
129 }
130
131 func TestGetSubscriptionByIdSuccess(t *testing.T) {
132         flushExistingSubscriptions()
133
134         sub1 := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
135         sub2 := createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2")
136         r1 := rh.AddSubscription(sub1)
137         r2 := rh.AddSubscription(sub2)
138
139         resp1, ok := rh.GetSubscriptionById(r1.ID)
140         assert.Equal(t, ok, true)
141         assert.Equal(t, resp1.Data, sub1.Data)
142
143         resp2, ok := rh.GetSubscriptionById(r2.ID)
144         assert.Equal(t, ok, true)
145         assert.Equal(t, resp2.Data, sub2.Data)
146 }
147
148 func TestGetSubscriptionByIdForNonExistingSubscription(t *testing.T) {
149         resp, ok := rh.GetSubscriptionById("Non-existent-ID")
150         assert.Equal(t, ok, false)
151         assert.Equal(t, resp, models.Subscription{})
152 }
153
154 func TestNotifyClientsNoXapp(t *testing.T) {
155         rh.NotifyClients(models.AllDeployedXapps{}, models.EventTypeUndeployed)
156 }
157
158 func TestNotifySuccess(t *testing.T) {
159         flushExistingSubscriptions()
160
161         sub := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
162         resp := rh.AddSubscription(sub)
163
164         xapp := getDummyXapp()
165         ts := createHTTPServer(t, "POST", "/xapps_hook", 8087, http.StatusOK, nil)
166         defer ts.Close()
167
168         v, ok := rh.subscriptions.Get(resp.ID)
169         assert.True(t, ok)
170         err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
171         assert.Nil(t, err)
172 }
173
174 func TestNotifySuccessIfHttpErrorResponse(t *testing.T) {
175         flushExistingSubscriptions()
176
177         sub := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
178         resp := rh.AddSubscription(sub)
179
180         xapp := getDummyXapp()
181         ts := createHTTPServer(t, "POST", "/xapps_hook", 8087, http.StatusInternalServerError, nil)
182         defer ts.Close()
183
184         v, ok := rh.subscriptions.Get(resp.ID)
185         assert.True(t, ok)
186         err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
187         assert.Nil(t, err)
188 }
189
190 func TestNotifyReturnsErrorAfterRetriesIfNoHttpServer(t *testing.T) {
191         flushExistingSubscriptions()
192
193         sub := createSubscription(models.EventTypeCreated, int64(2), int64(1), "http://localhost:8087/xapps_hook")
194         resp := rh.AddSubscription(sub)
195
196         xapp := getDummyXapp()
197
198         v, ok := rh.subscriptions.Get(resp.ID)
199         assert.True(t, ok)
200         err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
201         assert.NotNil(t, err)
202         assert.Equal(t, 0, len(rh.subscriptions.Items()))
203 }
204
205 func TestRestoreSubscriptionsSuccess(t *testing.T) {
206         var mockSdlRetOk error
207         mSdl := new(SdlMock)
208         mSdl2 := new(SdlMock)
209         key := "key-1"
210
211         subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
212         serializedSubsReq, err := json.Marshal(subsReq)
213         assert.Nil(t, err)
214
215         mockSdlGetRetVal := make(map[string]interface{})
216         //Cast data to string to act like a real SDL/Redis client
217         mockSdlGetRetVal[key] = string(serializedSubsReq)
218         mSdl.On("GetAll").Return([]string{key}, mockSdlRetOk).Twice()
219         mSdl.On("Get", []string{key}).Return(mockSdlGetRetVal, mockSdlRetOk).Once()
220         restHook := createResthook(true, mSdl,mSdl2)
221
222         val, found := restHook.subscriptions.Get(key)
223         assert.True(t, found)
224         assert.Equal(t, subsReq, val.(SubscriptionInfo).req)
225 }
226
227 func TestRestoreSubscriptionsFailsIfSdlGetAllFails(t *testing.T) {
228         var mockSdlRetStatus error
229         mSdl := new(SdlMock)
230         mSdl2 := new(SdlMock)
231         getCalled := 0
232         mGetAllCall := mSdl.On("GetAll")
233         mGetAllCall.RunFn = func(args mock.Arguments) {
234                 if getCalled > 0 {
235                         mockSdlRetStatus = errors.New("some SDL error")
236                 }
237                 getCalled++
238                 mGetAllCall.ReturnArguments = mock.Arguments{[]string{}, mockSdlRetStatus}
239         }
240
241         restHook := createResthook(true, mSdl,mSdl2)
242         assert.Equal(t, 0, len(restHook.subscriptions.Items()))
243 }
244
245 func TestRestoreSubscriptionsFailsIfSdlGetFails(t *testing.T) {
246         var mockSdlRetOk error
247         mSdl := new(SdlMock)
248         mSdl2 := new(SdlMock)
249         mockSdlRetNok := errors.New("some SDL error")
250         key := "key-1"
251         subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
252         serializedSubsReq, err := json.Marshal(subsReq)
253         assert.Nil(t, err)
254
255         mockSdlGetRetVal := make(map[string]interface{})
256         mockSdlGetRetVal[key] = serializedSubsReq
257
258         mSdl.On("GetAll").Return([]string{key}, mockSdlRetOk).Twice()
259         mSdl.On("Get", []string{key}).Return(mockSdlGetRetVal, mockSdlRetNok).Once()
260
261         restHook := createResthook(true, mSdl,mSdl2)
262         assert.Equal(t, 0, len(restHook.subscriptions.Items()))
263 }
264
265 func TestTeardown(t *testing.T) {
266         var mockSdlRetOk error
267         mockedSdl.On("RemoveAll").Return(mockSdlRetOk).Once()
268
269         rh.FlushSubscriptions()
270 }
271
272 func createSubscription(et models.EventType, maxRetries, retryTimer int64, targetUrl string) models.SubscriptionRequest {
273         return models.SubscriptionRequest{&models.SubscriptionData{et, &maxRetries, &retryTimer, &targetUrl}}
274 }
275
276 func getDummyXapp() models.Xapp {
277         return generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "service-ricxapp-dummy-xapp-rmr.ricxapp", "4560")
278 }
279
280 func generateXapp(name, status, ver, iname, istatus, ip, port string) (x models.Xapp) {
281         x.Name = &name
282         x.Status = status
283         x.Version = ver
284         p, _ := strconv.Atoi(port)
285         var msgs appmgr.RtmData
286
287         instance := &models.XappInstance{
288                 Name:       &iname,
289                 Status:     istatus,
290                 IP:         ip,
291                 Port:       int64(p),
292                 TxMessages: msgs.TxMessages,
293                 RxMessages: msgs.RxMessages,
294         }
295         x.Instances = append(x.Instances, instance)
296         return
297 }
298
299 func flushExistingSubscriptions() {
300         var mockSdlRetOk error
301         mockedSdl.On("RemoveAll").Return(mockSdlRetOk).Once()
302         rh.FlushSubscriptions()
303 }
304
305 func createHTTPServer(t *testing.T, method, url string, port, status int, respData interface{}) *httptest.Server {
306         l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
307         if err != nil {
308                 t.Error("Failed to create listener: " + err.Error())
309         }
310         ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
311                 assert.Equal(t, r.Method, method)
312                 assert.Equal(t, r.URL.String(), url)
313                 w.Header().Add("Content-Type", "application/json")
314                 w.WriteHeader(status)
315                 b, _ := json.Marshal(respData)
316                 w.Write(b)
317         }))
318         ts.Listener.Close()
319         ts.Listener = l
320
321         ts.Start()
322         time.Sleep(time.Duration(1 * time.Second))
323         return ts
324 }
325
326 func (m *SdlMock) expectDbSet(t *testing.T, subsReq models.SubscriptionRequest, mockRet error) {
327         serializedSubReq, _ := json.Marshal(subsReq)
328         m.On("Set", mock.Anything).Run(
329                 func(args mock.Arguments) {
330                         sdlKVs := args.Get(0).([]interface{})
331                         assert.Equal(t, 2, len(sdlKVs))
332                         //Validate that subscription request is set to SDL
333                         assert.Equal(t, serializedSubReq, sdlKVs[1])
334                 }).Return(mockRet).Once()
335 }
336
337 type SdlMock struct {
338         mock.Mock
339 }
340
341 func (m *SdlMock) Set(pairs ...interface{}) error {
342         a := m.Called(pairs)
343         return a.Error(0)
344 }
345
346 func (m *SdlMock) Get(keys []string) (map[string]interface{}, error) {
347         a := m.Called(keys)
348         return a.Get(0).(map[string]interface{}), a.Error(1)
349 }
350
351 func (m *SdlMock) GetAll() ([]string, error) {
352         a := m.Called()
353         return a.Get(0).([]string), a.Error(1)
354 }
355
356 func (m *SdlMock) RemoveAll() error {
357         a := m.Called()
358         return a.Error(0)
359 }
360
361 func (m *SdlMock) Remove(keys []string) error {
362         a := m.Called()
363         return a.Error(0)
364 }