Improve appmgr UT coverage 47/5047/1
authorTimo Tietavainen <timo.tietavainen@nokia.com>
Thu, 12 Nov 2020 01:50:01 +0000 (03:50 +0200)
committerTimo Tietavainen <timo.tietavainen@nokia.com>
Thu, 12 Nov 2020 01:50:01 +0000 (03:50 +0200)
Improve existing unit tests coverage for appmgr's 'cm', 'helm' and 'resthooks'
packages.
Fix also 'helm install' command's overwrite configuration '---set' flag to be
'--set'.

Signed-off-by: Timo Tietavainen <timo.tietavainen@nokia.com>
Change-Id: I2a2f13575fc10191c7ee1fe21e8d3ec344cb71cc

pkg/cm/cm.go
pkg/cm/cm_test.go
pkg/helm/helm.go
pkg/helm/helm_test.go
pkg/resthooks/resthooks.go
pkg/resthooks/resthooks_test.go
pkg/resthooks/types.go
test/dummy-xapp_values.json [new file with mode: 0644]
test/faulty_schema.json [new file with mode: 0644]

index 9ff2876..9857e00 100755 (executable)
@@ -23,21 +23,24 @@ import (
        "encoding/json"
        "errors"
        "fmt"
+       "github.com/spf13/viper"
+       "github.com/valyala/fastjson"
+       "github.com/xeipuuv/gojsonschema"
        "io/ioutil"
        "os"
        "path"
        "regexp"
-       "strings"
        "strconv"
-       "github.com/spf13/viper"
-       "github.com/valyala/fastjson"
-       "github.com/xeipuuv/gojsonschema"
+       "strings"
 
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/models"
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/util"
 )
 
+var kubeExec = util.KubectlExec
+var helmExec = util.HelmExec
+
 type CM struct{}
 
 func NewCM() *CM {
@@ -69,7 +72,7 @@ func (cm *CM) UploadConfigElement(Element string) (configList models.AllXappConf
 
                c := models.XAppConfig{
                        Metadata: &models.ConfigMetadata{XappName: &xAppName, Namespace: &namespace},
-                       Config: activeConfig,
+                       Config:   activeConfig,
                }
                configList = append(configList, &c)
        }
@@ -104,7 +107,7 @@ func (cm *CM) ReadSchema(name string, desc *interface{}) (err error) {
 }
 
 func (cm *CM) UpdateConfigMap(r models.XAppConfig) (models.ConfigValidationErrors, error) {
-       fmt.Printf("Configmap update: xappName=%s namespace=%s config: %v", *r.Metadata.XappName, *r.Metadata.Namespace, r.Config)
+       fmt.Printf("Configmap update: xappName=%s namespace=%s config: %v\n", *r.Metadata.XappName, *r.Metadata.Namespace, r.Config)
        if validationErrors, err := cm.Validate(r); err != nil {
                return validationErrors, err
        }
@@ -153,7 +156,6 @@ func (cm *CM) ParseJson(dsContent string) (*fastjson.Value, error) {
        return v, err
 }
 
-
 func (cm *CM) GenerateJSONFile(jsonString string) error {
        cmJson, err := json.RawMessage(jsonString).MarshalJSON()
        if err != nil {
@@ -188,14 +190,14 @@ func (cm *CM) ReadFile(name string, data interface{}) (err error) {
 
 func (cm *CM) ReadConfigmap(name string, ns string) (string, error) {
        args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
-       out, err := util.KubectlExec(args)
+       out, err := kubeExec(args)
        return string(out), err
 }
 
-func (cm *CM) ReplaceConfigMap(name, ns string) (error) {
+func (cm *CM) ReplaceConfigMap(name, ns string) error {
        cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl replace -f -"
        args := fmt.Sprintf(cmd, ns, cm.GetConfigMapName(name, ns), viper.GetString("xapp.tmpConfig"))
-       _, err := util.KubectlExec(args)
+       _, err := kubeExec(args)
        return err
 }
 
@@ -204,7 +206,7 @@ func (cm *CM) FetchChart(name string) (err error) {
        repo := viper.GetString("helm.repo-name")
        fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
 
-       _, err = util.HelmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
+       _, err = helmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
        return
 }
 
@@ -213,7 +215,7 @@ func (cm *CM) GetRtmData(name string) (msgs appmgr.RtmData) {
 
        ns := cm.GetNamespace("")
        args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
-       out, err := util.KubectlExec(args)
+       out, err := kubeExec(args)
        if err != nil {
                return
        }
@@ -262,7 +264,7 @@ func (cm *CM) GetNamesFromHelmRepo() (names []string) {
        rname := viper.GetString("helm.repo-name")
 
        cmdArgs := strings.Join([]string{"search ", rname}, "")
-       out, err := util.HelmExec(cmdArgs)
+       out, err := helmExec(cmdArgs)
        if err != nil {
                return
        }
index 01f5bc5..b180fc9 100755 (executable)
@@ -24,6 +24,7 @@ import (
        "errors"
        "os"
        "reflect"
+       "strings"
        "testing"
 
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
@@ -31,6 +32,26 @@ import (
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/util"
 )
 
+const (
+       expectedHelmSearchCmd = "search helm-repo"
+       expectedHelmFetchCmd  = `fetch --untar --untardir /tmp helm-repo/dummy-xapp`
+)
+
+var caughtKubeExecArgs []string
+var kubeExecRetOut string
+var kubeExecRetErr error
+var caughtHelmExecArgs string
+var helmExecRetOut string
+var helmExecRetErr error
+
+var expectedKubectlGetCmd []string = []string{
+       `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-anr-appconfig`,
+       `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-appmgr-appconfig`,
+       `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-dualco-appconfig`,
+       `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-reporter-appconfig`,
+       `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-uemgr-appconfig`,
+}
+
 var helmSearchOutput = `
 helm-repo/anr           0.0.1           1.0             Helm Chart for Nokia ANR (Automatic Neighbour Relation) xAPP
 helm-repo/appmgr        0.0.2           1.0             Helm Chart for xAppManager
@@ -62,6 +83,15 @@ var kubectlConfigmapOutput = `
     }
 }
 `
+var cfgData = `{
+       "active":true,
+       "interfaceId": {
+               "globalENBId":{
+                       "plmnId": "1234",
+                       "eNBId":"55"
+               }
+       }
+}`
 
 type ConfigSample struct {
        Level int
@@ -112,20 +142,181 @@ func TestMain(m *testing.M) {
        os.Exit(code)
 }
 
-func TestGetRtmData(t *testing.T) {
+func TestUploadConfigAllSuccess(t *testing.T) {
+       var cfg interface{}
+       var expectedResult models.AllXappConfig
+       ns := "ricxapp"
+       xapps := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
+
+       if ret := json.Unmarshal([]byte(cfgData), &cfg); ret != nil {
+               t.Errorf("UploadConfigAll Json unmarshal failed: %v", ret)
+       }
+
+       for i, _ := range xapps {
+               expectedResult = append(expectedResult,
+                       &models.XAppConfig{
+                               Config: cfg,
+                               Metadata: &models.ConfigMetadata{
+                                       Namespace: &ns,
+                                       XappName:  &xapps[i],
+                               },
+                       },
+               )
+       }
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       //Fake helm search success
+       helmExecRetOut = helmSearchOutput
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' success
+       kubeExecRetOut = strings.ReplaceAll(cfgData, "\\", "")
+
+       result := NewCM().UploadConfigAll()
+       if !reflect.DeepEqual(result, expectedResult) {
+               t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedResult, result)
+       }
+       if caughtHelmExecArgs != expectedHelmSearchCmd {
+               t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedHelmSearchCmd, caughtHelmExecArgs)
+       }
+       if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubectlGetCmd) {
+               t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedKubectlGetCmd, caughtKubeExecArgs)
+       }
+}
+
+func TestUploadConfigAllReturnsEmptyMapIfAllConfigMapReadsFail(t *testing.T) {
+       var expectedResult models.AllXappConfig
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       //Fake helm search success
+       helmExecRetOut = helmSearchOutput
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' failure
+       kubeExecRetErr = errors.New("some error")
+
+       result := NewCM().UploadConfigAll()
+       if !reflect.DeepEqual(result, expectedResult) {
+               t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedResult, result)
+       }
+}
+
+func TestUploadConfigElementSuccess(t *testing.T) {
+       var cfg interface{}
+       var expectedResult models.AllXappConfig
+       ns := "ricxapp"
+       xapps := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
+
+       if ret := json.Unmarshal([]byte(cfgData), &cfg); ret != nil {
+               t.Errorf("UploadConfigElement Json unmarshal failed: %v", ret)
+       }
+
+       for i, _ := range xapps {
+               expectedResult = append(expectedResult,
+                       &models.XAppConfig{
+                               Config: cfg.(map[string]interface{})["active"],
+                               Metadata: &models.ConfigMetadata{
+                                       Namespace: &ns,
+                                       XappName:  &xapps[i],
+                               },
+                       },
+               )
+       }
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       //Fake helm search success
+       helmExecRetOut = helmSearchOutput
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' success
+       kubeExecRetOut = strings.ReplaceAll(cfgData, "\\", "")
+
+       result := NewCM().UploadConfigElement("active")
+       if !reflect.DeepEqual(result, expectedResult) {
+               t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedResult, result)
+       }
+       if caughtHelmExecArgs != expectedHelmSearchCmd {
+               t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedHelmSearchCmd, caughtHelmExecArgs)
+       }
+       if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubectlGetCmd) {
+               t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedKubectlGetCmd, caughtKubeExecArgs)
+       }
+}
+
+func TestUploadConfigElementReturnsEmptyMapIfElementMissingFromConfigMap(t *testing.T) {
+       var expectedResult models.AllXappConfig
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       //Fake helm search success
+       helmExecRetOut = helmSearchOutput
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' success
+       kubeExecRetOut = strings.ReplaceAll(cfgData, "\\", "")
+
+       //Try to upload non-existing configuration element
+       result := NewCM().UploadConfigElement("some-not-existing-element")
+       if !reflect.DeepEqual(result, expectedResult) {
+               t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedResult, result)
+       }
+}
+
+func TestGetRtmDataSuccess(t *testing.T) {
+       expectedKubeCmd := []string{
+               `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-dummy-xapp-appconfig`,
+       }
        expectedMsgs := appmgr.RtmData{
                TxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
                RxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
                Policies:   []int64{11, 22, 33},
        }
 
-       util.KubectlExec = func(args string) (out []byte, err error) {
-               return []byte(kubectlConfigmapOutput), nil
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' success
+       kubeExecRetOut = kubectlConfigmapOutput
+
+       result := NewCM().GetRtmData("dummy-xapp")
+       if !reflect.DeepEqual(result, expectedMsgs) {
+               t.Errorf("GetRtmData failed: expected: %v, got: %v", expectedMsgs, result)
+       }
+       if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubeCmd) {
+               t.Errorf("GetRtmData failed: expected: '%v', got: '%v'", expectedKubeCmd, caughtKubeExecArgs)
+       }
+}
+
+func TestGetRtmDataReturnsNoDataIfConfigmapGetFails(t *testing.T) {
+       var expectedMsgs appmgr.RtmData
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' failure
+       kubeExecRetErr = errors.New("some error")
+
+       result := NewCM().GetRtmData("dummy-xapp")
+       if !reflect.DeepEqual(result, expectedMsgs) {
+               t.Errorf("GetRtmData failed: expected: %v, got: %v", expectedMsgs, result)
        }
+}
+
+func TestGetRtmDataReturnsNoDataIfJsonParseFails(t *testing.T) {
+       var expectedMsgs appmgr.RtmData
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' to return nothing what will cause JSON parse failure
 
        result := NewCM().GetRtmData("dummy-xapp")
        if !reflect.DeepEqual(result, expectedMsgs) {
-               t.Errorf("TestGetRtmData failed: expected: %v, got: %v", expectedMsgs, result)
+               t.Errorf("GetRtmData failed: expected: %v, got: %v", expectedMsgs, result)
        }
 }
 
@@ -146,32 +337,50 @@ func TestFetchChartFails(t *testing.T) {
 }
 
 func TestFetchChartSuccess(t *testing.T) {
-       util.HelmExec = func(args string) (out []byte, err error) {
-               return
-       }
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
 
        if NewCM().FetchChart("dummy-xapp") != nil {
                t.Errorf("TestFetchChart failed!")
        }
 }
 
+func TestGetNamespaceSuccess(t *testing.T) {
+       if ns := NewCM().GetNamespace("my-ns"); ns != "my-ns" {
+               t.Errorf("GetNamespace failed: expected: my-ns, got: %s", ns)
+       }
+}
+
+func TestGetNamespaceReturnsConfiguredNamespaceName(t *testing.T) {
+       if ns := NewCM().GetNamespace(""); ns != "ricxapp" {
+               t.Errorf("GetNamespace failed: expected: ricxapp, got: %s", ns)
+       }
+}
+
 func TestGetNamesFromHelmRepoSuccess(t *testing.T) {
        expectedResult := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
-       util.HelmExec = func(args string) (out []byte, err error) {
-               return []byte(helmSearchOutput), nil
-       }
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       //Fake helm search success
+       helmExecRetOut = helmSearchOutput
 
        names := NewCM().GetNamesFromHelmRepo()
        if !reflect.DeepEqual(names, expectedResult) {
                t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
        }
+       if caughtHelmExecArgs != expectedHelmSearchCmd {
+               t.Errorf("GetNamesFromHelmRepo failed: expected: %v, got: %v", expectedHelmSearchCmd, caughtHelmExecArgs)
+       }
 }
 
 func TestGetNamesFromHelmRepoFailure(t *testing.T) {
        expectedResult := []string{}
-       util.HelmExec = func(args string) (out []byte, err error) {
-               return []byte(helmSearchOutput), errors.New("Command failed!")
-       }
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helmSearchOutput
+       helmExecRetErr = errors.New("Command failed!")
 
        names := NewCM().GetNamesFromHelmRepo()
        if names != nil {
@@ -180,26 +389,128 @@ func TestGetNamesFromHelmRepoFailure(t *testing.T) {
 }
 
 func TestBuildConfigMapSuccess(t *testing.T) {
+       expectedKubeCmd := []string{
+               `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-dummy-xapp-appconfig`,
+       }
        name := "dummy-xapp"
        namespace := "ricxapp"
        m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
-       s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, "Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
+       s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, ` +
+               `"Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
 
-       util.KubectlExec = func(args string) (out []byte, err error) {
-               return []byte(`{"logger": {"level": 2}}`), nil
-       }
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' success
+       kubeExecRetOut = `{"logger": {"level": 2}}`
 
        cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: s})
        if err != nil {
                t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
        }
+       if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubeCmd) {
+               t.Errorf("BuildConfigMap failed: expected: %v, got: %v", expectedKubeCmd, caughtKubeExecArgs)
+       }
 }
 
-func TestUpdateConfigMapFails(t *testing.T) {
+func TestBuildConfigMapReturnErrorIfJsonMarshalFails(t *testing.T) {
+       name := "dummy-xapp"
+       namespace := "ricxapp"
+       m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
+       //Give channel as a configuration input, this will fail JSON marshal
+       cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: make(chan int)})
+       if err == nil {
+               t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
+       }
+}
+
+func TestBuildConfigMapReturnErrorIfKubectlGetConfigmapFails(t *testing.T) {
+       name := "dummy-xapp"
+       namespace := "ricxapp"
+       m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
+       s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, ` +
+               `"Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl get configmap' failure
+       kubeExecRetErr = errors.New("some error")
+
+       cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: s})
+       if err == nil {
+               t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
+       } else if err.Error() != "some error" {
+               t.Errorf("BuildConfigMap failed: expected: 'some error', got: '%s'", err.Error())
+       }
+}
+
+func TestBuildConfigMapReturnErrorIfJsonParserFails(t *testing.T) {
+       name := "dummy-xapp"
+       namespace := "ricxapp"
+       m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
+       s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, ` +
+               `"Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Return empty json that causes JSON parser to fail
+       kubeExecRetOut = ``
+
+       cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: s})
+       if err == nil {
+               t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
+       }
+}
+
+func TestGenerateJSONFileSuccess(t *testing.T) {
+       err := NewCM().GenerateJSONFile("{}")
+       if err != nil {
+               t.Errorf("GenerateJSONFile failed: %v", err)
+       }
+}
+
+func TestReplaceConfigMapSuccess(t *testing.T) {
+       name := "dummy-xapp"
+       namespace := "ricxapp"
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       //Fake 'kubectl create configmap' success
+       kubeExecRetOut = ""
+
+       err := NewCM().ReplaceConfigMap(name, namespace)
+       if err != nil {
+               t.Errorf("ReplaceConfigMap failed: %v", err)
+       }
+}
+
+func TestUpdateConfigMapReturnsErrorIfSchemaFileIsMissing(t *testing.T) {
+       name := "dummy-xapp"
+       namespace := "ricxapp"
+       config := models.XAppConfig{Metadata: &models.ConfigMetadata{XappName: &name, Namespace: &namespace}}
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = `{}`
+
+       //Will fail at schema reading, because schema file is mission
+       validationErrors, err := NewCM().UpdateConfigMap(config)
+       if err == nil {
+               t.Errorf("UpdateConfigMap failed: %v -> %v", err, validationErrors)
+       }
+       if caughtHelmExecArgs != expectedHelmFetchCmd {
+               t.Errorf("UpdateConfigMap failed: expected: %v, got: %v", expectedHelmFetchCmd, caughtHelmExecArgs)
+       }
+}
+
+func TestUpdateConfigMapReturnsErrorIfHelmFetchChartFails(t *testing.T) {
        name := "dummy-xapp"
        namespace := "ricxapp"
        config := models.XAppConfig{Metadata: &models.ConfigMetadata{XappName: &name, Namespace: &namespace}}
 
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetErr = errors.New("some error")
+
        validationErrors, err := NewCM().UpdateConfigMap(config)
        if err == nil {
                t.Errorf("UpdateConfigMap failed: %v -> %v", err, validationErrors)
@@ -225,6 +536,7 @@ func TestValidationSuccess(t *testing.T) {
 func TestValidationFails(t *testing.T) {
        var d interface{}
        var cfg map[string]interface{}
+
        err := json.Unmarshal([]byte(`{"active": "INVALID", "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}`), &cfg)
 
        err = NewCM().ReadFile("../../test/schema.json", &d)
@@ -234,7 +546,47 @@ func TestValidationFails(t *testing.T) {
 
        feedback, err := NewCM().doValidate(d, cfg)
        if err == nil {
-               t.Errorf("doValidate should faile but didn't: %v -> %v", err, feedback)
+               t.Errorf("doValidate should fail but didn't: %v -> %v", err, feedback)
        }
        appmgr.Logger.Debug("Feedbacks: %v", feedback)
 }
+
+func TestReadFileReturnsErrorIfFileReadFails(t *testing.T) {
+       var d interface{}
+
+       if err := NewCM().ReadFile("not/existing/test/schema.json", &d); err == nil {
+               t.Errorf("ReadFile should fail but it didn't")
+       }
+}
+
+func TestReadFileReturnsErrorIfJsonUnmarshalFails(t *testing.T) {
+       var d interface{}
+
+       if err := NewCM().ReadFile("../../test/faulty_schema.json", &d); err == nil {
+               t.Errorf("ReadFile should fail but it didn't")
+       }
+}
+
+func mockedKubeExec(args string) (out []byte, err error) {
+       caughtKubeExecArgs = append(caughtKubeExecArgs, args)
+       return []byte(kubeExecRetOut), kubeExecRetErr
+}
+
+func resetKubeExecMock() {
+       kubeExec = util.KubectlExec
+       caughtKubeExecArgs = nil
+       kubeExecRetOut = ""
+       kubeExecRetErr = nil
+}
+
+func mockedHelmExec(args string) (out []byte, err error) {
+       caughtHelmExecArgs = args
+       return []byte(helmExecRetOut), helmExecRetErr
+}
+
+func resetHelmExecMock() {
+       helmExec = util.HelmExec
+       caughtHelmExecArgs = ""
+       helmExecRetOut = ""
+       helmExecRetErr = nil
+}
index ed00b9e..e777403 100755 (executable)
@@ -36,6 +36,9 @@ import (
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/util"
 )
 
+var kubeExec = util.KubectlExec
+var helmExec = util.HelmExec
+
 type Helm struct {
        initDone bool
        cm       *cm.CM
@@ -71,7 +74,7 @@ func (h *Helm) Initialize() {
 }
 
 func (h *Helm) Run(args string) (out []byte, err error) {
-       return util.HelmExec(args)
+       return helmExec(args)
 }
 
 // API functions
@@ -80,7 +83,7 @@ func (h *Helm) Init() (out []byte, err error) {
                return out, err
        }
 
-       return util.HelmExec(strings.Join([]string{"init -c --skip-refresh"}, ""))
+       return helmExec(strings.Join([]string{"init -c --skip-refresh"}, ""))
 }
 
 func (h *Helm) AddRepo() (out []byte, err error) {
@@ -100,7 +103,7 @@ func (h *Helm) AddRepo() (out []byte, err error) {
        repoArgs := fmt.Sprintf(" %s %s ", viper.GetString("helm.repo-name"), viper.GetString("helm.repo"))
        credentials := fmt.Sprintf(" --username %s --password %s", string(username), string(password))
 
-       return util.HelmExec(strings.Join([]string{"repo add ", repoArgs, credentials}, ""))
+       return helmExec(strings.Join([]string{"repo add ", repoArgs, credentials}, ""))
 }
 
 func (h *Helm) Install(m models.XappDescriptor) (xapp models.Xapp, err error) {
@@ -124,7 +127,6 @@ func (h *Helm) Status(name string) (xapp models.Xapp, err error) {
                appmgr.Logger.Info("Getting xapps status: %v", err.Error())
                return
        }
-
        return h.ParseStatus(name, string(out))
 }
 
@@ -218,7 +220,7 @@ func (h *Helm) GetEndpointInfo(name string) (svc string, port int) {
        port = 4560 // Default
        ns := h.cm.GetNamespace("")
        args := fmt.Sprintf(" get service -n %s service-%s-%s-rmr -o json", ns, ns, name)
-       out, err := util.KubectlExec(args)
+       out, err := kubeExec(args)
        if err != nil {
                return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
        }
@@ -339,7 +341,7 @@ func (h *Helm) GetInstallArgs(x models.XappDescriptor, cmOverride bool) (args st
        }
 
        if cmOverride == true {
-               args = fmt.Sprintf("%s ---set ricapp.appconfig.override=%s-appconfig", args, *x.XappName)
+               args = fmt.Sprintf("%s --set ricapp.appconfig.override=%s-appconfig", args, *x.XappName)
        }
 
        if x.OverrideFile != nil {
index 109103c..1bfdb22 100755 (executable)
 package helm
 
 import (
+       "errors"
+       "github.com/spf13/viper"
        "os"
        "reflect"
        "strconv"
+       "strings"
        "testing"
 
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
@@ -30,6 +33,12 @@ import (
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/util"
 )
 
+var caughtKubeExecArgs string
+var kubeExecRetOut string
+var kubeExecRetErr error
+var caughtHelmExecArgs string
+var helmExecRetOut string
+var helmExecRetErr error
 var helmStatusOutput = `
 LAST DEPLOYED: Sat Mar  9 06:50:45 2019
 NAMESPACE: default
@@ -51,7 +60,7 @@ NAME        READY  UP-TO-DATE  AVAILABLE  AGE
 dummy-xapp  3/3    3           3          55m
 `
 
-var helListOutput = `Next: ""
+var helListAllOutput = `Next: ""
 Releases:
 - AppVersion: "1.0"
   Chart: dummy-xapp-chart-0.1.0
@@ -76,7 +85,18 @@ Releases:
   Updated: Sun Mar 24 07:17:00 2019
   `
 
-var helmServiceOutput = `{
+var helListOutput = `Next: ""
+Releases:
+- AppVersion: "1.0"
+  Chart: dummy-xapp-chart-0.1.0
+  Name: dummy-xapp
+  Namespace: default
+  Revision: 1
+  Status: DEPLOYED
+  Updated: Mon Mar 11 06:55:05 2019
+  `
+
+var kubeServiceOutput = `{
     "apiVersion": "v1",
     "kind": "Service",
     "metadata": {
@@ -130,44 +150,332 @@ func TestMain(m *testing.M) {
        os.Exit(code)
 }
 
-func TestHelmStatus(t *testing.T) {
-       util.KubectlExec = func(args string) (out []byte, err error) {
-               return []byte(helmServiceOutput), nil
+func TestInit(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+
+       NewHelm().Init()
+
+       expectedHelmCommand := "init -c --skip-refresh"
+       if caughtHelmExecArgs != expectedHelmCommand {
+               t.Errorf("Init failed: expected %v, got %v", expectedHelmCommand, caughtHelmExecArgs)
+       }
+}
+
+func TestAddRepoSuccess(t *testing.T) {
+       defer func() {
+               resetHelmExecMock()
+               removeTestUsernameFile()
+               removeTestPasswordFile()
+       }()
+       helmExec = mockedHelmExec
+
+       if err := writeTestUsernameFile(); err != nil {
+               t.Errorf("AddRepo username file create failed: %s", err)
+               return
+       }
+       if err := writeTestPasswordFile(); err != nil {
+               t.Errorf("AddRepo password file create failed: %s", err)
+               return
+       }
+
+       if _, err := NewHelm().AddRepo(); err != nil {
+               t.Errorf("AddRepo failed: %v", err)
+       }
+
+       if !strings.Contains(caughtHelmExecArgs, "repo add") {
+               t.Errorf("AddRepo failed: expected %v, got %v", "repo add", caughtHelmExecArgs)
+       }
+}
+
+func TestAddRepoReturnsErrorIfNoUsernameFile(t *testing.T) {
+       if _, err := NewHelm().AddRepo(); err == nil {
+               t.Errorf("AddRepo expected to fail but it didn't")
        }
+}
+
+func TestAddRepoReturnsErrorIfNoPasswordFile(t *testing.T) {
+       defer func() { resetHelmExecMock(); removeTestUsernameFile() }()
+       helmExec = mockedHelmExec
+
+       if err := writeTestUsernameFile(); err != nil {
+               t.Errorf("AddRepo username file create failed: %s", err)
+               return
+       }
+       if _, err := NewHelm().AddRepo(); err == nil {
+               t.Errorf("AddRepo expected to fail but it didn't")
+       }
+}
+
+func TestInstallSuccess(t *testing.T) {
+       name := "dummy-xapp"
+       xappDesc := models.XappDescriptor{XappName: &name, Namespace: "ricxapp"}
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helmStatusOutput
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       kubeExecRetOut = kubeServiceOutput
+
+       xapp, err := NewHelm().Install(xappDesc)
+       if err != nil {
+               t.Errorf("Install failed: %v", err)
+       }
+       validateXappModel(t, xapp)
+}
+
+func TestInstallReturnsErrorIfHelmRepoUpdateFails(t *testing.T) {
+       name := "dummy-xapp"
+       xappDesc := models.XappDescriptor{XappName: &name, Namespace: "ricxapp"}
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetErr = errors.New("some helm command error")
+
+       if _, err := NewHelm().Install(xappDesc); err == nil {
+               t.Errorf("Install expected to fail but it didn't")
+       }
+}
+
+func TestStatusSuccess(t *testing.T) {
+       name := "dummy-xapp"
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helmStatusOutput
+
+       xapp, err := NewHelm().Status(name)
+       if err != nil {
+               t.Errorf("Status failed: %v", err)
+       }
+       validateXappModel(t, xapp)
+}
+
+func TestStatusReturnsErrorIfHelmStatusFails(t *testing.T) {
+       name := "dummy-xapp"
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetErr = errors.New("some helm command error")
+
+       if _, err := NewHelm().Status(name); err == nil {
+               t.Errorf("Status expected to fail but it didn't")
+       }
+}
+
+func TestParseStatusSuccess(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helListOutput
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       kubeExecRetOut = kubeServiceOutput
+
        xapp, err := NewHelm().ParseStatus("dummy-xapp", helmStatusOutput)
        if err != nil {
-               t.Errorf("Helm install failed: %v", err)
+               t.Errorf("ParseStatus failed: %v", err)
        }
-       x := getXappData()
-       xapp.Version = "1.0"
 
-       if *x.Name != *xapp.Name || x.Status != xapp.Status || x.Version != xapp.Version {
-               t.Errorf("\n%v \n%v", *xapp.Name, *x.Name)
+       validateXappModel(t, xapp)
+
+       expectedHelmCommand := "list --deployed --output yaml --namespace=ricxapp dummy-xapp"
+       if caughtHelmExecArgs != expectedHelmCommand {
+               t.Errorf("ParseStatus failed: expected %v, got %v", expectedHelmCommand, caughtHelmExecArgs)
+       }
+}
+
+func TestListSuccess(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helListAllOutput
+
+       names, err := NewHelm().List()
+       if err != nil {
+               t.Errorf("List failed: %v", err)
        }
 
-       if *x.Instances[0].Name != *xapp.Instances[0].Name || x.Instances[0].Status != xapp.Instances[0].Status {
-               t.Errorf("\n1:%v 2:%v", *x.Instances[0].Name, *xapp.Instances[0].Name)
+       if !reflect.DeepEqual(names, []string{"dummy-xapp", "dummy-xapp2"}) {
+               t.Errorf("List failed: %v", err)
+       }
+       expectedHelmCommand := "list --all --deployed --output yaml --namespace=ricxapp"
+       if caughtHelmExecArgs != expectedHelmCommand {
+               t.Errorf("List: expected %v, got %v", expectedHelmCommand, caughtHelmExecArgs)
        }
+}
+
+func TestListReturnsErrorIfHelmListFails(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetErr = errors.New("some helm command error")
 
-       if x.Instances[0].IP != xapp.Instances[0].IP || x.Instances[0].Port != xapp.Instances[0].Port {
-               t.Errorf("\n%v - %v, %v - %v", x.Instances[0].IP, xapp.Instances[0].IP, x.Instances[0].Port, xapp.Instances[0].Port)
+       if _, err := NewHelm().List(); err == nil {
+               t.Errorf("List expected to fail but it didn't")
        }
 }
 
-func TestHelmLists(t *testing.T) {
-       names, err := NewHelm().GetNames(helListOutput)
+func TestDeleteSuccess(t *testing.T) {
+       name := "dummy-xapp"
+
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helmStatusOutput
+
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       kubeExecRetOut = kubeServiceOutput
+
+       xapp, err := NewHelm().Delete(name)
        if err != nil {
-               t.Errorf("Helm status failed: %v", err)
+               t.Errorf("Delete failed: %v", err)
+       }
+
+       validateXappModel(t, xapp)
+
+       expectedHelmCommand := "del --purge dummy-xapp"
+       if caughtHelmExecArgs != expectedHelmCommand {
+               t.Errorf("Delete failed: expected %v, got %v", expectedHelmCommand, caughtHelmExecArgs)
        }
+}
+
+func TestDeleteReturnsErrorIfHelmStatusFails(t *testing.T) {
+       name := "dummy-xapp"
 
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetErr = errors.New("some helm command error")
+
+       if _, err := NewHelm().Delete(name); err == nil {
+               t.Errorf("Delete expected to fail but it didn't")
+       }
+}
+
+func TestFetchSuccessIfCmdArgHasTestSuffix(t *testing.T) {
+       if err := NewHelm().Fetch("kissa", "koira"); err != nil {
+               t.Errorf("Fetch failed: %v", err)
+       }
+}
+
+func TestGetVersionSuccess(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helListOutput
+
+       if version := NewHelm().GetVersion("dummy-xapp"); version != "1.0" {
+               t.Errorf("GetVersion failed: expected 1.0, got %v", version)
+       }
+
+       expectedHelmCommand := "list --deployed --output yaml --namespace=ricxapp dummy-xapp"
+       if caughtHelmExecArgs != expectedHelmCommand {
+               t.Errorf("GetVersion failed: expected %v, got %v", expectedHelmCommand, caughtHelmExecArgs)
+       }
+}
+
+func TestGetVersionReturnsEmptyStringIfHelmListFails(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetErr = errors.New("some helm command error")
+
+       if version := NewHelm().GetVersion("dummy-xapp"); version != "" {
+               t.Errorf("GetVersion expected to return empty string, got %v", version)
+       }
+}
+
+func TestGetAddressSuccess(t *testing.T) {
+       ip, port := NewHelm().GetAddress(helmStatusOutput)
+       if ip != "10.102.184.212" {
+               t.Errorf("GetAddress failed: expected 10.102.184.212, got %v", ip)
+       }
+       if port != "80/TCP" {
+               t.Errorf("GetAddress failed: expected 80/TCP, got %v", port)
+       }
+}
+
+func TestGetEndpointInfoSuccess(t *testing.T) {
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       kubeExecRetOut = kubeServiceOutput
+
+       svc, port := NewHelm().GetEndpointInfo("dummy-xapp")
+       expectedSvc := "service-ricxapp-dummy-xapp-rmr.ricxapp"
+       if svc != expectedSvc {
+               t.Errorf("GetEndpointInfo failed: expected %v, got %v", expectedSvc, svc)
+       }
+       if port != 4560 {
+               t.Errorf("GetEndpointInfo failed: expected port 4560, got %v", port)
+       }
+       expectedKubeCommand := " get service -n ricxapp service-ricxapp-dummy-xapp-rmr -o json"
+       if caughtKubeExecArgs != expectedKubeCommand {
+               t.Errorf("GetEndpointInfo failed: expected %v, got %v", expectedKubeCommand, caughtKubeExecArgs)
+       }
+}
+
+func TestGetEndpointInfoReturnsDefaultPortIfJsonParseFails(t *testing.T) {
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       kubeExecRetOut = "not-json-syntax"
+
+       svc, port := NewHelm().GetEndpointInfo("dummy-xapp")
+       expectedSvc := "service-ricxapp-dummy-xapp-rmr.ricxapp"
+       if svc != expectedSvc {
+               t.Errorf("GetEndpointInfo failed: expected %v, got %v", expectedSvc, svc)
+       }
+       if port != 4560 {
+               t.Errorf("GetEndpointInfo failed: expected port 4560, got %v", port)
+       }
+}
+
+func TestGetEndpointInfoReturnsDefaultPortIfKubeGetServiceFails(t *testing.T) {
+       defer func() { resetKubeExecMock() }()
+       kubeExec = mockedKubeExec
+       kubeExecRetErr = errors.New("some helm command error")
+
+       svc, port := NewHelm().GetEndpointInfo("dummy-xapp")
+       expectedSvc := "service-ricxapp-dummy-xapp-rmr.ricxapp"
+       if svc != expectedSvc {
+               t.Errorf("GetEndpointInfo failed: expected %v, got %v", expectedSvc, svc)
+       }
+       if port != 4560 {
+               t.Errorf("GetEndpointInfo failed: expected port 4560, got %v", port)
+       }
+}
+
+func TestHelmStatusAllSuccess(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetOut = helListAllOutput
+
+       if _, err := NewHelm().StatusAll(); err != nil {
+               t.Errorf("StatusAll failed: %v", err)
+       }
+       // Todo: check StatusAll response content
+}
+
+func TestStatusAllReturnsErrorIfHelmListFails(t *testing.T) {
+       defer func() { resetHelmExecMock() }()
+       helmExec = mockedHelmExec
+       helmExecRetErr = errors.New("some helm command error")
+
+       if _, err := NewHelm().StatusAll(); err == nil {
+               t.Errorf("StatusAll expected to fail but it didn't")
+       }
+}
+
+func TestGetNamesSuccess(t *testing.T) {
+       names, err := NewHelm().GetNames(helListAllOutput)
+       if err != nil {
+               t.Errorf("GetNames failed: %v", err)
+       }
        if !reflect.DeepEqual(names, []string{"dummy-xapp", "dummy-xapp2"}) {
-               t.Errorf("Helm status failed: %v", err)
+               t.Errorf("GetNames failed: %v", err)
        }
 }
 
 func TestAddTillerEnv(t *testing.T) {
        if NewHelm().AddTillerEnv() != nil {
-               t.Errorf("TestAddTillerEnv failed!")
+               t.Errorf("AddTillerEnv failed!")
        }
 }
 
@@ -177,22 +485,62 @@ func TestGetInstallArgs(t *testing.T) {
 
        expectedArgs := "install helm-repo/dummy-xapp  --namespace=ricxapp --name=dummy-xapp"
        if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
-               t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+               t.Errorf("GetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+       }
+
+       expectedArgs += " --set ricapp.appconfig.override=dummy-xapp-appconfig"
+       if args := NewHelm().GetInstallArgs(x, true); args != expectedArgs {
+               t.Errorf("GetInstallArgs failed: expected %v, got %v", expectedArgs, args)
        }
 
        x.HelmVersion = "1.2.3"
        expectedArgs = "install helm-repo/dummy-xapp  --namespace=ricxapp --version=1.2.3 --name=dummy-xapp"
        if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
-               t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+               t.Errorf("GetInstallArgs failed: expected %v, got %v", expectedArgs, args)
        }
 
        x.ReleaseName = "ueec-xapp"
        expectedArgs = "install helm-repo/dummy-xapp  --namespace=ricxapp --version=1.2.3 --name=ueec-xapp"
        if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
-               t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+               t.Errorf("GetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+       }
+
+       x.OverrideFile = "../../test/dummy-xapp_values.json"
+       expectedArgs += " -f=/tmp/appmgr_override.yaml"
+       if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
+               t.Errorf("GetInstallArgs failed: expected %v, got %v", expectedArgs, args)
        }
 }
 
+func writeTestUsernameFile() error {
+       f, err := os.Create(viper.GetString("helm.helm-username-file"))
+       if err != nil {
+               return err
+       }
+       _, err = f.WriteString("some-username")
+       f.Close()
+       return err
+}
+
+func removeTestUsernameFile() error {
+       return os.Remove(viper.GetString("helm.helm-username-file"))
+}
+
+func writeTestPasswordFile() (err error) {
+       f, err := os.Create(viper.GetString("helm.helm-password-file"))
+       if err != nil {
+               return err
+       }
+
+       _, err = f.WriteString("some-password")
+       f.Close()
+       return err
+}
+
+func removeTestPasswordFile() error {
+       return os.Remove(viper.GetString("helm.helm-password-file"))
+}
+
 func getXappData() (x models.Xapp) {
        //name1 := "dummy-xapp-8984fc9fd-l6xch"
        //name2 := "dummy-xapp-8984fc9fd-pp4hg"
@@ -224,3 +572,44 @@ func generateXapp(name, status, ver, iname, istatus, ip, port string) (x models.
 
        return
 }
+
+func mockedKubeExec(args string) (out []byte, err error) {
+       caughtKubeExecArgs = args
+       return []byte(kubeExecRetOut), kubeExecRetErr
+}
+
+func resetKubeExecMock() {
+       kubeExec = util.KubectlExec
+       caughtKubeExecArgs = ""
+       kubeExecRetOut = ""
+       kubeExecRetErr = nil
+}
+
+func mockedHelmExec(args string) (out []byte, err error) {
+       caughtHelmExecArgs = args
+       return []byte(helmExecRetOut), helmExecRetErr
+}
+
+func resetHelmExecMock() {
+       helmExec = util.HelmExec
+       caughtHelmExecArgs = ""
+       helmExecRetOut = ""
+       helmExecRetErr = nil
+}
+
+func validateXappModel(t *testing.T, xapp models.Xapp) {
+       expXapp := getXappData()
+       xapp.Version = "1.0"
+
+       if *expXapp.Name != *xapp.Name || expXapp.Status != xapp.Status || expXapp.Version != xapp.Version {
+               t.Errorf("\n%v \n%v", *xapp.Name, *expXapp.Name)
+       }
+
+       if *expXapp.Instances[0].Name != *xapp.Instances[0].Name || expXapp.Instances[0].Status != xapp.Instances[0].Status {
+               t.Errorf("\n1:%v 2:%v", *expXapp.Instances[0].Name, *xapp.Instances[0].Name)
+       }
+
+       if expXapp.Instances[0].IP != xapp.Instances[0].IP || expXapp.Instances[0].Port != xapp.Instances[0].Port {
+               t.Errorf("\n%v - %v, %v - %v", expXapp.Instances[0].IP, xapp.Instances[0].IP, expXapp.Instances[0].Port, xapp.Instances[0].Port)
+       }
+}
index dea70c9..9ac49f9 100755 (executable)
@@ -33,9 +33,13 @@ import (
 )
 
 func NewResthook(restoreData bool) *Resthook {
+       return createResthook(restoreData, sdl.NewSdlInstance("appmgr", sdl.NewDatabase()))
+}
+
+func createResthook(restoreData bool, sdlInst iSdl) *Resthook {
        rh := &Resthook{
                client: &http.Client{},
-               db:     sdl.NewSdlInstance("appmgr", sdl.NewDatabase()),
+               db:     sdlInst,
        }
 
        if restoreData {
@@ -176,7 +180,6 @@ func (rh *Resthook) retry(s SubscriptionInfo, fn func() error) error {
 func (rh *Resthook) StoreSubscriptions(m cmap.ConcurrentMap) {
        for v := range m.Iter() {
                s := v.Val.(SubscriptionInfo)
-
                data, err := json.Marshal(s.req)
                if err != nil {
                        appmgr.Logger.Error("json.marshal failed: %v ", err.Error())
index d1ac790..e89383f 100755 (executable)
 package resthooks
 
 import (
+       "encoding/json"
+       "errors"
+       "fmt"
        "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/mock"
+       "net"
+       "net/http"
+       "net/http/httptest"
        "os"
+       "time"
+
+       "strconv"
        "testing"
 
        "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
@@ -30,31 +40,40 @@ import (
 
 var rh *Resthook
 var resp models.SubscriptionResponse
+var mockedSdl *SdlMock
 
 // Test cases
 func TestMain(m *testing.M) {
        appmgr.Init()
        appmgr.Logger.SetLevel(0)
 
-       rh = NewResthook(false)
+       mockedSdl = new(SdlMock)
+       rh = createResthook(false, mockedSdl)
        code := m.Run()
        os.Exit(code)
 }
 
 func TestAddSubscriptionSuccess(t *testing.T) {
-       resp := rh.AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+       var mockSdlRetOk error
+       subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+
+       mockedSdl.expectDbSet(t, subsReq, mockSdlRetOk)
+       resp := rh.AddSubscription(subsReq)
        assert.Equal(t, resp.Version, int64(0))
        assert.Equal(t, resp.EventType, models.EventTypeCreated)
 }
 
 func TestAddSubscriptionExists(t *testing.T) {
-       resp := rh.AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+       resp := rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
        assert.Equal(t, resp.Version, int64(0))
        assert.Equal(t, resp.EventType, models.EventTypeCreated)
 }
 
 func TestDeletesubscriptionSuccess(t *testing.T) {
-       resp := rh.AddSubscription(CreateSubscription(models.EventTypeDeleted, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+       var mockSdlRetOk error
+
+       mockedSdl.On("Set", mock.Anything).Return(mockSdlRetOk)
+       resp := rh.AddSubscription(createSubscription(models.EventTypeDeleted, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
        assert.Equal(t, resp.Version, int64(0))
        assert.Equal(t, resp.EventType, models.EventTypeDeleted)
 
@@ -72,17 +91,23 @@ func TestDeletesubscriptionInvalid(t *testing.T) {
 }
 
 func TestModifySubscriptionSuccess(t *testing.T) {
-       resp := rh.AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+       resp := rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
        assert.Equal(t, resp.Version, int64(0))
        assert.Equal(t, resp.EventType, models.EventTypeCreated)
 
-       resp, ok := rh.ModifySubscription(resp.ID, CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+       resp, ok := rh.ModifySubscription(resp.ID, createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
        assert.Equal(t, ok, true)
        assert.Equal(t, resp.Version, int64(0))
        assert.Equal(t, resp.EventType, models.EventTypeModified)
 }
 
-func TestModifysubscriptionInvalid(t *testing.T) {
+func TestModifySubscriptionForNonExistingSubscription(t *testing.T) {
+       resp, ok := rh.ModifySubscription("Non-existent-ID", createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+       assert.Equal(t, ok, false)
+       assert.Equal(t, *resp, models.SubscriptionResponse{})
+}
+
+func TestDeleteSubscriptionForNonExistingSubscription(t *testing.T) {
        resp, ok := rh.DeleteSubscription("Non-existent-ID")
        assert.Equal(t, ok, false)
        assert.Equal(t, resp.Version, int64(0))
@@ -90,21 +115,22 @@ func TestModifysubscriptionInvalid(t *testing.T) {
 }
 
 func TestGetAllSubscriptionSuccess(t *testing.T) {
-       rh.FlushSubscriptions()
+       flushExistingSubscriptions()
        subscriptions := rh.GetAllSubscriptions()
        assert.Equal(t, len(subscriptions), 0)
 
-       rh.AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
-       rh.AddSubscription(CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+       rh.AddSubscription(createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+       rh.AddSubscription(createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
 
        subscriptions = rh.GetAllSubscriptions()
        assert.Equal(t, len(subscriptions), 2)
 }
 
 func TestGetSubscriptionByIdSuccess(t *testing.T) {
-       rh.FlushSubscriptions()
-       sub1 := CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
-       sub2 := CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2")
+       flushExistingSubscriptions()
+
+       sub1 := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+       sub2 := createSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2")
        r1 := rh.AddSubscription(sub1)
        r2 := rh.AddSubscription(sub2)
 
@@ -117,10 +143,212 @@ func TestGetSubscriptionByIdSuccess(t *testing.T) {
        assert.Equal(t, resp2.Data, sub2.Data)
 }
 
+func TestGetSubscriptionByIdForNonExistingSubscription(t *testing.T) {
+       resp, ok := rh.GetSubscriptionById("Non-existent-ID")
+       assert.Equal(t, ok, false)
+       assert.Equal(t, resp, models.Subscription{})
+}
+
+func TestNotifyClientsNoXapp(t *testing.T) {
+       rh.NotifyClients(models.AllDeployedXapps{}, models.EventTypeUndeployed)
+}
+
+func TestNotifySuccess(t *testing.T) {
+       flushExistingSubscriptions()
+
+       sub := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+       resp := rh.AddSubscription(sub)
+
+       xapp := getDummyXapp()
+       ts := createHTTPServer(t, "POST", "/xapps_hook", 8087, http.StatusOK, nil)
+       defer ts.Close()
+
+       v, ok := rh.subscriptions.Get(resp.ID)
+       assert.True(t, ok)
+       err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
+       assert.Nil(t, err)
+}
+
+func TestNotifySuccessIfHttpErrorResponse(t *testing.T) {
+       flushExistingSubscriptions()
+
+       sub := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+       resp := rh.AddSubscription(sub)
+
+       xapp := getDummyXapp()
+       ts := createHTTPServer(t, "POST", "/xapps_hook", 8087, http.StatusInternalServerError, nil)
+       defer ts.Close()
+
+       v, ok := rh.subscriptions.Get(resp.ID)
+       assert.True(t, ok)
+       err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
+       assert.Nil(t, err)
+}
+
+func TestNotifyReturnsErrorAfterRetriesIfNoHttpServer(t *testing.T) {
+       flushExistingSubscriptions()
+
+       sub := createSubscription(models.EventTypeCreated, int64(2), int64(1), "http://localhost:8087/xapps_hook")
+       resp := rh.AddSubscription(sub)
+
+       xapp := getDummyXapp()
+
+       v, ok := rh.subscriptions.Get(resp.ID)
+       assert.True(t, ok)
+       err := rh.notify(models.AllDeployedXapps{&xapp}, models.EventTypeUndeployed, v.(SubscriptionInfo), 1)
+       assert.NotNil(t, err)
+       assert.Equal(t, 0, len(rh.subscriptions.Items()))
+}
+
+func TestRestoreSubscriptionsSuccess(t *testing.T) {
+       var mockSdlRetOk error
+       mSdl := new(SdlMock)
+       key := "key-1"
+
+       subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+       serializedSubsReq, err := json.Marshal(subsReq)
+       assert.Nil(t, err)
+
+       mockSdlGetRetVal := make(map[string]interface{})
+       //Cast data to string to act like a real SDL/Redis client
+       mockSdlGetRetVal[key] = string(serializedSubsReq)
+       mSdl.On("GetAll").Return([]string{key}, mockSdlRetOk).Twice()
+       mSdl.On("Get", []string{key}).Return(mockSdlGetRetVal, mockSdlRetOk).Once()
+       restHook := createResthook(true, mSdl)
+
+       val, found := restHook.subscriptions.Get(key)
+       assert.True(t, found)
+       assert.Equal(t, subsReq, val.(SubscriptionInfo).req)
+}
+
+func TestRestoreSubscriptionsFailsIfSdlGetAllFails(t *testing.T) {
+       var mockSdlRetStatus error
+       mSdl := new(SdlMock)
+       getCalled := 0
+       mGetAllCall := mSdl.On("GetAll")
+       mGetAllCall.RunFn = func(args mock.Arguments) {
+               if getCalled > 0 {
+                       mockSdlRetStatus = errors.New("some SDL error")
+               }
+               getCalled++
+               mGetAllCall.ReturnArguments = mock.Arguments{[]string{}, mockSdlRetStatus}
+       }
+
+       restHook := createResthook(true, mSdl)
+       assert.Equal(t, 0, len(restHook.subscriptions.Items()))
+}
+
+func TestRestoreSubscriptionsFailsIfSdlGetFails(t *testing.T) {
+       var mockSdlRetOk error
+       mSdl := new(SdlMock)
+       mockSdlRetNok := errors.New("some SDL error")
+       key := "key-1"
+       subsReq := createSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+       serializedSubsReq, err := json.Marshal(subsReq)
+       assert.Nil(t, err)
+
+       mockSdlGetRetVal := make(map[string]interface{})
+       mockSdlGetRetVal[key] = serializedSubsReq
+
+       mSdl.On("GetAll").Return([]string{key}, mockSdlRetOk).Twice()
+       mSdl.On("Get", []string{key}).Return(mockSdlGetRetVal, mockSdlRetNok).Once()
+
+       restHook := createResthook(true, mSdl)
+       assert.Equal(t, 0, len(restHook.subscriptions.Items()))
+}
+
 func TestTeardown(t *testing.T) {
+       var mockSdlRetOk error
+       mockedSdl.On("RemoveAll").Return(mockSdlRetOk).Once()
+
        rh.FlushSubscriptions()
 }
 
-func CreateSubscription(et models.EventType, maxRetries, retryTimer int64, targetUrl string) models.SubscriptionRequest {
+func createSubscription(et models.EventType, maxRetries, retryTimer int64, targetUrl string) models.SubscriptionRequest {
        return models.SubscriptionRequest{&models.SubscriptionData{et, &maxRetries, &retryTimer, &targetUrl}}
 }
+
+func getDummyXapp() models.Xapp {
+       return generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "service-ricxapp-dummy-xapp-rmr.ricxapp", "4560")
+}
+
+func generateXapp(name, status, ver, iname, istatus, ip, port string) (x models.Xapp) {
+       x.Name = &name
+       x.Status = status
+       x.Version = ver
+       p, _ := strconv.Atoi(port)
+       var msgs appmgr.RtmData
+
+       instance := &models.XappInstance{
+               Name:       &iname,
+               Status:     istatus,
+               IP:         ip,
+               Port:       int64(p),
+               TxMessages: msgs.TxMessages,
+               RxMessages: msgs.RxMessages,
+       }
+       x.Instances = append(x.Instances, instance)
+       return
+}
+
+func flushExistingSubscriptions() {
+       var mockSdlRetOk error
+       mockedSdl.On("RemoveAll").Return(mockSdlRetOk).Once()
+       rh.FlushSubscriptions()
+}
+
+func createHTTPServer(t *testing.T, method, url string, port, status int, respData interface{}) *httptest.Server {
+       l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
+       if err != nil {
+               t.Error("Failed to create listener: " + err.Error())
+       }
+       ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               assert.Equal(t, r.Method, method)
+               assert.Equal(t, r.URL.String(), url)
+               w.Header().Add("Content-Type", "application/json")
+               w.WriteHeader(status)
+               b, _ := json.Marshal(respData)
+               w.Write(b)
+       }))
+       ts.Listener.Close()
+       ts.Listener = l
+
+       ts.Start()
+       time.Sleep(time.Duration(1 * time.Second))
+       return ts
+}
+
+func (m *SdlMock) expectDbSet(t *testing.T, subsReq models.SubscriptionRequest, mockRet error) {
+       serializedSubReq, _ := json.Marshal(subsReq)
+       m.On("Set", mock.Anything).Run(
+               func(args mock.Arguments) {
+                       sdlKVs := args.Get(0).([]interface{})
+                       assert.Equal(t, 2, len(sdlKVs))
+                       //Validate that subscription request is set to SDL
+                       assert.Equal(t, serializedSubReq, sdlKVs[1])
+               }).Return(mockRet).Once()
+}
+
+type SdlMock struct {
+       mock.Mock
+}
+
+func (m *SdlMock) Set(pairs ...interface{}) error {
+       a := m.Called(pairs)
+       return a.Error(0)
+}
+
+func (m *SdlMock) Get(keys []string) (map[string]interface{}, error) {
+       a := m.Called(keys)
+       return a.Get(0).(map[string]interface{}), a.Error(1)
+}
+
+func (m *SdlMock) GetAll() ([]string, error) {
+       a := m.Called()
+       return a.Get(0).([]string), a.Error(1)
+}
+
+func (m *SdlMock) RemoveAll() error {
+       a := m.Called()
+       return a.Error(0)
+}
index 2229abe..f2af972 100755 (executable)
@@ -20,7 +20,6 @@
 package resthooks
 
 import (
-       sdl "gerrit.o-ran-sc.org/r/ric-plt/sdlgo"
        cmap "github.com/orcaman/concurrent-map"
        "net/http"
 
@@ -36,7 +35,7 @@ type SubscriptionInfo struct {
 type Resthook struct {
        client        *http.Client
        subscriptions cmap.ConcurrentMap
-       db            *sdl.SdlInstance
+       db            iSdl
        Seq           int64
 }
 
@@ -47,3 +46,10 @@ type SubscriptionNotification struct {
        Version int64  `json:"version,omitempty"`
        XApps   string `json:"xApps,omitempty"`
 }
+
+type iSdl interface {
+       Set(pairs ...interface{}) error
+       Get(keys []string) (map[string]interface{}, error)
+       GetAll() ([]string, error)
+       RemoveAll() error
+}
diff --git a/test/dummy-xapp_values.json b/test/dummy-xapp_values.json
new file mode 100644 (file)
index 0000000..7687f63
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "XappName": "dummy-xapp",
+  "ReleaseName": "dummy-xapp",
+  "HelmVersion": "1.0.0",
+  "Namespace": "ricxapp"
+}
diff --git a/test/faulty_schema.json b/test/faulty_schema.json
new file mode 100644 (file)
index 0000000..e69de29