blob: 6da0d95ff5e2e618d5e0242b6079d49f300867b6 [file] [log] [blame]
elinuxhenrikc78feea2022-04-07 17:01:47 +02001// -
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
21package main
22
23import (
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
45func 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
81func 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
119func 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
165func 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
202func 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
290func 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
360func 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
415func 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.
428func 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
442func 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}