Merge "Minor bugs fix in ORU-app simulator" into e-release
[nonrtric.git] / test / usecases / oruclosedlooprecovery / goversion / main_test.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2021: 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 main
22
23 import (
24         "bytes"
25         "encoding/json"
26         "errors"
27         "fmt"
28         "io/ioutil"
29         "net/http"
30         "net/http/httptest"
31         "os"
32         "sync"
33         "syscall"
34         "testing"
35         "time"
36
37         log "github.com/sirupsen/logrus"
38         "github.com/stretchr/testify/mock"
39         "github.com/stretchr/testify/require"
40         "oransc.org/usecase/oruclosedloop/internal/config"
41         "oransc.org/usecase/oruclosedloop/internal/linkfailure"
42         "oransc.org/usecase/oruclosedloop/mocks"
43 )
44
45 func Test_init(t *testing.T) {
46         assertions := require.New(t)
47
48         os.Setenv("CONSUMER_HOST", "consumerHost")
49         os.Setenv("CONSUMER_PORT", "8095")
50         t.Cleanup(func() {
51                 os.Clearenv()
52         })
53
54         doInit()
55
56         wantedConfiguration := &config.Config{
57                 ConsumerHost:           "consumerHost",
58                 ConsumerPort:           8095,
59                 InfoCoordinatorAddress: "http://enrichmentservice:8083",
60                 SDNRAddress:            "http://localhost:3904",
61                 SDNRUser:               "admin",
62                 SDNPassword:            "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U",
63                 ORUToODUMapFile:        "o-ru-to-o-du-map.csv",
64                 ConsumerCertPath:       "security/consumer.crt",
65                 ConsumerKeyPath:        "security/consumer.key",
66                 LogLevel:               log.InfoLevel,
67         }
68         assertions.Equal(wantedConfiguration, configuration)
69
70         assertions.Equal(fmt.Sprint(wantedConfiguration.ConsumerPort), consumerPort)
71         assertions.Equal(wantedConfiguration.ConsumerHost+":"+fmt.Sprint(wantedConfiguration.ConsumerPort), jobRegistrationInfo.JobResultURI)
72
73         wantedLinkFailureConfig := linkfailure.Configuration{
74                 SDNRAddress:  wantedConfiguration.SDNRAddress,
75                 SDNRUser:     wantedConfiguration.SDNRUser,
76                 SDNRPassword: wantedConfiguration.SDNPassword,
77         }
78         assertions.Equal(wantedLinkFailureConfig, linkfailureConfig)
79 }
80
81 func Test_validateConfiguration(t *testing.T) {
82         assertions := require.New(t)
83
84         type args struct {
85                 configuration *config.Config
86         }
87         tests := []struct {
88                 name    string
89                 args    args
90                 wantErr error
91         }{
92                 {
93                         name: "Valid config, should return nil",
94                         args: args{
95                                 configuration: &config.Config{
96                                         ConsumerHost:     "host",
97                                         ConsumerPort:     80,
98                                         ConsumerCertPath: "security/consumer.crt",
99                                         ConsumerKeyPath:  "security/consumer.key",
100                                 },
101                         },
102                 },
103                 {
104                         name: "Invalid config, should return error",
105                         args: args{
106                                 configuration: &config.Config{},
107                         },
108                         wantErr: fmt.Errorf("consumer host and port must be provided"),
109                 },
110         }
111         for _, tt := range tests {
112                 t.Run(tt.name, func(t *testing.T) {
113                         err := validateConfiguration(tt.args.configuration)
114                         assertions.Equal(tt.wantErr, err)
115                 })
116         }
117 }
118
119 func Test_initializeLookupService(t *testing.T) {
120         assertions := require.New(t)
121         type args struct {
122                 csvFile         string
123                 oRuId           string
124                 mockReturn      [][]string
125                 mockReturnError error
126         }
127         tests := []struct {
128                 name        string
129                 args        args
130                 wantODuId   string
131                 wantInitErr error
132         }{
133                 {
134                         name: "Successful initialization, should return nil and lookup service should be initiated with data",
135                         args: args{
136                                 csvFile:    "file",
137                                 oRuId:      "1",
138                                 mockReturn: [][]string{{"1", "2"}},
139                         },
140                         wantODuId: "2",
141                 },
142                 {
143                         name: "Unsuccessful initialization, should return error and lookup service should not be initiated with data",
144                         args: args{
145                                 csvFile:         "file",
146                                 mockReturnError: errors.New("Error"),
147                         },
148                         wantInitErr: errors.New("Error"),
149                 },
150         }
151         for _, tt := range tests {
152                 t.Run(tt.name, func(t *testing.T) {
153                         mockCsvFileHelper := &mocks.CsvFileHelper{}
154                         mockCsvFileHelper.On("GetCsvFromFile", mock.Anything).Return(tt.args.mockReturn, tt.args.mockReturnError)
155
156                         err := initializeLookupService(mockCsvFileHelper, tt.args.csvFile)
157                         oDuId, _ := lookupService.GetODuID(tt.args.oRuId)
158                         assertions.Equal(tt.wantODuId, oDuId)
159                         assertions.Equal(tt.wantInitErr, err)
160                         mockCsvFileHelper.AssertCalled(t, "GetCsvFromFile", tt.args.csvFile)
161                 })
162         }
163 }
164
165 func Test_getRouter_shouldContainAllPathsWithHandlers(t *testing.T) {
166         assertions := require.New(t)
167
168         r := getRouter()
169         messageHandlerRoute := r.Get("messageHandler")
170         assertions.NotNil(messageHandlerRoute)
171         supportedMethods, err := messageHandlerRoute.GetMethods()
172         assertions.Equal([]string{http.MethodPost}, supportedMethods)
173         assertions.Nil(err)
174         path, _ := messageHandlerRoute.GetPathTemplate()
175         assertions.Equal("/", path)
176
177         startHandlerRoute := r.Get("start")
178         assertions.NotNil(messageHandlerRoute)
179         supportedMethods, err = startHandlerRoute.GetMethods()
180         assertions.Equal([]string{http.MethodPost}, supportedMethods)
181         assertions.Nil(err)
182         path, _ = startHandlerRoute.GetPathTemplate()
183         assertions.Equal("/admin/start", path)
184
185         stopHandlerRoute := r.Get("stop")
186         assertions.NotNil(stopHandlerRoute)
187         supportedMethods, err = stopHandlerRoute.GetMethods()
188         assertions.Equal([]string{http.MethodPost}, supportedMethods)
189         assertions.Nil(err)
190         path, _ = stopHandlerRoute.GetPathTemplate()
191         assertions.Equal("/admin/stop", path)
192
193         statusHandlerRoute := r.Get("status")
194         assertions.NotNil(statusHandlerRoute)
195         supportedMethods, err = statusHandlerRoute.GetMethods()
196         assertions.Equal([]string{http.MethodGet}, supportedMethods)
197         assertions.Nil(err)
198         path, _ = statusHandlerRoute.GetPathTemplate()
199         assertions.Equal("/status", path)
200 }
201
202 func Test_startHandler(t *testing.T) {
203         assertions := require.New(t)
204
205         jobRegistrationInfo.JobResultURI = "host:80"
206
207         type args struct {
208                 mockReturnBody   []byte
209                 mockReturnStatus int
210         }
211         tests := []struct {
212                 name         string
213                 args         args
214                 wantedStatus int
215                 wantedBody   string
216         }{
217                 {
218                         name: "Start with successful registration, should return ok",
219                         args: args{
220                                 mockReturnBody:   []byte(""),
221                                 mockReturnStatus: http.StatusOK,
222                         },
223                         wantedStatus: http.StatusOK,
224                 },
225                 {
226                         name: "Start with error response at registration, should return error",
227                         args: args{
228                                 mockReturnBody:   []byte("error"),
229                                 mockReturnStatus: http.StatusBadRequest,
230                         },
231                         wantedStatus: http.StatusBadRequest,
232                         wantedBody:   "Unable to register consumer job due to:",
233                 },
234         }
235         for _, tt := range tests {
236                 t.Run(tt.name, func(t *testing.T) {
237                         clientMock := setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
238
239                         handler := http.HandlerFunc(startHandler)
240                         responseRecorder := httptest.NewRecorder()
241                         r, _ := http.NewRequest(http.MethodPost, "/start", nil)
242
243                         handler.ServeHTTP(responseRecorder, r)
244
245                         assertions.Equal(tt.wantedStatus, responseRecorder.Code, tt.name)
246                         assertions.Contains(responseRecorder.Body.String(), tt.wantedBody, tt.name)
247
248                         var wantedJobRegistrationInfo = struct {
249                                 InfoTypeId    string      `json:"info_type_id"`
250                                 JobResultUri  string      `json:"job_result_uri"`
251                                 JobOwner      string      `json:"job_owner"`
252                                 JobDefinition interface{} `json:"job_definition"`
253                         }{
254                                 InfoTypeId:    "STD_Fault_Messages",
255                                 JobResultUri:  "host:80",
256                                 JobOwner:      "O-RU Closed Loop Usecase",
257                                 JobDefinition: "{}",
258                         }
259                         wantedBody, _ := json.Marshal(wantedJobRegistrationInfo)
260
261                         var actualRequest *http.Request
262                         clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool {
263                                 actualRequest = req
264                                 return true
265                         }))
266                         assertions.Equal(http.MethodPut, actualRequest.Method)
267                         assertions.Equal("http", actualRequest.URL.Scheme)
268                         assertions.Equal("enrichmentservice:8083", actualRequest.URL.Host)
269                         assertions.Equal("/data-consumer/v1/info-jobs/14e7bb84-a44d-44c1-90b7-6995a92ad43c", actualRequest.URL.Path)
270                         assertions.Equal("application/json; charset=utf-8", actualRequest.Header.Get("Content-Type"))
271                         body, _ := ioutil.ReadAll(actualRequest.Body)
272                         expectedBody := wantedBody
273                         assertions.Equal(expectedBody, body)
274                         clientMock.AssertNumberOfCalls(t, "Do", 1)
275
276                         // Check that the running status is "started"
277                         statusHandler := http.HandlerFunc(statusHandler)
278                         statusResponseRecorder := httptest.NewRecorder()
279                         statusRequest, _ := http.NewRequest(http.MethodGet, "/status", nil)
280
281                         statusHandler.ServeHTTP(statusResponseRecorder, statusRequest)
282
283                         assertions.Equal(http.StatusOK, statusResponseRecorder.Code)
284                         assertions.Equal(`{"status": "started"}`, statusResponseRecorder.Body.String())
285                 })
286         }
287 }
288
289 func Test_stopHandler(t *testing.T) {
290         assertions := require.New(t)
291
292         jobRegistrationInfo.JobResultURI = "host:80"
293
294         type args struct {
295                 mockReturnBody   []byte
296                 mockReturnStatus int
297         }
298         tests := []struct {
299                 name         string
300                 args         args
301                 wantedStatus int
302                 wantedBody   string
303         }{
304                 {
305                         name: "Stop with successful job deletion, should return ok",
306                         args: args{
307                                 mockReturnBody:   []byte(""),
308                                 mockReturnStatus: http.StatusOK,
309                         },
310                         wantedStatus: http.StatusOK,
311                 },
312                 {
313                         name: "Stop with error response at job deletion, should return error",
314                         args: args{
315                                 mockReturnBody:   []byte("error"),
316                                 mockReturnStatus: http.StatusBadRequest,
317                         },
318                         wantedStatus: http.StatusBadRequest,
319                         wantedBody:   "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually",
320                 },
321         }
322         for _, tt := range tests {
323                 t.Run(tt.name, func(t *testing.T) {
324                         clientMock := setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
325
326                         handler := http.HandlerFunc(stopHandler)
327                         responseRecorder := httptest.NewRecorder()
328                         r, _ := http.NewRequest(http.MethodPost, "/stop", nil)
329
330                         handler.ServeHTTP(responseRecorder, r)
331
332                         assertions.Equal(tt.wantedStatus, responseRecorder.Code, tt.name)
333                         assertions.Contains(responseRecorder.Body.String(), tt.wantedBody, tt.name)
334
335                         var actualRequest *http.Request
336                         clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool {
337                                 actualRequest = req
338                                 return true
339                         }))
340                         assertions.Equal(http.MethodDelete, actualRequest.Method)
341                         assertions.Equal("http", actualRequest.URL.Scheme)
342                         assertions.Equal("enrichmentservice:8083", actualRequest.URL.Host)
343                         assertions.Equal("/data-consumer/v1/info-jobs/14e7bb84-a44d-44c1-90b7-6995a92ad43c", actualRequest.URL.Path)
344                         clientMock.AssertNumberOfCalls(t, "Do", 1)
345
346                         // Check that the running status is "stopped"
347                         statusHandler := http.HandlerFunc(statusHandler)
348                         statusResponseRecorder := httptest.NewRecorder()
349                         statusRequest, _ := http.NewRequest(http.MethodGet, "/status", nil)
350
351                         statusHandler.ServeHTTP(statusResponseRecorder, statusRequest)
352
353                         assertions.Equal(http.StatusOK, statusResponseRecorder.Code)
354                         assertions.Equal(`{"status": "stopped"}`, statusResponseRecorder.Body.String())
355                 })
356         }
357 }
358
359 func Test_deleteOnShutdown(t *testing.T) {
360         assertions := require.New(t)
361
362         var buf bytes.Buffer
363         log.SetOutput(&buf)
364
365         t.Cleanup(func() {
366                 log.SetOutput(os.Stderr)
367         })
368
369         type args struct {
370                 mockReturnBody   []byte
371                 mockReturnStatus int
372         }
373         tests := []struct {
374                 name      string
375                 args      args
376                 wantedLog string
377         }{
378                 {
379                         name: "Delete with successful job deletion, should return ok",
380                         args: args{
381                                 mockReturnBody:   []byte(""),
382                                 mockReturnStatus: http.StatusOK,
383                         },
384                 },
385                 {
386                         name: "Stop with error response at job deletion, should return error",
387                         args: args{
388                                 mockReturnBody:   []byte("error"),
389                                 mockReturnStatus: http.StatusBadRequest,
390                         },
391                         wantedLog: "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually",
392                 },
393         }
394         for _, tt := range tests {
395                 t.Run(tt.name, func(t *testing.T) {
396                         setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
397
398                         c := make(chan os.Signal, 1)
399                         go deleteOnShutdown(c)
400                         c <- syscall.SIGTERM
401
402                         waitForLogToBeWritten(&buf)
403
404                         log := buf.String()
405                         if tt.wantedLog != "" {
406                                 assertions.Contains(log, "level=error")
407                                 assertions.Contains(log, "Unable to delete job on shutdown due to:")
408                                 assertions.Contains(log, tt.wantedLog)
409                         }
410                 })
411         }
412 }
413
414 func waitForLogToBeWritten(logBuf *bytes.Buffer) {
415         wg := sync.WaitGroup{}
416         wg.Add(1)
417         for {
418                 if waitTimeout(&wg, 10*time.Millisecond) && logBuf.Len() != 0 {
419                         wg.Done()
420                         break
421                 }
422         }
423 }
424
425 // waitTimeout waits for the waitgroup for the specified max timeout.
426 // Returns true if waiting timed out.
427 func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
428         c := make(chan struct{})
429         go func() {
430                 defer close(c)
431                 wg.Wait()
432         }()
433         select {
434         case <-c:
435                 return false // completed normally
436         case <-time.After(timeout):
437                 return true // timed out
438         }
439 }
440
441 func setUpClientMock(body []byte, status int) *mocks.HTTPClient {
442         clientMock := mocks.HTTPClient{}
443         clientMock.On("Do", mock.Anything).Return(&http.Response{
444                 Body:       ioutil.NopCloser(bytes.NewReader(body)),
445                 StatusCode: status,
446         }, nil)
447         client = &clientMock
448         return &clientMock
449 }