Improve appmgr UT coverage
[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
45 // Test cases
46 func TestMain(m *testing.M) {
47         appmgr.Init()
48         appmgr.Logger.SetLevel(0)
49
50         mockedSdl = new(SdlMock)
51         rh = createResthook(false, mockedSdl)
52         code := m.Run()
53         os.Exit(code)
54 }
55
56 func TestAddSubscriptionSuccess(t *testing.T) {
57         var mockSdlRetOk error
58         subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
59
60         mockedSdl.expectDbSet(t, subsReq, mockSdlRetOk)
61         resp := rh.AddSubscription(subsReq)
62         assert.Equal(t, resp.Version, int64(0))
63         assert.Equal(t, resp.EventType, models.EventTypeCreated)
64 }
65
66 func TestAddSubscriptionExists(t *testing.T) {
67         resp := rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
68         assert.Equal(t, resp.Version, int64(0))
69         assert.Equal(t, resp.EventType, models.EventTypeCreated)
70 }
71
72 func TestDeletesubscriptionSuccess(t *testing.T) {
73         var mockSdlRetOk error
74
75         mockedSdl.On("Set", mock.Anything).Return(mockSdlRetOk)
76         resp := rh.AddSubscription(createSubscription(models.EventTypeDeleted, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
77         assert.Equal(t, resp.Version, int64(0))
78         assert.Equal(t, resp.EventType, models.EventTypeDeleted)
79
80         resp, ok := rh.DeleteSubscription(resp.ID)
81         assert.Equal(t, ok, true)
82         assert.Equal(t, resp.Version, int64(0))
83         assert.Equal(t, resp.EventType, models.EventTypeDeleted)
84 }
85
86 func TestDeletesubscriptionInvalid(t *testing.T) {
87         resp, ok := rh.DeleteSubscription("Non-existent-ID")
88         assert.Equal(t, ok, false)
89         assert.Equal(t, resp.Version, int64(0))
90         assert.Equal(t, resp.EventType, models.EventType(""))
91 }
92
93 func TestModifySubscriptionSuccess(t *testing.T) {
94         resp := rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
95         assert.Equal(t, resp.Version, int64(0))
96         assert.Equal(t, resp.EventType, models.EventTypeCreated)
97
98         resp, ok := rh.ModifySubscription(resp.ID, createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
99         assert.Equal(t, ok, true)
100         assert.Equal(t, resp.Version, int64(0))
101         assert.Equal(t, resp.EventType, models.EventTypeModified)
102 }
103
104 func TestModifySubscriptionForNonExistingSubscription(t *testing.T) {
105         resp, ok := rh.ModifySubscription("Non-existent-ID", createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
106         assert.Equal(t, ok, false)
107         assert.Equal(t, *resp, models.SubscriptionResponse{})
108 }
109
110 func TestDeleteSubscriptionForNonExistingSubscription(t *testing.T) {
111         resp, ok := rh.DeleteSubscription("Non-existent-ID")
112         assert.Equal(t, ok, false)
113         assert.Equal(t, resp.Version, int64(0))
114         assert.Equal(t, resp.EventType, models.EventType(""))
115 }
116
117 func TestGetAllSubscriptionSuccess(t *testing.T) {
118         flushExistingSubscriptions()
119         subscriptions := rh.GetAllSubscriptions()
120         assert.Equal(t, len(subscriptions), 0)
121
122         rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
123         rh.AddSubscription(createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
124
125         subscriptions = rh.GetAllSubscriptions()
126         assert.Equal(t, len(subscriptions), 2)
127 }
128
129 func TestGetSubscriptionByIdSuccess(t *testing.T) {
130         flushExistingSubscriptions()
131
132         sub1 := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
133         sub2 := createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2")
134         r1 := rh.AddSubscription(sub1)
135         r2 := rh.AddSubscription(sub2)
136
137         resp1, ok := rh.GetSubscriptionById(r1.ID)
138         assert.Equal(t, ok, true)
139         assert.Equal(t, resp1.Data, sub1.Data)
140
141         resp2, ok := rh.GetSubscriptionById(r2.ID)
142         assert.Equal(t, ok, true)
143         assert.Equal(t, resp2.Data, sub2.Data)
144 }
145
146 func TestGetSubscriptionByIdForNonExistingSubscription(t *testing.T) {
147         resp, ok := rh.GetSubscriptionById("Non-existent-ID")
148         assert.Equal(t, ok, false)
149         assert.Equal(t, resp, models.Subscription{})
150 }
151
152 func TestNotifyClientsNoXapp(t *testing.T) {
153         rh.NotifyClients(models.AllDeployedXapps{}, models.EventTypeUndeployed)
154 }
155
156 func TestNotifySuccess(t *testing.T) {
157         flushExistingSubscriptions()
158
159         sub := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
160         resp := rh.AddSubscription(sub)
161
162         xapp := getDummyXapp()
163         ts := createHTTPServer(t, "POST", "/xapps_hook", 8087, http.StatusOK, nil)
164         defer ts.Close()
165
166         v, ok := rh.subscriptions.Get(resp.ID)
167         assert.True(t, ok)
168         err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
169         assert.Nil(t, err)
170 }
171
172 func TestNotifySuccessIfHttpErrorResponse(t *testing.T) {
173         flushExistingSubscriptions()
174
175         sub := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
176         resp := rh.AddSubscription(sub)
177
178         xapp := getDummyXapp()
179         ts := createHTTPServer(t, "POST", "/xapps_hook", 8087, http.StatusInternalServerError, nil)
180         defer ts.Close()
181
182         v, ok := rh.subscriptions.Get(resp.ID)
183         assert.True(t, ok)
184         err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
185         assert.Nil(t, err)
186 }
187
188 func TestNotifyReturnsErrorAfterRetriesIfNoHttpServer(t *testing.T) {
189         flushExistingSubscriptions()
190
191         sub := createSubscription(models.EventTypeCreated, int64(2), int64(1), "http://localhost:8087/xapps_hook")
192         resp := rh.AddSubscription(sub)
193
194         xapp := getDummyXapp()
195
196         v, ok := rh.subscriptions.Get(resp.ID)
197         assert.True(t, ok)
198         err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
199         assert.NotNil(t, err)
200         assert.Equal(t, 0, len(rh.subscriptions.Items()))
201 }
202
203 func TestRestoreSubscriptionsSuccess(t *testing.T) {
204         var mockSdlRetOk error
205         mSdl := new(SdlMock)
206         key := "key-1"
207
208         subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
209         serializedSubsReq, err := json.Marshal(subsReq)
210         assert.Nil(t, err)
211
212         mockSdlGetRetVal := make(map[string]interface{})
213         //Cast data to string to act like a real SDL/Redis client
214         mockSdlGetRetVal[key] = string(serializedSubsReq)
215         mSdl.On("GetAll").Return([]string{key}, mockSdlRetOk).Twice()
216         mSdl.On("Get", []string{key}).Return(mockSdlGetRetVal, mockSdlRetOk).Once()
217         restHook := createResthook(true, mSdl)
218
219         val, found := restHook.subscriptions.Get(key)
220         assert.True(t, found)
221         assert.Equal(t, subsReq, val.(SubscriptionInfo).req)
222 }
223
224 func TestRestoreSubscriptionsFailsIfSdlGetAllFails(t *testing.T) {
225         var mockSdlRetStatus error
226         mSdl := new(SdlMock)
227         getCalled := 0
228         mGetAllCall := mSdl.On("GetAll")
229         mGetAllCall.RunFn = func(args mock.Arguments) {
230                 if getCalled > 0 {
231                         mockSdlRetStatus = errors.New("some SDL error")
232                 }
233                 getCalled++
234                 mGetAllCall.ReturnArguments = mock.Arguments{[]string{}, mockSdlRetStatus}
235         }
236
237         restHook := createResthook(true, mSdl)
238         assert.Equal(t, 0, len(restHook.subscriptions.Items()))
239 }
240
241 func TestRestoreSubscriptionsFailsIfSdlGetFails(t *testing.T) {
242         var mockSdlRetOk error
243         mSdl := new(SdlMock)
244         mockSdlRetNok := errors.New("some SDL error")
245         key := "key-1"
246         subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
247         serializedSubsReq, err := json.Marshal(subsReq)
248         assert.Nil(t, err)
249
250         mockSdlGetRetVal := make(map[string]interface{})
251         mockSdlGetRetVal[key] = serializedSubsReq
252
253         mSdl.On("GetAll").Return([]string{key}, mockSdlRetOk).Twice()
254         mSdl.On("Get", []string{key}).Return(mockSdlGetRetVal, mockSdlRetNok).Once()
255
256         restHook := createResthook(true, mSdl)
257         assert.Equal(t, 0, len(restHook.subscriptions.Items()))
258 }
259
260 func TestTeardown(t *testing.T) {
261         var mockSdlRetOk error
262         mockedSdl.On("RemoveAll").Return(mockSdlRetOk).Once()
263
264         rh.FlushSubscriptions()
265 }
266
267 func createSubscription(et models.EventType, maxRetries, retryTimer int64, targetUrl string) models.SubscriptionRequest {
268         return models.SubscriptionRequest{&models.SubscriptionData{et, &maxRetries, &retryTimer, &targetUrl}}
269 }
270
271 func getDummyXapp() models.Xapp {
272         return generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "service-ricxapp-dummy-xapp-rmr.ricxapp", "4560")
273 }
274
275 func generateXapp(name, status, ver, iname, istatus, ip, port string) (x models.Xapp) {
276         x.Name = &name
277         x.Status = status
278         x.Version = ver
279         p, _ := strconv.Atoi(port)
280         var msgs appmgr.RtmData
281
282         instance := &models.XappInstance{
283                 Name:       &iname,
284                 Status:     istatus,
285                 IP:         ip,
286                 Port:       int64(p),
287                 TxMessages: msgs.TxMessages,
288                 RxMessages: msgs.RxMessages,
289         }
290         x.Instances = append(x.Instances, instance)
291         return
292 }
293
294 func flushExistingSubscriptions() {
295         var mockSdlRetOk error
296         mockedSdl.On("RemoveAll").Return(mockSdlRetOk).Once()
297         rh.FlushSubscriptions()
298 }
299
300 func createHTTPServer(t *testing.T, method, url string, port, status int, respData interface{}) *httptest.Server {
301         l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
302         if err != nil {
303                 t.Error("Failed to create listener: " + err.Error())
304         }
305         ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
306                 assert.Equal(t, r.Method, method)
307                 assert.Equal(t, r.URL.String(), url)
308                 w.Header().Add("Content-Type", "application/json")
309                 w.WriteHeader(status)
310                 b, _ := json.Marshal(respData)
311                 w.Write(b)
312         }))
313         ts.Listener.Close()
314         ts.Listener = l
315
316         ts.Start()
317         time.Sleep(time.Duration(1 * time.Second))
318         return ts
319 }
320
321 func (m *SdlMock) expectDbSet(t *testing.T, subsReq models.SubscriptionRequest, mockRet error) {
322         serializedSubReq, _ := json.Marshal(subsReq)
323         m.On("Set", mock.Anything).Run(
324                 func(args mock.Arguments) {
325                         sdlKVs := args.Get(0).([]interface{})
326                         assert.Equal(t, 2, len(sdlKVs))
327                         //Validate that subscription request is set to SDL
328                         assert.Equal(t, serializedSubReq, sdlKVs[1])
329                 }).Return(mockRet).Once()
330 }
331
332 type SdlMock struct {
333         mock.Mock
334 }
335
336 func (m *SdlMock) Set(pairs ...interface{}) error {
337         a := m.Called(pairs)
338         return a.Error(0)
339 }
340
341 func (m *SdlMock) Get(keys []string) (map[string]interface{}, error) {
342         a := m.Called(keys)
343         return a.Get(0).(map[string]interface{}), a.Error(1)
344 }
345
346 func (m *SdlMock) GetAll() ([]string, error) {
347         a := m.Called()
348         return a.Get(0).([]string), a.Error(1)
349 }
350
351 func (m *SdlMock) RemoveAll() error {
352         a := m.Called()
353         return a.Error(0)
354 }