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
+}