From 451fce087f129eb200107c0cf63fd40ba40502e7 Mon Sep 17 00:00:00 2001 From: Bharath Sreekanth <93715158+bharathsreekanth@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:16:50 -0500 Subject: [PATCH] Usr/sreekb/goscaleio uts (#158) Unit tests coverage increase --- api/api_logging_test.go | 308 +++++++++ api_test.go | 44 +- compatibility_management_test.go | 118 ++++ deploy_test.go | 31 + device_test.go | 1006 ++++++++++++++++++++++++++++++ fault_set_test.go | 60 ++ instance.go | 2 +- instance_test.go | 494 +++++++++++++++ 8 files changed, 2056 insertions(+), 7 deletions(-) create mode 100644 api/api_logging_test.go create mode 100644 compatibility_management_test.go create mode 100644 device_test.go create mode 100644 instance_test.go diff --git a/api/api_logging_test.go b/api/api_logging_test.go new file mode 100644 index 0000000..6e4dfee --- /dev/null +++ b/api/api_logging_test.go @@ -0,0 +1,308 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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. +package api + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "strings" + "testing" + + log "github.com/sirupsen/logrus" +) + +func TestIsBinOctetBody(t *testing.T) { + tests := []struct { + name string + header http.Header + expected bool + }{ + { + name: "Content-Type is binary/octet-stream", + header: http.Header{ + "Content-Type": []string{"binary/octet-stream"}, + }, + expected: true, + }, + { + name: "Content-Type is application/json", + header: http.Header{ + "Content-Type": []string{"application/json"}, + }, + expected: false, + }, + { + name: "Content-Type is missing", + header: http.Header{}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Call the function with the test case's header + got := isBinOctetBody(tt.header) + + if got != tt.expected { + t.Errorf("isBinOctetBody() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestDumpRequest(t *testing.T) { + // Test case: Request with no body + req, err := http.NewRequest("GET", "http://example.com/instances", nil) + if err != nil { + t.Fatal(err) + } + b, err := dumpRequest(req, !isBinOctetBody(req.Header)) + if err != nil { + t.Fatal(err) + } + expected := "GET /instances HTTP/1.1\r\nHost: example.com\r\n\r\n" + if string(b) != expected { + t.Errorf("Expected %q, got %q", expected, string(b)) + } + + // Test case: Request with body + req, err = http.NewRequest("POST", "http://example.com/instances", strings.NewReader("Hello, world!")) + if err != nil { + t.Fatal(err) + } + b, err = dumpRequest(req, true) + if err != nil { + t.Fatal(err) + } + expected = "POST /instances HTTP/1.1\r\nHost: example.com\r\n\r\nHello, world!" + if string(b) != expected { + t.Errorf("Expected %q, got %q", expected, string(b)) + } + + // Test case: Request with chunked transfer encoding + req, err = http.NewRequest("PUT", "http://example.com/instances", strings.NewReader("Hello, world!")) + if err != nil { + t.Fatal(err) + } + req.TransferEncoding = []string{"chunked"} + b, err = dumpRequest(req, true) + if err != nil { + t.Fatal(err) + } + expected = "PUT /instances HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\nd\r\nHello, world!\r\n0\r\n\r\n" + if string(b) != expected { + t.Errorf("Expected %q, got %q", expected, string(b)) + } + + // Test case: Request with connection close + req, err = http.NewRequest("DELETE", "http://example.com/instances", nil) + if err != nil { + t.Fatal(err) + } + req.Close = true + b, err = dumpRequest(req, false) + if err != nil { + t.Fatal(err) + } + expected = "DELETE /instances HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" + if string(b) != expected { + t.Errorf("Expected %q, got %q", expected, string(b)) + } +} + +func TestWriteIndentedN(t *testing.T) { + // Test case: Empty input + var b bytes.Buffer + err := WriteIndentedN(&b, []byte{}, 4) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if b.String() != "" { + t.Errorf("Expected empty output, got: %s", b.String()) + } + + // Test case: Single line + b.Reset() + err = WriteIndentedN(&b, []byte("Hello, world!"), 4) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected := " Hello, world!" + if b.String() != expected { + t.Errorf("Expected: %s, got: %s", expected, b.String()) + } + + // Test case: Multiple lines + b.Reset() + err = WriteIndentedN(&b, []byte("Line 1\nLine 2\nLine 3"), 2) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + expected = " Line 1\n Line 2\n Line 3" + if b.String() != expected { + t.Errorf("Expected: %s, got: %s", expected, b.String()) + } +} + +func TestLogResponse(_ *testing.T) { + // Test case: Logging a successful response + res := &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{"Content-Type": []string{"application/json"}}, + Body: io.NopCloser(strings.NewReader(`{"message": "success"}`)), + } + logResponse(context.Background(), res, nil) + + // Test case: Logging a failed response + res = &http.Response{ + StatusCode: http.StatusInternalServerError, + Header: http.Header{"Content-Type": []string{"text/plain"}}, + Body: io.NopCloser(strings.NewReader("Internal server error")), + } + logResponse(context.Background(), res, nil) + + // Test case: Logging a response with binary content + res = &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{"Content-Type": []string{"application/octet-stream"}}, + Body: io.NopCloser(bytes.NewReader([]byte{0x01, 0x02, 0x03})), + } + logResponse(context.Background(), res, nil) +} + +func logChecker(level log.Level, msg string) func(func(args ...interface{}), string) { + return func(lf func(args ...interface{}), message string) { + if level != log.DebugLevel { + // If the log level is not DebugLevel, call the logging function with an error message + lf(fmt.Sprintf("Expected debug level, got %v", level)) + } + if !strings.Contains(msg, "GOSCALEIO HTTP REQUEST") { + // If the message does not contain the expected string, call the logging function with an error message + lf(fmt.Sprintf("Expected request log, got %s", msg)) + } + + // You can log the original message if needed + lf(message) + } +} + +func TestLogRequest(t *testing.T) { + // Test case: Valid request + req, err := http.NewRequest("GET", "https://example.com/instances", nil) + if err != nil { + t.Fatal(err) + } + logFunc := logChecker(log.DebugLevel, "GOSCALEIO HTTP REQUEST") + logRequest(context.Background(), req, logFunc) + + // Test case: Error in dumpRequest + req, err = http.NewRequest("GET", "https://example.com/instances", NewErrorReader(errors.New("simulated error while reading request body"))) + if err != nil { + t.Fatal(err) + } + _, err = io.ReadAll(req.Body) + if err == nil { + t.Fatalf("Expected error when reading request body, got nil") + } + + logRequest(context.Background(), req, logFunc) + + // Test case: Error in WriteIndented + req, err = http.NewRequest("GET", "https://example.com/instances", nil) + if err != nil { + t.Fatal(err) + } + + req.Header.Set(HeaderKeyContentType, "application/json") + + logRequest(context.Background(), req, logFunc) +} + +// errorReader is a custom io.Reader that always returns an error when Read is called. +type ErrorReader struct { + err error +} + +// NewErrorReader creates an instance of errorReader with the specified error. +func NewErrorReader(err error) *ErrorReader { + return &ErrorReader{err: err} +} + +// Read always returns an error (simulating a read failure). +func (r *ErrorReader) Read(_ []byte) (n int, err error) { + return 0, r.err +} + +func TestDrainBody(t *testing.T) { + // Test case: b is http.NoBody + r1, r2, err := drainBody(http.NoBody) + if r1 != http.NoBody { + t.Errorf("Expected r1 to be http.NoBody, got %v", r1) + } + if r2 != http.NoBody { + t.Errorf("Expected r2 to be http.NoBody, got %v", r2) + } + if err != nil { + t.Errorf("Expected err to be nil, got %v", err) + } + + // Test case: b is not http.NoBody + b := io.NopCloser(strings.NewReader("test")) + r1, r2, err = drainBody(b) + if r1 == http.NoBody { + t.Error("Expected r1 to not be http.NoBody") + } + if r2 == http.NoBody { + t.Error("Expected r2 to not be http.NoBody") + } + if err != nil { + t.Errorf("Expected err to be nil, got %v", err) + } + if _, err := r1.Read(make([]byte, 1)); err != nil { + t.Errorf("Expected r1 to be readable, got %v", err) + } + if _, err := r2.Read(make([]byte, 1)); err != nil { + t.Errorf("Expected r2 to be readable, got %v", err) + } + + // Test case: b.ReadFrom returns an error + b = io.NopCloser(&ErrorReader{}) + r1, r2, err = drainBody(b) + if r1 != nil { + t.Errorf("Expected r1 to be nil, got %v", r1) + } + if r2 == nil { + t.Errorf("Expected r2 to be not nil, got %v", r2) + } + if err == nil { + t.Error("Expected err to not be nil") + } + + // Test case: b.Close returns an error + b = io.NopCloser(strings.NewReader("test")) + r1, r2, err = drainBody(b) + if r1 == nil { + t.Error("Expected r1 to not be nil") + } + if r2 == nil { + t.Error("Expected r2 to not be nil") + } + if err != nil { + t.Error("Expected err to be nil") + } +} diff --git a/api_test.go b/api_test.go index d9b2f5b..18a6d54 100644 --- a/api_test.go +++ b/api_test.go @@ -148,6 +148,38 @@ func TestClientVersion(t *testing.T) { if ver != "4.0" { t.Fatal("Expecting version string \"4.0\", got ", ver) } + + // Test unauthorized authentication + _, err = client.Authenticate(&ConfigConnect{ + Username: "ScaleIOUser", + Password: "badpassword", + Endpoint: "", + Version: "4.0", + }) + if err == nil { + t.Fatal(err) + } + + // Test for version retrieval with retry + _, err = client.GetVersion() + if err != nil { + // Check if the error is due to unauthorized access + if strings.Contains(err.Error(), "Unauthorized") { + // retry + _, err = client.Authenticate(&ConfigConnect{ + Username: "ScaleIOUser", + Password: "password", + Endpoint: "", + Version: "4.0", + }) + if err != nil { + t.Fatal(err) + } + return + } + // If error is not due to unauthorized access, fail the test + t.Fatal(err) + } } func TestClientLogin(t *testing.T) { @@ -158,15 +190,15 @@ func TestClientLogin(t *testing.T) { resp.WriteHeader(http.StatusOK) resp.Write([]byte(`"2.0"`)) case "/api/login": - //accept := req.Header.Get("Accept") + // accept := req.Header.Get("Accept") // check Accept header - //if ver := strings.Split(accept, ";"); len(ver) != 2 { + // if ver := strings.Split(accept, ";"); len(ver) != 2 { // t.Fatal("Expecting Accept header to include version") - //} else { + // } else { // if !strings.HasPrefix(ver[1], "version=") { // t.Fatal("Header Accept must include version") - // } - //} + // } + // } uname, pwd, basic := req.BasicAuth() if !basic { @@ -273,7 +305,7 @@ func Test_updateHeaders(_ *testing.T) { wg.Wait() } -func Test_getJSONWithRetry(t *testing.T) { +func TestGetJSONWithRetry(t *testing.T) { t.Run("retried request is similar to the original", func(t *testing.T) { var ( paths []string // record the requested paths in order. diff --git a/compatibility_management_test.go b/compatibility_management_test.go new file mode 100644 index 0000000..37d7c1d --- /dev/null +++ b/compatibility_management_test.go @@ -0,0 +1,118 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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. + +package goscaleio + +import ( + "encoding/json" + "math" + "net/http" + "net/http/httptest" + "testing" + + types "github.com/dell/goscaleio/types/v1" +) + +func mockCompatibilityTestServerHandler(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case "/api/v1/Compatibility": + if req.Method == http.MethodGet { + resp.WriteHeader(http.StatusOK) + response := types.CompatibilityManagement{ + ID: "mock-compatibility-system-id", + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + } else if req.Method == http.MethodPost { + resp.WriteHeader(http.StatusOK) + response := types.CompatibilityManagement{ + ID: "mock-compatibility-management-id", + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + } + } +} + +func TestGetCompatibility(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockCompatibilityTestServerHandler)) + defer mockServer.Close() + tests := []struct { + name string + error string + }{ + { + name: "compatibility management success", + error: "", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + + _, err = s.GetCompatibilityManagement() + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestSetCompatibility(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockCompatibilityTestServerHandler)) + defer mockServer.Close() + tests := []struct { + name string + compatibilitymanagement types.CompatibilityManagementPost + error string + }{ + { + name: "compatibility management success", + compatibilitymanagement: types.CompatibilityManagementPost{ + ID: "mock-compatibility-management-id", + }, + error: "", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "4.0", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + + _, err = s.SetCompatibilityManagement(&tc.compatibilitymanagement) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} diff --git a/deploy_test.go b/deploy_test.go index 827b093..66e6d76 100644 --- a/deploy_test.go +++ b/deploy_test.go @@ -54,6 +54,37 @@ func TestNewGateway(t *testing.T) { assert.Equal(t, "4.0", gc.version, "Unexpected version") } +func TestNewGatewayInsecure(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && r.URL.Path == "/rest/auth/login" { + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, `{"access_token":"mock_access_token"}`) + return + } + if r.Method == http.MethodGet && r.URL.Path == "/api/version" { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, "4.0") + return + } + http.NotFound(w, r) + })) + + defer server.Close() + + gc, err := NewGateway(server.URL, "test_username", "test_password", true, false) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + assert.Nil(t, err, "Unexpected error") + assert.NotNil(t, gc, "GatewayClient is nil") + assert.Equal(t, "mock_access_token", gc.token, "Unexpected access token") + assert.Equal(t, "4.0", gc.version, "Unexpected version") +} + // TestGetVersion tests the GetVersion function. func TestGetVersion(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/device_test.go b/device_test.go new file mode 100644 index 0000000..902f3ae --- /dev/null +++ b/device_test.go @@ -0,0 +1,1006 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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. + +package goscaleio + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "net/http" + "net/http/httptest" + "strings" + "testing" + + types "github.com/dell/goscaleio/types/v1" +) + +func TestGetAllDevices(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "get all devices success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case "/api/types/Device/instances": + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1"}, + {ID: "mock-device-id-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "get one device success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + deviceID := "mock-device-id-1" + switch req.RequestURI { + case fmt.Sprintf("/api/instances/Sds::%v/relationships/Device", deviceID): + resp.WriteHeader(http.StatusOK) + response := types.Device{ + ID: "1", + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "get device by field success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case "/api/types/Device/instances": + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1"}, + {ID: "mock-device-id-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + for _, tc := range cases { + client, err := NewClientWithArgs(tc.server.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + + _, err = s.GetAllDevice() + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + + tc.server.Close() + } +} + +func TestGetDevice(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "get one device success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + deviceID := "mock-device-id-1" + switch req.RequestURI { + case fmt.Sprintf("/api/instances/Sds::%v/relationships/Device", deviceID): + resp.WriteHeader(http.StatusOK) + response := types.Device{ + ID: "mock-device-id-1", + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + for _, tc := range cases { + client, err := NewClientWithArgs(tc.server.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + + _, err = s.GetDevice("1") + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + + tc.server.Close() + } +} + +func TestGetDeviceByField(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "get device by field success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case "/api/types/Device/instances": + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "get device by field failure": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case "/api/types/Device/instances": + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: errors.New("couldn't find device"), + }, + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + deviceFields := map[string]map[string]string{ + "get device by field success": {"ID": "mock-device-id-1", "Name": "mock-device-name-1"}, + "get device by field failure": {"Name": "mock-device-name-invalid"}, + "bad request": {}, + } + + for id, tc := range cases { + t.Run(id, func(t *testing.T) { + client, err := NewClientWithArgs(tc.server.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "4.0" + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + + temp := deviceFields[id] + for fieldKey, fieldValue := range temp { + _, err = s.GetDeviceByField(fieldKey, fieldValue) + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + } + }) + } +} + +func TestSDSFindDevice(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "get device by field success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case fmt.Sprintf("/api/instances/Sds::%v/relationships/Device", "mock-sds-id"): + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "get device by field failure": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case fmt.Sprintf("/api/instances/Sds::%v/relationships/Device", "mock-sds-name-invalid"): + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: errors.New("couldn't find device"), + }, + + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + deviceFields := map[string]map[string]string{ + "get device by field success": {"ID": "mock-device-id-1", "Name": "mock-device-name-1"}, + "get device by field error": {"ID": "mock-device-id-invalid", "Name": "mock-device-name-invalid"}, + "bad request": {}, + } + + for id, tc := range cases { + t.Run(id, func(t *testing.T) { + client, err := NewClientWithArgs(tc.server.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "4.0" + if err != nil { + t.Fatal(err) + } + + sds := NewSds(client) + sds.Sds.Name = "mock-sds-name" + sds.Sds.ID = "mock-sds-id" + for fieldKey, fieldValue := range deviceFields[id] { + _, err = sds.FindDevice(fieldKey, fieldValue) + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + } + }) + } +} + +func TestSDSGetDevice(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "get device by field success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case fmt.Sprintf("/api/instances/Sds::%v/relationships/Device", "mock-device-id-1"): + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + for id, tc := range cases { + t.Run(id, func(t *testing.T) { + client, err := NewClientWithArgs(tc.server.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "4.0" + if err != nil { + t.Fatal(err) + } + + sds := NewSds(client) + sds.Sds.Name = "mock-sds-name" + sds.Sds.ID = "mock-sds-id" + _, err = sds.GetDevice() + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + }) + } +} + +func TestStoragePoolGetDevice(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "get device by field success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case fmt.Sprintf("/api/instances/StoragePool::%v/relationships/Device", "mock-storage-pool-name"): + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + for id, tc := range cases { + t.Run(id, func(t *testing.T) { + client, err := NewClientWithArgs(tc.server.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "4.0" + if err != nil { + t.Fatal(err) + } + + pool := NewStoragePool(client) + pool.StoragePool.Name = "mock-storage-pool-name" + + _, err = pool.GetDevice() + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + }) + } +} + +func TestStoragePoolFindDevice(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "get device by field success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case fmt.Sprintf("/api/instances/StoragePool::%v/relationships/Device", "mock-storage-pool-id"): + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "get device by field failure": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case fmt.Sprintf("/api/instances/StoragePool::%v/relationships/Device", "InvalidStoragePoolID"): + resp.WriteHeader(http.StatusOK) + response := []types.Device{ + {ID: "mock-device-id-1", Name: "mock-device-name-1"}, + {ID: "mock-device-id-2", Name: "mock-device-name-2"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: errors.New("couldn't find device"), + }, + + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + deviceFields := map[string]map[string]string{ + "get device by field success": {"ID": "mock-device-id-1", "Name": "mock-device-name-1"}, + "get device by field error": {"ID": "mock-device-id-invalid", "Name": "mock-device-name-invalid"}, + "bad request": {}, + } + + for id, tc := range cases { + t.Run(id, func(t *testing.T) { + client, err := NewClientWithArgs(tc.server.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "4.0" + if err != nil { + t.Fatal(err) + } + + pool := NewStoragePool(client) + pool.StoragePool.Name = "mock-storage-pool-name" + pool.StoragePool.ID = "mock-storage-pool-id" + for fieldKey, fieldValue := range deviceFields[id] { + _, err = pool.FindDevice(fieldKey, fieldValue) + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + } + }) + } +} + +func TestStoragePoolSetDeviceName(t *testing.T) { + type checkFn func(*testing.T, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, types.System, []checkFn){ + "success": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + mockDeviceID := "mock-device-id" + url := fmt.Sprintf("/api/instances/Device::%v/action/setDeviceName", mockDeviceID) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + return + } + http.NotFound(w, r) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasNoError) + }, + "error response": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Server Error", http.StatusInternalServerError) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, _, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + storagePool := NewStoragePool(client) + err := storagePool.SetDeviceName("mock-device-id", "mock-device-name") + + for _, checkFn := range checkFns { + checkFn(t, err) + } + }) + } +} + +func TestStoragePoolSetMediaType(t *testing.T) { + type checkFn func(*testing.T, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, types.System, []checkFn){ + "success": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + deviceID := "mock-device-id" + url := fmt.Sprintf("/api/instances/Device::%v/action/setMediaType", deviceID) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + return + } + http.NotFound(w, r) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasNoError) + }, + "error response": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Server Error", http.StatusInternalServerError) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, _, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + storagePool := NewStoragePool(client) + err := storagePool.SetDeviceMediaType("mock-device-id", "mock-media-type") + + for _, checkFn := range checkFns { + checkFn(t, err) + } + }) + } +} + +func TestStoragePoolSetDeviceExternalAccelerationType(t *testing.T) { + type checkFn func(*testing.T, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, types.System, []checkFn){ + "success": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + deviceID := "mock-device-id" + url := fmt.Sprintf("/api/instances/Device::%v/action/setExternalAccelerationType", deviceID) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + return + } + http.NotFound(w, r) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasNoError) + }, + "error response": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Server Error", http.StatusInternalServerError) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, _, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + storagePool := NewStoragePool(client) + err := storagePool.SetDeviceExternalAccelerationType("mock-device-id", "mock-acceleration-type") + + for _, checkFn := range checkFns { + checkFn(t, err) + } + }) + } +} + +func TestStoragePoolSetDeviceCapacityLimit(t *testing.T) { + type checkFn func(*testing.T, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, types.System, []checkFn){ + "success": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + deviceID := "mock-device-id" + url := fmt.Sprintf("/api/instances/Device::%v/action/setDeviceCapacityLimit", deviceID) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + return + } + http.NotFound(w, r) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasNoError) + }, + "error response": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Server Error", http.StatusInternalServerError) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, _, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + storagePool := NewStoragePool(client) + err := storagePool.SetDeviceCapacityLimit("mock-device-id", "100G") + + for _, checkFn := range checkFns { + checkFn(t, err) + } + }) + } +} + +func TestStoragePoolUpdateDeviceOriginalPathways(t *testing.T) { + type checkFn func(*testing.T, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, types.System, []checkFn){ + "success": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + deviceID := "mock-device-id" + url := fmt.Sprintf("/api/instances/Device::%v/action/updateDeviceOriginalPathname", deviceID) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + return + } + http.NotFound(w, r) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasNoError) + }, + "error response": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Server Error", http.StatusInternalServerError) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, _, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + storagePool := NewStoragePool(client) + err := storagePool.UpdateDeviceOriginalPathways("mock-device-id") + + for _, checkFn := range checkFns { + checkFn(t, err) + } + }) + } +} + +func TestStoragePoolRemoveDevice(t *testing.T) { + type checkFn func(*testing.T, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, types.System, []checkFn){ + "success": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + deviceID := "mock-device-id" + url := fmt.Sprintf("/api/instances/Device::%v/action/removeDevice", deviceID) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + return + } + http.NotFound(w, r) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasNoError) + }, + "error response": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Server Error", http.StatusInternalServerError) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, _, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + storagePool := NewStoragePool(client) + err := storagePool.RemoveDevice("mock-device-id") + + for _, checkFn := range checkFns { + checkFn(t, err) + } + }) + } +} + +func TestStoragePoolAttachDevice(t *testing.T) { + type checkFn func(*testing.T, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, types.System, []checkFn){ + "success": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + url := "/api/types/Device/instances" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + return + } + http.NotFound(w, r) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasNoError) + }, + "error response": func(_ *testing.T) (*httptest.Server, types.System, []checkFn) { + systemID := "mock-system-id" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "Server Error", http.StatusInternalServerError) + })) + system := types.System{ + ID: systemID, + } + return server, system, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, _, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + storagePool := NewStoragePool(client) + deviceParam := &types.DeviceParam{ + Name: "mock-device-name", + } + _, err := storagePool.AttachDevice(deviceParam) + + for _, checkFn := range checkFns { + checkFn(t, err) + } + }) + } +} diff --git a/fault_set_test.go b/fault_set_test.go index ca61397..7a602bd 100644 --- a/fault_set_test.go +++ b/fault_set_test.go @@ -13,6 +13,7 @@ package goscaleio import ( + "encoding/json" "errors" "math" "net/http" @@ -25,6 +26,7 @@ import ( var ( ID string + Name string errFs error ) @@ -328,3 +330,61 @@ func TestGetAllFaultSetsSds(t *testing.T) { }) } } + +func TestGetFaultSetByName(t *testing.T) { + type testCase struct { + server *httptest.Server + expectedErr error + } + + cases := map[string]testCase{ + "success": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case "/api/types/FaultSet/instances": + resp.WriteHeader(http.StatusOK) + response := []types.FaultSet{ + {ID: "mock-fault-set-id", Name: "mock-fault-set-name"}, + } + + content, err := json.Marshal(response) + if err != nil { + t.Fatal(err) + } + + resp.Write(content) + default: + resp.WriteHeader(http.StatusNoContent) + } + })), + expectedErr: nil, + }, + "bad request": { + server: httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, _ *http.Request) { + resp.WriteHeader(http.StatusBadRequest) + resp.Write([]byte(`{"message":"bad request","httpStatusCode":400,"errorCode":0}`)) + })), + expectedErr: errors.New("bad request"), + }, + } + + for id, tc := range cases { + t.Run(id, func(t *testing.T) { + client, err := NewClientWithArgs(tc.server.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "4.0" + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + _, err = s.GetFaultSetByName("mock-fault-set-name") + if err != nil { + if tc.expectedErr.Error() != err.Error() { + t.Fatal(err) + } + } + }) + } +} diff --git a/instance.go b/instance.go index f1e8b37..2a1ffc2 100644 --- a/instance.go +++ b/instance.go @@ -114,7 +114,7 @@ func (c *Client) FindVolumeID(volumename string) (string, error) { Name: volumename, } - path := fmt.Sprintf("/api/types/Volume/instances/action/queryIdByKey") + path := "/api/types/Volume/instances/action/queryIdByKey" volumeID, err := c.getStringWithRetry(http.MethodPost, path, volumeQeryIDByKeyParam) diff --git a/instance_test.go b/instance_test.go new file mode 100644 index 0000000..66d773f --- /dev/null +++ b/instance_test.go @@ -0,0 +1,494 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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. + +package goscaleio + +import ( + "encoding/json" + "fmt" + "math" + "net/http" + "net/http/httptest" + "testing" + + types "github.com/dell/goscaleio/types/v1" +) + +func mockInstanceServerHandler(resp http.ResponseWriter, req *http.Request) { + switch req.RequestURI { + case "/api/types/System/instances": + if req.Method == http.MethodGet { + resp.WriteHeader(http.StatusOK) + response := []types.System{ + { + ID: "mock-system-id", + Name: "mock-system-name", + }, + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + } + case "/api/types/Volume/instances/action/queryIdByKey": + resp.WriteHeader(http.StatusOK) + response := "mock-volume-id" + + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + + resp.Write(content) + case fmt.Sprintf("/api/instances/Volume::%s", "mock-volume-id"): + resp.WriteHeader(http.StatusOK) + response := types.Volume{ + ID: "mock-volume-id", + Name: "mock-volume-name", + AncestorVolumeID: "mock-ancestore-volume-id", + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + + resp.Write(content) + case "/api/types/Volume/instances": + if req.Method == http.MethodGet { + resp.WriteHeader(http.StatusOK) + response := []types.Volume{ + {ID: "mock-volume-id"}, + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + } else if req.Method == http.MethodPost { + resp.WriteHeader(http.StatusOK) + response := types.VolumeResp{ + ID: "mock-volume-id", + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + } + case "/api/types/StoragePool/instances": + resp.WriteHeader(http.StatusOK) + response := []types.StoragePool{ + { + ID: "mock-storage-pool-id", + Name: "mock-storage-pool-name", + ProtectionDomainID: "mock-protection-domain-id", + }, + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + case fmt.Sprintf("/api/instances/StoragePool::%s", "mock-storage-pool-id"): + resp.WriteHeader(http.StatusOK) + response := types.StoragePool{ + ID: "mock-storage-pool-id", + Name: "mock-storage-pool-name", + ProtectionDomainID: "mock-protection-domain-id", + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + case fmt.Sprintf("/api/instances/StoragePool::%s/relationships/Volume", "mock-storage-pool-id"): + resp.WriteHeader(http.StatusOK) + response := []types.Volume{ + { + Name: "mock-volume-name-1", + ID: "mock-volume-id-1", + StoragePoolID: "mock-storage-pool-id", + VTreeID: "mock-vtree-id", + }, + { + Name: "mock-volume-name-2", + ID: "mock-volume-id-2", + StoragePoolID: "mock-storage-pool-id-2", + VTreeID: "mock-vtree-id", + }, + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + case "/api/types/SnapshotPolicy/instances/action/queryIdByKey": + resp.WriteHeader(http.StatusOK) + response := "mock-snapshot-policy-id" + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + case fmt.Sprintf("/api/instances/SnapshotPolicy::%s", "mock-snapshot-policy-id"): + resp.WriteHeader(http.StatusOK) + response := types.SnapshotPolicy{ + ID: "mock-snapshot-policy-id", + Name: "mock-snapshot-policy-name", + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + case "/api/types/SnapshotPolicy/instances": + resp.WriteHeader(http.StatusOK) + response := []types.SnapshotPolicy{ + { + ID: "mock-snapshot-policy-id", + Name: "mock-snapshot-policy-name", + }, + } + content, err := json.Marshal(response) + if err != nil { + http.Error(resp, err.Error(), http.StatusNotFound) + } + resp.Write(content) + + default: + resp.WriteHeader(http.StatusNoContent) + } +} + +func TestGetInstance(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + tests := []struct { + name string + systemhref string + error string + }{ + { + name: "system href valid", + systemhref: "/api/instances/System::mock-system-id", + error: "", + }, + { + name: "system href null", + systemhref: "", + error: "", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + _, err = client.GetInstance(tc.systemhref) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestGetVolume(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + + tests := []struct { + name string + volumeid string + volumename string + volumehref string + ancestorevolumeid string + snapshots bool + error string + }{ + { + name: "get volume name not null", + volumeid: "", + volumehref: "", + ancestorevolumeid: "mock-ancestor-volume-id", + volumename: "mock-volume-name", + snapshots: false, + error: "volume not found", + }, + { + name: "get volume id not null volume name and href null", + volumeid: "mock-volume-id", + volumehref: "", + ancestorevolumeid: "mock-ancestor-volume-id", + volumename: "", + snapshots: false, + error: "", + }, + { + name: "get volume volume id href and volume name null", + volumeid: "", + volumehref: "", + ancestorevolumeid: "mock-ancestor-volume-id", + volumename: "", + snapshots: false, + error: "", + }, + { + name: "get volume same id and ancestor volume id", + volumeid: "", + volumehref: "/api/types/Volume/instances", + ancestorevolumeid: "mock-volume-id", + volumename: "mock-volume-name", + snapshots: false, + error: "volume not found", + }, + { + name: "get volume same id and ancestor volume id", + volumeid: "", + volumehref: "", + ancestorevolumeid: "mock-volume-id", + volumename: "mock-volume-name", + snapshots: true, + error: "volume not found", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.GetVolume(tc.volumehref, tc.volumeid, tc.ancestorevolumeid, tc.volumename, tc.snapshots) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestGetStoragePool(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + + tests := []struct { + name string + storagepoolhref string + error string + }{ + { + name: "storage pool valid", + storagepoolhref: "/api/instances/StoragePool::mock-storage-pool-id", + error: "storage pool not found", + }, + { + name: "get volume valid and href null", + storagepoolhref: "", + error: "", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.GetStoragePool(tc.storagepoolhref) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestFindStoragePool(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + + tests := []struct { + name string + poolid string + poolname string + storagepoolhref string + protectiondomainid string + error string + }{ + { + name: "storage pool valid", + poolid: "mock-storage-pool-id", + poolname: "mock-storage-pool-name", + protectiondomainid: "mock-protection-domain-id", + storagepoolhref: "", + error: "", + }, + { + name: "storage pool valid href valid", + poolid: "mock-storage-pool-id", + poolname: "mock-storage-pool-name", + protectiondomainid: "mock-protection-domain-id", + storagepoolhref: "/api/instances/StoragePool::mock-storage-pool-id", + error: "", + }, + { + name: "storage pool invalid", + poolid: "mock-storage-pool-invalid-id", + poolname: "mock-storage-pool-invalid-name", + protectiondomainid: "mock-protection-domain", + storagepoolhref: "", + error: "Couldn't find storage pool", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + _, err = client.FindStoragePool(tc.poolid, tc.poolname, tc.storagepoolhref, tc.protectiondomainid) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestGetStoragePoolVolumes(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + + tests := []struct { + name string + storagepoolid string + error string + }{ + { + name: "storage pool valid", + storagepoolid: "mock-storage-pool-id", + error: "", + }, + { + name: "storage pool invalid", + storagepoolid: "mock-storage-pool-id-invalid", + error: "storage pool not found", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.GetStoragePoolVolumes(tc.storagepoolid) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestCreateVolume(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + + tests := []struct { + name string + volumeparam *types.VolumeParam + protectiondomain string + storagepoolname string + error string + }{ + { + name: "valid volume create", + volumeparam: &types.VolumeParam{ + Name: "mock-volume-name", + VolumeSizeInKb: "1024", + StoragePoolID: "mock-storage-pool-id", + ProtectionDomainID: "mock-protection-domain-id", + }, + storagepoolname: "mock-storage-pool-name", + protectiondomain: "mock-protection-domain-id", + error: "", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.CreateVolume(tc.volumeparam, tc.storagepoolname, tc.protectiondomain) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestGetSnapshotPolicyI(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + + tests := []struct { + name string + snapshotpolicyid string + snapshotpolicyname string + error string + }{ + { + name: "snapshot policy name empty", + snapshotpolicyid: "mock-snapshot-policy-id", + snapshotpolicyname: "", + error: "", + }, + { + name: "snapshot policy d empty", + snapshotpolicyid: "", + snapshotpolicyname: "mock-snapshot-policy-name", + error: "", + }, + } + + for _, tc := range tests { + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.GetSnapshotPolicy(tc.snapshotpolicyname, tc.snapshotpolicyid) + if err != nil { + if tc.error != err.Error() { + t.Fatal(err) + } + } + } +} + +func TestNewSnapshotPolicyI(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(mockInstanceServerHandler)) + defer mockServer.Close() + + client, err := NewClientWithArgs(mockServer.URL, "3.6", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + _ = NewSnapshotPolicy(client) +}