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