blob: 6da0d95ff5e2e618d5e0242b6079d49f300867b6 [file] [log] [blame]
// -
// ========================LICENSE_START=================================
// O-RAN-SC
// %%
// Copyright (C) 2021: Nordix Foundation
// %%
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================LICENSE_END===================================
//
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"sync"
"syscall"
"testing"
"time"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"oransc.org/usecase/oruclosedloop/internal/config"
"oransc.org/usecase/oruclosedloop/internal/linkfailure"
"oransc.org/usecase/oruclosedloop/mocks"
)
func Test_init(t *testing.T) {
assertions := require.New(t)
os.Setenv("CONSUMER_HOST", "consumerHost")
os.Setenv("CONSUMER_PORT", "8095")
t.Cleanup(func() {
os.Clearenv()
})
doInit()
wantedConfiguration := &config.Config{
ConsumerHost: "consumerHost",
ConsumerPort: 8095,
InfoCoordinatorAddress: "http://enrichmentservice:8083",
SDNRAddress: "http://localhost:3904",
SDNRUser: "admin",
SDNPassword: "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U",
ORUToODUMapFile: "o-ru-to-o-du-map.csv",
ConsumerCertPath: "security/consumer.crt",
ConsumerKeyPath: "security/consumer.key",
LogLevel: log.InfoLevel,
}
assertions.Equal(wantedConfiguration, configuration)
assertions.Equal(fmt.Sprint(wantedConfiguration.ConsumerPort), consumerPort)
assertions.Equal(wantedConfiguration.ConsumerHost+":"+fmt.Sprint(wantedConfiguration.ConsumerPort), jobRegistrationInfo.JobResultURI)
wantedLinkFailureConfig := linkfailure.Configuration{
SDNRAddress: wantedConfiguration.SDNRAddress,
SDNRUser: wantedConfiguration.SDNRUser,
SDNRPassword: wantedConfiguration.SDNPassword,
}
assertions.Equal(wantedLinkFailureConfig, linkfailureConfig)
}
func Test_validateConfiguration(t *testing.T) {
assertions := require.New(t)
type args struct {
configuration *config.Config
}
tests := []struct {
name string
args args
wantErr error
}{
{
name: "Valid config, should return nil",
args: args{
configuration: &config.Config{
ConsumerHost: "host",
ConsumerPort: 80,
ConsumerCertPath: "security/consumer.crt",
ConsumerKeyPath: "security/consumer.key",
},
},
},
{
name: "Invalid config, should return error",
args: args{
configuration: &config.Config{},
},
wantErr: fmt.Errorf("consumer host and port must be provided"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateConfiguration(tt.args.configuration)
assertions.Equal(tt.wantErr, err)
})
}
}
func Test_initializeLookupService(t *testing.T) {
assertions := require.New(t)
type args struct {
csvFile string
oRuId string
mockReturn [][]string
mockReturnError error
}
tests := []struct {
name string
args args
wantODuId string
wantInitErr error
}{
{
name: "Successful initialization, should return nil and lookup service should be initiated with data",
args: args{
csvFile: "file",
oRuId: "1",
mockReturn: [][]string{{"1", "2"}},
},
wantODuId: "2",
},
{
name: "Unsuccessful initialization, should return error and lookup service should not be initiated with data",
args: args{
csvFile: "file",
mockReturnError: errors.New("Error"),
},
wantInitErr: errors.New("Error"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockCsvFileHelper := &mocks.CsvFileHelper{}
mockCsvFileHelper.On("GetCsvFromFile", mock.Anything).Return(tt.args.mockReturn, tt.args.mockReturnError)
err := initializeLookupService(mockCsvFileHelper, tt.args.csvFile)
oDuId, _ := lookupService.GetODuID(tt.args.oRuId)
assertions.Equal(tt.wantODuId, oDuId)
assertions.Equal(tt.wantInitErr, err)
mockCsvFileHelper.AssertCalled(t, "GetCsvFromFile", tt.args.csvFile)
})
}
}
func Test_getRouter_shouldContainAllPathsWithHandlers(t *testing.T) {
assertions := require.New(t)
r := getRouter()
messageHandlerRoute := r.Get("messageHandler")
assertions.NotNil(messageHandlerRoute)
supportedMethods, err := messageHandlerRoute.GetMethods()
assertions.Equal([]string{http.MethodPost}, supportedMethods)
assertions.Nil(err)
path, _ := messageHandlerRoute.GetPathTemplate()
assertions.Equal("/", path)
startHandlerRoute := r.Get("start")
assertions.NotNil(messageHandlerRoute)
supportedMethods, err = startHandlerRoute.GetMethods()
assertions.Equal([]string{http.MethodPost}, supportedMethods)
assertions.Nil(err)
path, _ = startHandlerRoute.GetPathTemplate()
assertions.Equal("/admin/start", path)
stopHandlerRoute := r.Get("stop")
assertions.NotNil(stopHandlerRoute)
supportedMethods, err = stopHandlerRoute.GetMethods()
assertions.Equal([]string{http.MethodPost}, supportedMethods)
assertions.Nil(err)
path, _ = stopHandlerRoute.GetPathTemplate()
assertions.Equal("/admin/stop", path)
statusHandlerRoute := r.Get("status")
assertions.NotNil(statusHandlerRoute)
supportedMethods, err = statusHandlerRoute.GetMethods()
assertions.Equal([]string{http.MethodGet}, supportedMethods)
assertions.Nil(err)
path, _ = statusHandlerRoute.GetPathTemplate()
assertions.Equal("/status", path)
}
func Test_startHandler(t *testing.T) {
assertions := require.New(t)
jobRegistrationInfo.JobResultURI = "host:80"
var job_definition interface{}
type args struct {
mockReturnBody []byte
mockReturnStatus int
}
tests := []struct {
name string
args args
wantedStatus int
wantedBody string
}{
{
name: "Start with successful registration, should return ok",
args: args{
mockReturnBody: []byte(""),
mockReturnStatus: http.StatusOK,
},
wantedStatus: http.StatusOK,
},
{
name: "Start with error response at registration, should return error",
args: args{
mockReturnBody: []byte("error"),
mockReturnStatus: http.StatusBadRequest,
},
wantedStatus: http.StatusBadRequest,
wantedBody: "Unable to register consumer job due to:",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clientMock := setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
handler := http.HandlerFunc(startHandler)
responseRecorder := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/start", nil)
handler.ServeHTTP(responseRecorder, r)
assertions.Equal(tt.wantedStatus, responseRecorder.Code, tt.name)
assertions.Contains(responseRecorder.Body.String(), tt.wantedBody, tt.name)
var wantedJobRegistrationInfo = struct {
InfoTypeId string `json:"info_type_id"`
JobResultUri string `json:"job_result_uri"`
JobOwner string `json:"job_owner"`
JobDefinition interface{} `json:"job_definition"`
}{
InfoTypeId: "STD_Fault_Messages",
JobResultUri: "host:80",
JobOwner: "O-RU Closed Loop Usecase",
JobDefinition: job_definition,
}
wantedBody, _ := json.Marshal(wantedJobRegistrationInfo)
var actualRequest *http.Request
clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool {
actualRequest = req
return true
}))
assertions.Equal(http.MethodPut, actualRequest.Method)
assertions.Equal("http", actualRequest.URL.Scheme)
assertions.Equal("enrichmentservice:8083", actualRequest.URL.Host)
assertions.Equal("/data-consumer/v1/info-jobs/14e7bb84-a44d-44c1-90b7-6995a92ad43c", actualRequest.URL.Path)
assertions.Equal("application/json; charset=utf-8", actualRequest.Header.Get("Content-Type"))
body, _ := ioutil.ReadAll(actualRequest.Body)
expectedBody := wantedBody
assertions.Equal(expectedBody, body)
clientMock.AssertNumberOfCalls(t, "Do", 1)
// Check that the running status is "started"
statusHandler := http.HandlerFunc(statusHandler)
statusResponseRecorder := httptest.NewRecorder()
statusRequest, _ := http.NewRequest(http.MethodGet, "/status", nil)
statusHandler.ServeHTTP(statusResponseRecorder, statusRequest)
assertions.Equal(http.StatusOK, statusResponseRecorder.Code)
assertions.Equal(`{"status": "started"}`, statusResponseRecorder.Body.String())
})
}
}
func Test_stopHandler(t *testing.T) {
assertions := require.New(t)
jobRegistrationInfo.JobResultURI = "host:80"
type args struct {
mockReturnBody []byte
mockReturnStatus int
}
tests := []struct {
name string
args args
wantedStatus int
wantedBody string
}{
{
name: "Stop with successful job deletion, should return ok",
args: args{
mockReturnBody: []byte(""),
mockReturnStatus: http.StatusOK,
},
wantedStatus: http.StatusOK,
},
{
name: "Stop with error response at job deletion, should return error",
args: args{
mockReturnBody: []byte("error"),
mockReturnStatus: http.StatusBadRequest,
},
wantedStatus: http.StatusBadRequest,
wantedBody: "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clientMock := setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
handler := http.HandlerFunc(stopHandler)
responseRecorder := httptest.NewRecorder()
r, _ := http.NewRequest(http.MethodPost, "/stop", nil)
handler.ServeHTTP(responseRecorder, r)
assertions.Equal(tt.wantedStatus, responseRecorder.Code, tt.name)
assertions.Contains(responseRecorder.Body.String(), tt.wantedBody, tt.name)
var actualRequest *http.Request
clientMock.AssertCalled(t, "Do", mock.MatchedBy(func(req *http.Request) bool {
actualRequest = req
return true
}))
assertions.Equal(http.MethodDelete, actualRequest.Method)
assertions.Equal("http", actualRequest.URL.Scheme)
assertions.Equal("enrichmentservice:8083", actualRequest.URL.Host)
assertions.Equal("/data-consumer/v1/info-jobs/14e7bb84-a44d-44c1-90b7-6995a92ad43c", actualRequest.URL.Path)
clientMock.AssertNumberOfCalls(t, "Do", 1)
// Check that the running status is "stopped"
statusHandler := http.HandlerFunc(statusHandler)
statusResponseRecorder := httptest.NewRecorder()
statusRequest, _ := http.NewRequest(http.MethodGet, "/status", nil)
statusHandler.ServeHTTP(statusResponseRecorder, statusRequest)
assertions.Equal(http.StatusOK, statusResponseRecorder.Code)
assertions.Equal(`{"status": "stopped"}`, statusResponseRecorder.Body.String())
})
}
}
func Test_deleteOnShutdown(t *testing.T) {
assertions := require.New(t)
var buf bytes.Buffer
log.SetOutput(&buf)
t.Cleanup(func() {
log.SetOutput(os.Stderr)
})
type args struct {
mockReturnBody []byte
mockReturnStatus int
}
tests := []struct {
name string
args args
wantedLog string
}{
{
name: "Delete with successful job deletion, should return ok",
args: args{
mockReturnBody: []byte(""),
mockReturnStatus: http.StatusOK,
},
},
{
name: "Stop with error response at job deletion, should return error",
args: args{
mockReturnBody: []byte("error"),
mockReturnStatus: http.StatusBadRequest,
},
wantedLog: "Please remove job 14e7bb84-a44d-44c1-90b7-6995a92ad43c manually",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setUpClientMock(tt.args.mockReturnBody, tt.args.mockReturnStatus)
c := make(chan os.Signal, 1)
go deleteOnShutdown(c)
c <- syscall.SIGTERM
waitForLogToBeWritten(&buf)
log := buf.String()
if tt.wantedLog != "" {
assertions.Contains(log, "level=error")
assertions.Contains(log, "Unable to delete job on shutdown due to:")
assertions.Contains(log, tt.wantedLog)
}
})
}
}
func waitForLogToBeWritten(logBuf *bytes.Buffer) {
wg := sync.WaitGroup{}
wg.Add(1)
for {
if waitTimeout(&wg, 10*time.Millisecond) && logBuf.Len() != 0 {
wg.Done()
break
}
}
}
// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
c := make(chan struct{})
go func() {
defer close(c)
wg.Wait()
}()
select {
case <-c:
return false // completed normally
case <-time.After(timeout):
return true // timed out
}
}
func setUpClientMock(body []byte, status int) *mocks.HTTPClient {
clientMock := mocks.HTTPClient{}
clientMock.On("Do", mock.Anything).Return(&http.Response{
Body: ioutil.NopCloser(bytes.NewReader(body)),
StatusCode: status,
}, nil)
client = &clientMock
return &clientMock
}