6b1b0e51f1160cc97b8681dd1bb739a36d4e52f1
[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
194 func Test_startHandler(t *testing.T) {
195         assertions := require.New(t)
196
197         jobRegistrationInfo.JobResultURI = "host:80"
198
199         type args struct {
200                 mockReturnBody   []byte
201                 mockReturnStatus int
202         }
203         tests := []struct {
204                 name         string
205                 args         args
206                 wantedStatus int
207                 wantedBody   string
208         }{
209                 {
210                         name: "Start with successful registration, should return ok",
211                         args: args{
212                                 mockReturnBody:   []byte(""),
213                                 mockReturnStatus: http.StatusOK,
214                         },
215                         wantedStatus: http.StatusOK,
216                 },
217                 {
218                         name: "Start with error response at registration, should return error",
219                         args: args{
220                                 mockReturnBody:   []byte("error"),
221                                 mockReturnStatus: http.StatusBadRequest,
222                         },
223                         wantedStatus: http.StatusBadRequest,
224                         wantedBody:   "Unable to register consumer job due to:",
225                 },
226         }
227         for _, tt := range tests {
228                 t.Run(tt.name, func(t *testing.T) {
229                         clientMock := setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
230
231                         handler := http.HandlerFunc(startHandler)
232                         responseRecorder := httptest.NewRecorder()
233                         r, _ := http.NewRequest(http.MethodPost, "/start", nil)
234
235                         handler.ServeHTTP(responseRecorder, r)
236
237                         assertions.Equal(tt.wantedStatus, responseRecorder.Code, tt.name)
238                         assertions.Contains(responseRecorder.Body.String(), tt.wantedBody, tt.name)
239
240                         var wantedJobRegistrationInfo = struct {
241                                 InfoTypeId    string      `json:"info_type_id"`
242                                 JobResultUri  string      `json:"job_result_uri"`
243                                 JobOwner      string      `json:"job_owner"`
244                                 JobDefinition interface{} `json:"job_definition"`
245                         }{
246                                 InfoTypeId:    "STD_Fault_Messages",
247                                 JobResultUri:  "host:80",
248                                 JobOwner:      "O-RU Closed Loop Usecase",
249                                 JobDefinition: "{}",
250                         }
251                         wantedBody, _ := json.Marshal(wantedJobRegistrationInfo)
252
253                         var actualRequest *http.Request
254                         clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool {
255                                 actualRequest = req
256                                 return true
257                         }))
258                         assertions.Equal(http.MethodPut, actualRequest.Method)
259                         assertions.Equal("http", actualRequest.URL.Scheme)
260                         assertions.Equal("enrichmentservice:8083", actualRequest.URL.Host)
261                         assertions.Equal("/data-consumer/v1/info-jobs/14e7bb84-a44d-44c1-90b7-6995a92ad43c", actualRequest.URL.Path)
262                         assertions.Equal("application/json; charset=utf-8", actualRequest.Header.Get("Content-Type"))
263                         body, _ := ioutil.ReadAll(actualRequest.Body)
264                         expectedBody := wantedBody
265                         assertions.Equal(expectedBody, body)
266                         clientMock.AssertNumberOfCalls(t, "Do", 1)
267                 })
268         }
269 }
270
271 func Test_stopHandler(t *testing.T) {
272         assertions := require.New(t)
273
274         jobRegistrationInfo.JobResultURI = "host:80"
275
276         type args struct {
277                 mockReturnBody   []byte
278                 mockReturnStatus int
279         }
280         tests := []struct {
281                 name         string
282                 args         args
283                 wantedStatus int
284                 wantedBody   string
285         }{
286                 {
287                         name: "Stop with successful job deletion, should return ok",
288                         args: args{
289                                 mockReturnBody:   []byte(""),
290                                 mockReturnStatus: http.StatusOK,
291                         },
292                         wantedStatus: http.StatusOK,
293                 },
294                 {
295                         name: "Stop with error response at job deletion, should return error",
296                         args: args{
297                                 mockReturnBody:   []byte("error"),
298                                 mockReturnStatus: http.StatusBadRequest,
299                         },
300                         wantedStatus: http.StatusBadRequest,
301                         wantedBody:   "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually",
302                 },
303         }
304         for _, tt := range tests {
305                 t.Run(tt.name, func(t *testing.T) {
306                         clientMock := setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
307
308                         handler := http.HandlerFunc(stopHandler)
309                         responseRecorder := httptest.NewRecorder()
310                         r, _ := http.NewRequest(http.MethodPost, "/stop", nil)
311
312                         handler.ServeHTTP(responseRecorder, r)
313
314                         assertions.Equal(tt.wantedStatus, responseRecorder.Code, tt.name)
315                         assertions.Contains(responseRecorder.Body.String(), tt.wantedBody, tt.name)
316
317                         var actualRequest *http.Request
318                         clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool {
319                                 actualRequest = req
320                                 return true
321                         }))
322                         assertions.Equal(http.MethodDelete, actualRequest.Method)
323                         assertions.Equal("http", actualRequest.URL.Scheme)
324                         assertions.Equal("enrichmentservice:8083", actualRequest.URL.Host)
325                         assertions.Equal("/data-consumer/v1/info-jobs/14e7bb84-a44d-44c1-90b7-6995a92ad43c", actualRequest.URL.Path)
326                         clientMock.AssertNumberOfCalls(t, "Do", 1)
327                 })
328         }
329 }
330
331 func Test_deleteOnShutdown(t *testing.T) {
332         assertions := require.New(t)
333
334         var buf bytes.Buffer
335         log.SetOutput(&buf)
336
337         t.Cleanup(func() {
338                 log.SetOutput(os.Stderr)
339         })
340
341         type args struct {
342                 mockReturnBody   []byte
343                 mockReturnStatus int
344         }
345         tests := []struct {
346                 name      string
347                 args      args
348                 wantedLog string
349         }{
350                 {
351                         name: "Delete with successful job deletion, should return ok",
352                         args: args{
353                                 mockReturnBody:   []byte(""),
354                                 mockReturnStatus: http.StatusOK,
355                         },
356                 },
357                 {
358                         name: "Stop with error response at job deletion, should return error",
359                         args: args{
360                                 mockReturnBody:   []byte("error"),
361                                 mockReturnStatus: http.StatusBadRequest,
362                         },
363                         wantedLog: "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually",
364                 },
365         }
366         for _, tt := range tests {
367                 t.Run(tt.name, func(t *testing.T) {
368                         setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
369
370                         c := make(chan os.Signal, 1)
371                         go deleteOnShutdown(c)
372                         c <- syscall.SIGTERM
373
374                         waitForLogToBeWritten(&buf)
375
376                         log := buf.String()
377                         if tt.wantedLog != "" {
378                                 assertions.Contains(log, "level=error")
379                                 assertions.Contains(log, "Unable to delete job on shutdown due to:")
380                                 assertions.Contains(log, tt.wantedLog)
381                         }
382                 })
383         }
384 }
385
386 func waitForLogToBeWritten(logBuf *bytes.Buffer) {
387         wg := sync.WaitGroup{}
388         wg.Add(1)
389         for {
390                 if waitTimeout(&wg, 10*time.Millisecond) && logBuf.Len() != 0 {
391                         wg.Done()
392                         break
393                 }
394         }
395 }
396
397 // waitTimeout waits for the waitgroup for the specified max timeout.
398 // Returns true if waiting timed out.
399 func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
400         c := make(chan struct{})
401         go func() {
402                 defer close(c)
403                 wg.Wait()
404         }()
405         select {
406         case <-c:
407                 return false // completed normally
408         case <-time.After(timeout):
409                 return true // timed out
410         }
411 }
412
413 func setUpClientMock(body []byte, status int) *mocks.HTTPClient {
414         clientMock := mocks.HTTPClient{}
415         clientMock.On("Do", mock.Anything).Return(&http.Response{
416                 Body:       ioutil.NopCloser(bytes.NewReader(body)),
417                 StatusCode: status,
418         }, nil)
419         client = &clientMock
420         return &clientMock
421 }