Adds PUT api to v2/projects 92/110392/5
authorLarry Sachs <larry.j.sachs@intel.com>
Tue, 21 Jul 2020 05:46:49 +0000 (22:46 -0700)
committerLarry Sachs <larry.j.sachs@intel.com>
Fri, 24 Jul 2020 21:05:40 +0000 (14:05 -0700)
Add functionality to support the PUT api for v2/projects/{project-name}
Also add unit tests for the PUT api

Issue-ID: MULTICLOUD-1130
Change-Id: Ia271569c5f0dec3152952e64171fd5a182aaa333
Signed-off-by: Larry Sachs <larry.j.sachs@intel.com>
src/orchestrator/api/api.go
src/orchestrator/api/projecthandler.go
src/orchestrator/api/projecthandler_test.go
src/orchestrator/examples/example_module.go
src/orchestrator/pkg/module/project.go
src/orchestrator/pkg/module/project_test.go

index 097b9ab..03afee2 100644 (file)
@@ -56,6 +56,7 @@ func NewRouter(projectClient moduleLib.ProjectManager,
                client: ControllerClient,
        }
        router.HandleFunc("/projects", projHandler.createHandler).Methods("POST")
+       router.HandleFunc("/projects/{project-name}", projHandler.updateHandler).Methods("PUT")
        router.HandleFunc("/projects/{project-name}", projHandler.getHandler).Methods("GET")
        router.HandleFunc("/projects", projHandler.getHandler).Methods("GET")
        router.HandleFunc("/projects/{project-name}", projHandler.deleteHandler).Methods("DELETE")
index 09b2409..83211b6 100644 (file)
@@ -54,7 +54,7 @@ func (h projectHandler) createHandler(w http.ResponseWriter, r *http.Request) {
                return
        }
 
-       ret, err := h.client.CreateProject(p)
+       ret, err := h.client.CreateProject(p, false)
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
@@ -69,6 +69,50 @@ func (h projectHandler) createHandler(w http.ResponseWriter, r *http.Request) {
        }
 }
 
+// Update handles updating the Project entry in the database
+func (h projectHandler) updateHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       name := vars["project-name"]
+
+       var p moduleLib.Project
+
+       err := json.NewDecoder(r.Body).Decode(&p)
+       switch {
+       case err == io.EOF:
+               http.Error(w, "Empty body", http.StatusBadRequest)
+               return
+       case err != nil:
+               http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+               return
+       }
+
+       // Name is required.
+       if p.MetaData.Name == "" {
+               http.Error(w, "Missing name in PUT request", http.StatusBadRequest)
+               return
+       }
+
+       // Name in URL should match name in body
+       if p.MetaData.Name != name {
+               http.Error(w, "Mismatched name in PUT request", http.StatusBadRequest)
+               return
+       }
+
+       ret, err := h.client.CreateProject(p, true)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusOK)
+       err = json.NewEncoder(w).Encode(ret)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
 // Get handles GET operations on a particular Project Name
 // Returns a Project
 func (h projectHandler) getHandler(w http.ResponseWriter, r *http.Request) {
index dae87e2..6810099 100644 (file)
@@ -40,7 +40,7 @@ type mockProjectManager struct {
        Err   error
 }
 
-func (m *mockProjectManager) CreateProject(inp moduleLib.Project) (moduleLib.Project, error) {
+func (m *mockProjectManager) CreateProject(inp moduleLib.Project, exists bool) (moduleLib.Project, error) {
        if m.Err != nil {
                return moduleLib.Project{}, m.Err
        }
@@ -144,6 +144,99 @@ func TestProjectCreateHandler(t *testing.T) {
        }
 }
 
+func TestProjectUpdateHandler(t *testing.T) {
+       testCases := []struct {
+               label, name   string
+               reader        io.Reader
+               expected      moduleLib.Project
+               expectedCode  int
+               projectClient *mockProjectManager
+       }{
+               {
+                       label: "Missing Project Name in Request Body",
+                       name:  "testProject",
+                       reader: bytes.NewBuffer([]byte(`{
+                               "description":"test description"
+                               }`)),
+                       expectedCode:  http.StatusBadRequest,
+                       projectClient: &mockProjectManager{},
+               },
+               {
+                       label:         "Missing Body Failure",
+                       name:          "testProject",
+                       expectedCode:  http.StatusBadRequest,
+                       projectClient: &mockProjectManager{},
+               },
+               {
+                       label:        "Mismatched Name Failure",
+                       name:         "testProject",
+                       expectedCode: http.StatusBadRequest,
+                       reader: bytes.NewBuffer([]byte(`{
+                               "metadata" : {
+                                       "name": "testProjectNameMismatch",
+                                       "description": "Test Project used for unit testing"
+                               }
+                       }`)),
+                       projectClient: &mockProjectManager{},
+               },
+               {
+                       label:        "Update Project",
+                       name:         "testProject",
+                       expectedCode: http.StatusOK,
+                       reader: bytes.NewBuffer([]byte(`{
+                               "metadata" : {
+                                       "name": "testProject",
+                                       "description": "Test Project used for unit testing"
+                               }
+                       }`)),
+                       expected: moduleLib.Project{
+                               MetaData: moduleLib.ProjectMetaData{
+                                       Name:        "testProject",
+                                       Description: "Test Project used for unit testing",
+                                       UserData1:   "update data1",
+                                       UserData2:   "update data2",
+                               },
+                       },
+                       projectClient: &mockProjectManager{
+                               //Items that will be returned by the mocked Client
+                               Items: []moduleLib.Project{
+                                       {
+                                               MetaData: moduleLib.ProjectMetaData{
+                                                       Name:        "testProject",
+                                                       Description: "Test Project used for unit testing",
+                                                       UserData1:   "update data1",
+                                                       UserData2:   "update data2",
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       request := httptest.NewRequest("PUT", "/v2/projects/"+testCase.name, testCase.reader)
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil))
+
+                       //Check returned code
+                       if resp.StatusCode != testCase.expectedCode {
+                               t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+                       }
+
+                       //Check returned body only if statusOK
+                       if resp.StatusCode == http.StatusOK {
+                               got := moduleLib.Project{}
+                               json.NewDecoder(resp.Body).Decode(&got)
+
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("updateHandler returned unexpected body: got %v;"+
+                                               " expected %v", got, testCase.expected)
+                               }
+                       }
+               })
+       }
+}
+
 func TestProjectGetHandler(t *testing.T) {
 
        testCases := []struct {
index 9138b08..7edbb75 100644 (file)
@@ -31,7 +31,14 @@ func ExampleClient_Project() {
                return
        }
        // Perform operations on Project Module
-       _, err := c.Project.CreateProject(moduleLib.Project{MetaData: moduleLib.ProjectMetaData{Name: "test", Description: "test", UserData1: "userData1", UserData2: "userData2"}})
+       // POST request (exists == false)
+       _, err := c.Project.CreateProject(moduleLib.Project{MetaData: moduleLib.ProjectMetaData{Name: "test", Description: "test", UserData1: "userData1", UserData2: "userData2"}}, false)
+       if err != nil {
+               log.Println(err)
+               return
+       }
+       // PUT request (exists == true)
+       _, err = c.Project.CreateProject(moduleLib.Project{MetaData: moduleLib.ProjectMetaData{Name: "test", Description: "test", UserData1: "userData1", UserData2: "userData2"}}, true)
        if err != nil {
                log.Println(err)
                return
index 02f6d82..e86266b 100644 (file)
@@ -55,7 +55,7 @@ func (pk ProjectKey) String() string {
 
 // ProjectManager is an interface exposes the Project functionality
 type ProjectManager interface {
-       CreateProject(pr Project) (Project, error)
+       CreateProject(pr Project, exists bool) (Project, error)
        GetProject(name string) (Project, error)
        DeleteProject(name string) error
        GetAllProjects() ([]Project, error)
@@ -78,7 +78,7 @@ func NewProjectClient() *ProjectClient {
 }
 
 // CreateProject a new collection based on the project
-func (v *ProjectClient) CreateProject(p Project) (Project, error) {
+func (v *ProjectClient) CreateProject(p Project, exists bool) (Project, error) {
 
        //Construct the composite key to select the entry
        key := ProjectKey{
@@ -87,7 +87,7 @@ func (v *ProjectClient) CreateProject(p Project) (Project, error) {
 
        //Check if this Project already exists
        _, err := v.GetProject(p.MetaData.Name)
-       if err == nil {
+       if err == nil && !exists {
                return Project{}, pkgerrors.New("Project already exists")
        }
 
index f6856f8..947478b 100644 (file)
@@ -62,13 +62,39 @@ func TestCreateProject(t *testing.T) {
                                Err: pkgerrors.New("Error Creating Project"),
                        },
                },
+               {
+                       label: "Create Existing Project",
+                       inp: Project{
+                               MetaData: ProjectMetaData{
+                                       Name:        "testProject",
+                                       Description: "A sample Project used for unit testing",
+                                       UserData1:   "data1",
+                                       UserData2:   "data2",
+                               },
+                       },
+                       expectedError: "Project already exists",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       ProjectKey{ProjectName: "testProject"}.String(): {
+                                               "projectmetadata": []byte(
+                                                       "{" +
+                                                               "\"metadata\" : {" +
+                                                               "\"Name\":\"testProject\"," +
+                                                               "\"Description\":\"Test project for unit testing\"," +
+                                                               "\"UserData1\":\"userData1\"," +
+                                                               "\"UserData2\":\"userData2\"}" +
+                                                               "}"),
+                                       },
+                               },
+                       },
+               },
        }
 
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        db.DBconn = testCase.mockdb
                        impl := NewProjectClient()
-                       got, err := impl.CreateProject(testCase.inp)
+                       got, err := impl.CreateProject(testCase.inp, false)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Create returned an unexpected error %s", err)
@@ -86,6 +112,85 @@ func TestCreateProject(t *testing.T) {
        }
 }
 
+func TestUpdateProject(t *testing.T) {
+       testCases := []struct {
+               label         string
+               inp           Project
+               expectedError string
+               mockdb        *db.MockDB
+               expected      Project
+       }{
+               {
+                       label: "Update Project",
+                       inp: Project{
+                               MetaData: ProjectMetaData{
+                                       Name:        "testProject",
+                                       Description: "Test project for unit testing",
+                                       UserData1:   "update userData1",
+                                       UserData2:   "update userData2",
+                               },
+                       },
+                       expected: Project{
+                               MetaData: ProjectMetaData{
+                                       Name:        "testProject",
+                                       Description: "Test project for unit testing",
+                                       UserData1:   "update userData1",
+                                       UserData2:   "update userData2",
+                               },
+                       },
+                       expectedError: "",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       ProjectKey{ProjectName: "testProject"}.String(): {
+                                               "projectmetadata": []byte(
+                                                       "{" +
+                                                               "\"metadata\" : {" +
+                                                               "\"Name\":\"testProject\"," +
+                                                               "\"Description\":\"Test project for unit testing\"," +
+                                                               "\"UserData1\":\"userData1\"," +
+                                                               "\"UserData2\":\"userData2\"}" +
+                                                               "}"),
+                                       },
+                               },
+                       },
+               },
+               {
+                       label: "Failed Update Project",
+                       inp: Project{
+                               MetaData: ProjectMetaData{
+                                       Name:        "unknownProject",
+                                       Description: "Unknown project for unit testing",
+                               },
+                       },
+                       expectedError: "Creating DB Entry",
+                       mockdb: &db.MockDB{
+                               Err: pkgerrors.New("Error Updating Project"),
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       db.DBconn = testCase.mockdb
+                       impl := NewProjectClient()
+                       got, err := impl.CreateProject(testCase.inp, true)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("Update returned an unexpected error %s", err)
+                               }
+                               if strings.Contains(err.Error(), testCase.expectedError) == false {
+                                       t.Fatalf("Update returned an unexpected error %s", err)
+                               }
+                       } else {
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("Update returned unexpected body: got %v;"+
+                                               " expected %v", got, testCase.expected)
+                               }
+                       }
+               })
+       }
+}
+
 func TestGetProject(t *testing.T) {
 
        testCases := []struct {