Seed code
Issue-ID: NONRTRIC-711
Signed-off-by: elinuxhenrik <henrik.b.andersson@est.tech>
Change-Id: Id092873336a6fd564ae702b4bb0e47395ab595f8
diff --git a/goversion/main_test.go b/goversion/main_test.go
new file mode 100644
index 0000000..6da0d95
--- /dev/null
+++ b/goversion/main_test.go
@@ -0,0 +1,450 @@
+// -
+// ========================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
+}