Use env varialbes to replace image urls & tags
[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                 LogLevel:               log.InfoLevel,
58                 ConsumerHost:           "consumerHost",
59                 ConsumerPort:           8095,
60                 InfoCoordinatorAddress: "http://enrichmentservice:8083",
61                 SDNRHost:               "http://localhost",
62                 SDNRPort:               3904,
63                 SDNRUser:               "admin",
64                 SDNPassword:            "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U",
65                 ORUToODUMapFile:        "o-ru-to-o-du-map.csv",
66         }
67         assertions.Equal(wantedConfiguration, configuration)
68
69         assertions.Equal(fmt.Sprint(wantedConfiguration.ConsumerPort), consumerPort)
70         assertions.Equal(wantedConfiguration.ConsumerHost+":"+fmt.Sprint(wantedConfiguration.ConsumerPort), jobRegistrationInfo.JobResultUri)
71
72         wantedLinkFailureConfig := linkfailure.Configuration{
73                 SDNRAddress:  wantedConfiguration.SDNRHost + ":" + fmt.Sprint(wantedConfiguration.SDNRPort),
74                 SDNRUser:     wantedConfiguration.SDNRUser,
75                 SDNRPassword: wantedConfiguration.SDNPassword,
76         }
77         assertions.Equal(wantedLinkFailureConfig, linkfailureConfig)
78 }
79
80 func Test_validateConfiguration(t *testing.T) {
81         assertions := require.New(t)
82
83         type args struct {
84                 configuration *config.Config
85         }
86         tests := []struct {
87                 name    string
88                 args    args
89                 wantErr error
90         }{
91                 {
92                         name: "Valid config, should return nil",
93                         args: args{
94                                 configuration: &config.Config{
95                                         ConsumerHost: "host",
96                                         ConsumerPort: 80,
97                                 },
98                         },
99                 },
100                 {
101                         name: "Invalid config, should return error",
102                         args: args{
103                                 configuration: &config.Config{},
104                         },
105                         wantErr: fmt.Errorf("consumer host and port must be provided"),
106                 },
107         }
108         for _, tt := range tests {
109                 t.Run(tt.name, func(t *testing.T) {
110                         err := validateConfiguration(tt.args.configuration)
111                         assertions.Equal(tt.wantErr, err)
112                 })
113         }
114 }
115
116 func Test_initializeLookupService(t *testing.T) {
117         assertions := require.New(t)
118         type args struct {
119                 csvFile         string
120                 oRuId           string
121                 mockReturn      [][]string
122                 mockReturnError error
123         }
124         tests := []struct {
125                 name        string
126                 args        args
127                 wantODuId   string
128                 wantInitErr error
129         }{
130                 {
131                         name: "Successful initialization, should return nil and lookup service should be initiated with data",
132                         args: args{
133                                 csvFile:    "file",
134                                 oRuId:      "1",
135                                 mockReturn: [][]string{{"1", "2"}},
136                         },
137                         wantODuId: "2",
138                 },
139                 {
140                         name: "Unsuccessful initialization, should return error and lookup service should not be initiated with data",
141                         args: args{
142                                 csvFile:         "file",
143                                 mockReturnError: errors.New("Error"),
144                         },
145                         wantInitErr: errors.New("Error"),
146                 },
147         }
148         for _, tt := range tests {
149                 t.Run(tt.name, func(t *testing.T) {
150                         mockCsvFileHelper := &mocks.CsvFileHelper{}
151                         mockCsvFileHelper.On("GetCsvFromFile", mock.Anything).Return(tt.args.mockReturn, tt.args.mockReturnError)
152
153                         err := initializeLookupService(mockCsvFileHelper, tt.args.csvFile)
154                         oDuId, _ := lookupService.GetODuID(tt.args.oRuId)
155                         assertions.Equal(tt.wantODuId, oDuId)
156                         assertions.Equal(tt.wantInitErr, err)
157                         mockCsvFileHelper.AssertCalled(t, "GetCsvFromFile", tt.args.csvFile)
158                 })
159         }
160 }
161
162 func Test_getRouter_shouldContainAllPathsWithHandlers(t *testing.T) {
163         assertions := require.New(t)
164
165         r := getRouter()
166         messageHandlerRoute := r.Get("messageHandler")
167         assertions.NotNil(messageHandlerRoute)
168         supportedMethods, err := messageHandlerRoute.GetMethods()
169         assertions.Equal([]string{http.MethodPost}, supportedMethods)
170         assertions.Nil(err)
171         path, _ := messageHandlerRoute.GetPathTemplate()
172         assertions.Equal("/", path)
173
174         startHandlerRoute := r.Get("start")
175         assertions.NotNil(messageHandlerRoute)
176         supportedMethods, err = startHandlerRoute.GetMethods()
177         assertions.Equal([]string{http.MethodPost}, supportedMethods)
178         assertions.Nil(err)
179         path, _ = startHandlerRoute.GetPathTemplate()
180         assertions.Equal("/admin/start", path)
181
182         stopHandlerRoute := r.Get("stop")
183         assertions.NotNil(stopHandlerRoute)
184         supportedMethods, err = stopHandlerRoute.GetMethods()
185         assertions.Equal([]string{http.MethodPost}, supportedMethods)
186         assertions.Nil(err)
187         path, _ = stopHandlerRoute.GetPathTemplate()
188         assertions.Equal("/admin/stop", path)
189 }
190
191 func Test_startServer_shouldDeleteJobWhenServerStopsWithErrorAndLog(t *testing.T) {
192         assertions := require.New(t)
193
194         var buf bytes.Buffer
195         log.SetOutput(&buf)
196
197         os.Setenv("CONSUMER_PORT", "wrong")
198         t.Cleanup(func() {
199                 log.SetOutput(os.Stderr)
200         })
201
202         mockServer := &mocks.Server{}
203         mockServer.On("ListenAndServe").Return(errors.New("Server failure"))
204
205         startServer(mockServer)
206
207         log := buf.String()
208         assertions.Contains(log, "level=error")
209         assertions.Contains(log, "Server stopped unintentionally due to: Server failure. Deleteing job.")
210         assertions.Contains(log, "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually")
211 }
212
213 func Test_startHandler(t *testing.T) {
214         assertions := require.New(t)
215
216         jobRegistrationInfo.JobResultUri = "host:80"
217
218         type args struct {
219                 mockReturnBody   []byte
220                 mockReturnStatus int
221         }
222         tests := []struct {
223                 name         string
224                 args         args
225                 wantedStatus int
226                 wantedBody   string
227         }{
228                 {
229                         name: "Start with successful registration, should return ok",
230                         args: args{
231                                 mockReturnBody:   []byte(""),
232                                 mockReturnStatus: http.StatusOK,
233                         },
234                         wantedStatus: http.StatusOK,
235                 },
236                 {
237                         name: "Start with error response at registration, should return error",
238                         args: args{
239                                 mockReturnBody:   []byte("error"),
240                                 mockReturnStatus: http.StatusBadRequest,
241                         },
242                         wantedStatus: http.StatusBadRequest,
243                         wantedBody:   "Unable to register consumer job due to:",
244                 },
245         }
246         for _, tt := range tests {
247                 t.Run(tt.name, func(t *testing.T) {
248                         clientMock := setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
249
250                         handler := http.HandlerFunc(startHandler)
251                         responseRecorder := httptest.NewRecorder()
252                         r, _ := http.NewRequest(http.MethodPost, "/start", nil)
253
254                         handler.ServeHTTP(responseRecorder, r)
255
256                         assertions.Equal(tt.wantedStatus, responseRecorder.Code, tt.name)
257                         assertions.Contains(responseRecorder.Body.String(), tt.wantedBody, tt.name)
258
259                         var wantedJobRegistrationInfo = struct {
260                                 InfoTypeId    string      `json:"info_type_id"`
261                                 JobResultUri  string      `json:"job_result_uri"`
262                                 JobOwner      string      `json:"job_owner"`
263                                 JobDefinition interface{} `json:"job_definition"`
264                         }{
265                                 InfoTypeId:    "STD_Fault_Messages",
266                                 JobResultUri:  "host:80",
267                                 JobOwner:      "O-RU Closed Loop Usecase",
268                                 JobDefinition: "{}",
269                         }
270                         wantedBody, _ := json.Marshal(wantedJobRegistrationInfo)
271
272                         var actualRequest *http.Request
273                         clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool {
274                                 actualRequest = req
275                                 return true
276                         }))
277                         assertions.Equal(http.MethodPut, actualRequest.Method)
278                         assertions.Equal("http", actualRequest.URL.Scheme)
279                         assertions.Equal("enrichmentservice:8083", actualRequest.URL.Host)
280                         assertions.Equal("/data-consumer/v1/info-jobs/14e7bb84-a44d-44c1-90b7-6995a92ad43c", actualRequest.URL.Path)
281                         assertions.Equal("application/json; charset=utf-8", actualRequest.Header.Get("Content-Type"))
282                         body, _ := ioutil.ReadAll(actualRequest.Body)
283                         expectedBody := wantedBody
284                         assertions.Equal(expectedBody, body)
285                         clientMock.AssertNumberOfCalls(t, "Do", 1)
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         }
348 }
349
350 func Test_deleteOnShutdown(t *testing.T) {
351         assertions := require.New(t)
352
353         var buf bytes.Buffer
354         log.SetOutput(&buf)
355
356         t.Cleanup(func() {
357                 log.SetOutput(os.Stderr)
358         })
359
360         type args struct {
361                 mockReturnBody   []byte
362                 mockReturnStatus int
363         }
364         tests := []struct {
365                 name      string
366                 args      args
367                 wantedLog string
368         }{
369                 {
370                         name: "Delete with successful job deletion, should return ok",
371                         args: args{
372                                 mockReturnBody:   []byte(""),
373                                 mockReturnStatus: http.StatusOK,
374                         },
375                 },
376                 {
377                         name: "Stop with error response at job deletion, should return error",
378                         args: args{
379                                 mockReturnBody:   []byte("error"),
380                                 mockReturnStatus: http.StatusBadRequest,
381                         },
382                         wantedLog: "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually",
383                 },
384         }
385         for _, tt := range tests {
386                 t.Run(tt.name, func(t *testing.T) {
387                         setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
388
389                         c := make(chan os.Signal, 1)
390                         go deleteOnShutdown(c)
391                         c <- syscall.SIGTERM
392
393                         waitForLogToBeWritten(&buf)
394
395                         log := buf.String()
396                         if tt.wantedLog != "" {
397                                 assertions.Contains(log, "level=error")
398                                 assertions.Contains(log, "Unable to delete job on shutdown due to:")
399                                 assertions.Contains(log, tt.wantedLog)
400                         }
401                 })
402         }
403 }
404
405 func waitForLogToBeWritten(logBuf *bytes.Buffer) {
406         wg := sync.WaitGroup{}
407         wg.Add(1)
408         for {
409                 if waitTimeout(&wg, 10*time.Millisecond) && logBuf.Len() != 0 {
410                         wg.Done()
411                         break
412                 }
413         }
414 }
415
416 // waitTimeout waits for the waitgroup for the specified max timeout.
417 // Returns true if waiting timed out.
418 func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
419         c := make(chan struct{})
420         go func() {
421                 defer close(c)
422                 wg.Wait()
423         }()
424         select {
425         case <-c:
426                 return false // completed normally
427         case <-time.After(timeout):
428                 return true // timed out
429         }
430 }
431
432 func setUpClientMock(body []byte, status int) *mocks.HTTPClient {
433         clientMock := mocks.HTTPClient{}
434         clientMock.On("Do", mock.Anything).Return(&http.Response{
435                 Body:       ioutil.NopCloser(bytes.NewReader(body)),
436                 StatusCode: status,
437         }, nil)
438         client = &clientMock
439         return &clientMock
440 }