From 02d5281f0c605137e596f69d117cc148f481ca88 Mon Sep 17 00:00:00 2001 From: zgdxiaoxiao Date: Tue, 30 Aug 2016 18:26:41 +0800 Subject: [PATCH] project api test and bugfix 1.add project api test 2.bugfix:the type of public in project.go is fixed to int32 --- api/harborapi_test.go | 225 +++++++++++++++++++++--------- api/project.go | 12 +- api/project_test.go | 229 ++++++++++++++++++++++++++++--- tests/apitests/apilib/project.go | 6 +- 4 files changed, 379 insertions(+), 93 deletions(-) diff --git a/api/harborapi_test.go b/api/harborapi_test.go index ae41796a5..0f2509d39 100644 --- a/api/harborapi_test.go +++ b/api/harborapi_test.go @@ -10,9 +10,9 @@ import ( "path/filepath" "runtime" - "github.com/vmware/harbor/tests/apitests/apilib" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" + "github.com/vmware/harbor/tests/apitests/apilib" "github.com/astaxie/beego" "github.com/dghubble/sling" @@ -22,6 +22,11 @@ import ( _ "github.com/vmware/harbor/auth/ldap" ) +const ( + jsonAcceptHeader = "application/json" + testAcceptHeader = "text/plain" +) + type api struct { basePath string } @@ -51,13 +56,31 @@ func init() { beego.TestBeegoInit(apppath) beego.Router("/api/search/", &SearchAPI{}) - beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post") + beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head") + beego.Router("/api/projects/:id/delete", &ProjectAPI{}, "delete:Delete") beego.Router("/api/users/:id([0-9]+)/password", &UserAPI{}, "put:ChangePassword") + beego.Router("/api/projects/:id/publicity", &ProjectAPI{}, "put:ToggleProjectPublic") + beego.Router("/api/projects/:id([0-9]+)/logs/filter", &ProjectAPI{}, "post:FilterAccessLog") _ = updateInitPassword(1, "Harbor12345") } +func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int, []byte, error) { + _sling = _sling.Set("Accept", acceptHeader) + req, err := _sling.Request() + if err != nil { + return 400, nil, err + } + if len(authInfo) > 0 { + req.SetBasicAuth(authInfo[0].Name, authInfo[0].Passwd) + } + w := httptest.NewRecorder() + beego.BeeApp.Handlers.ServeHTTP(w, req) + body, err := ioutil.ReadAll(w.Body) + return w.Code, body, err +} + //Search for projects and repositories //Implementation Notes //The Search endpoint returns information about the projects and repositories @@ -80,23 +103,7 @@ func (a api) SearchGet(q string) (apilib.Search, error) { _sling = _sling.QueryStruct(&QueryParams{Query: q}) - accepts := []string{"application/json", "text/plain"} - for key := range accepts { - _sling = _sling.Set("Accept", accepts[key]) - break // only use the first Accept - } - - req, err := _sling.Request() - - w := httptest.NewRecorder() - - beego.BeeApp.Handlers.ServeHTTP(w, req) - - body, err := ioutil.ReadAll(w.Body) - if err != nil { - // handle error - } - + _, body, err := request(_sling, jsonAcceptHeader) var successPayload = new(apilib.Search) err = json.Unmarshal(body, &successPayload) return *successPayload, err @@ -117,24 +124,10 @@ func (a api) ProjectsPost(prjUsr usrInfo, project apilib.Project) (int, error) { _sling = _sling.Path(path) - // accept header - accepts := []string{"application/json", "text/plain"} - for key := range accepts { - _sling = _sling.Set("Accept", accepts[key]) - break // only use the first Accept - } - // body params _sling = _sling.BodyJSON(project) - //fmt.Printf("project post req: %+v\n", _sling) - req, err := _sling.Request() - req.SetBasicAuth(prjUsr.Name, prjUsr.Passwd) - - w := httptest.NewRecorder() - - beego.BeeApp.Handlers.ServeHTTP(w, req) - - return w.Code, err + httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr) + return httpStatusCode, err } //Change password @@ -154,27 +147,11 @@ func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib. fmt.Printf("password %+v\n", password) _sling = _sling.Path(path) - // accept header - accepts := []string{"application/json", "text/plain"} - for key := range accepts { - _sling = _sling.Set("Accept", accepts[key]) - break // only use the first Accept - } - // body params _sling = _sling.BodyJSON(password) - fmt.Printf("project post req: %+v\n", _sling) - req, err := _sling.Request() - req.SetBasicAuth(user.Name, user.Passwd) - fmt.Printf("project post req: %+v\n", req) - if err != nil { - // handle error - } - w := httptest.NewRecorder() - beego.BeeApp.Handlers.ServeHTTP(w, req) - - return w.Code + httpStatusCode, _, _ := request(_sling, jsonAcceptHeader, user) + return httpStatusCode } @@ -184,8 +161,8 @@ func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib. ////@param repoName The name of repository which will be deleted. ////@param tag Tag of a repository. ////@return void -////func (a HarborAPI) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) { -//func (a HarborAPI) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) { +////func (a api) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) { +//func (a api) RepositoriesDelete(prjUsr UsrInfo, repoName string, tag string) (int, error) { // _sling := sling.New().Delete(a.basePath) // // create path and map variables @@ -220,6 +197,128 @@ func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib. // return httpResponse.StatusCode, err //} +//Delete project by projectID +func (a api) ProjectsDelete(prjUsr usrInfo, projectID string) (int, error) { + _sling := sling.New().Delete(a.basePath) + + //create api path + path := "api/projects/" + projectID + "/delete" + _sling = _sling.Path(path) + + //_sling = _sling.BodyJSON(project) + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) + fmt.Println(string(body)) + return httpStatusCode, err +} + +//Check if the project name user provided already exists +func (a api) ProjectsHead(prjUsr usrInfo, projectName string) (int, error) { + _sling := sling.New().Head(a.basePath) + + //create api path + path := "api/projects" + _sling = _sling.Path(path) + type QueryParams struct { + ProjectName string `url:"project_name,omitempty"` + } + _sling = _sling.QueryStruct(&QueryParams{ProjectName: projectName}) + + httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr) + return httpStatusCode, err +} + +//Search projects by projectName and isPublic +func (a api) ProjectsGet(projectName string, isPublic int32) (int, []apilib.Project, error) { + _sling := sling.New().Get(a.basePath) + + //create api path + path := "api/projects" + _sling = _sling.Path(path) + type QueryParams struct { + ProjectName string `url:"project_name,omitempty"` + IsPubilc int32 `url:"is_public,omitempty"` + } + _sling = _sling.QueryStruct(&QueryParams{ProjectName: projectName, IsPubilc: isPublic}) + + var successPayload []apilib.Project + + httpStatusCode, body, err := request(_sling, jsonAcceptHeader) + if err == nil && httpStatusCode == 200 { + err = json.Unmarshal(body, &successPayload) + } + + return httpStatusCode, successPayload, err +} + +//Update properties for a selected project. +func (a api) ToggleProjectPublicity(prjUsr usrInfo, projectID string, ispublic int32) (int, error) { + // create path and map variables + path := "/api/projects/" + projectID + "/publicity/" + _sling := sling.New().Put(a.basePath) + + _sling = _sling.Path(path) + + type QueryParams struct { + Public int32 `json:"public,omitempty"` + } + + _sling = _sling.BodyJSON(&QueryParams{Public: ispublic}) + + httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr) + return httpStatusCode, err + +} + +//Get access logs accompany with a relevant project. +func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apilib.AccessLogFilter) (int, []byte, error) { + //func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apilib.AccessLog) (int, apilib.AccessLog, error) { + _sling := sling.New().Post(a.basePath) + + path := "/api/projects/" + projectID + "/logs/filter" + + _sling = _sling.Path(path) + + // body params + _sling = _sling.BodyJSON(accessLog) + + // var successPayload []apilib.AccessLog + + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) + /* + if err == nil && httpStatusCode == 200 { + err = json.Unmarshal(body, &successPayload) + } + */ + return httpStatusCode, body, err + // return httpStatusCode, successPayload, err +} + +//Return relevant role members of projectID +func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []byte, error) { + _sling := sling.New().Post(a.basePath) + + path := "/api/projects/" + projectID + "/members/" + + _sling = _sling.Path(path) + + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) + return httpStatusCode, body, err + +} + +//Add project role member accompany with projectID +func (a api) AddProjectMember(prjUsr usrInfo, projectID string, roles apilib.RoleParam) (int, []byte, error) { + _sling := sling.New().Post(a.basePath) + + path := "/api/projects/" + projectID + "/members/" + + _sling = _sling.Path(path) + + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) + return httpStatusCode, body, err + +} + //Return projects created by Harbor //func (a HarborApi) ProjectsGet (projectName string, isPublic int32) ([]Project, error) { // } @@ -229,35 +328,35 @@ func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib. //} //Get access logs accompany with a relevant project. -//func (a HarborApi) ProjectsProjectIdLogsFilterPost (projectId int32, accessLog AccessLog) ([]AccessLog, error) { +//func (a HarborApi) ProjectsProjectIdLogsFilterPost (projectID int32, accessLog AccessLog) ([]AccessLog, error) { //} //Return a project's relevant role members. -//func (a HarborApi) ProjectsProjectIdMembersGet (projectId int32) ([]Role, error) { +//func (a HarborApi) ProjectsProjectIdMembersGet (projectID int32) ([]Role, error) { //} //Add project role member accompany with relevant project and user. -//func (a HarborApi) ProjectsProjectIdMembersPost (projectId int32, roles RoleParam) (error) { +//func (a HarborApi) ProjectsProjectIdMembersPost (projectID int32, roles RoleParam) (error) { //} //Delete project role members accompany with relevant project and user. -//func (a HarborApi) ProjectsProjectIdMembersUserIdDelete (projectId int32, userId int32) (error) { +//func (a HarborApi) ProjectsProjectIdMembersUserIdDelete (projectID int32, userId int32) (error) { //} //Return role members accompany with relevant project and user. -//func (a HarborApi) ProjectsProjectIdMembersUserIdGet (projectId int32, userId int32) ([]Role, error) { +//func (a HarborApi) ProjectsProjectIdMembersUserIdGet (projectID int32, userId int32) ([]Role, error) { //} //Update project role members accompany with relevant project and user. -//func (a HarborApi) ProjectsProjectIdMembersUserIdPut (projectId int32, userId int32, roles RoleParam) (error) { +//func (a HarborApi) ProjectsProjectIdMembersUserIdPut (projectID int32, userId int32, roles RoleParam) (error) { //} //Update properties for a selected project. -//func (a HarborApi) ProjectsProjectIdPut (projectId int32, project Project) (error) { +//func (a HarborApi) ProjectsProjectIdPut (projectID int32, project Project) (error) { //} //Get repositories accompany with relevant project and repo name. -//func (a HarborApi) RepositoriesGet (projectId int32, q string) ([]Repository, error) { +//func (a HarborApi) RepositoriesGet (projectID int32, q string) ([]Repository, error) { //} //Get manifests of a relevant repository. diff --git a/api/project.go b/api/project.go index 0f019143b..317202103 100644 --- a/api/project.go +++ b/api/project.go @@ -38,7 +38,7 @@ type ProjectAPI struct { type projectReq struct { ProjectName string `json:"project_name"` - Public bool `json:"public"` + Public int `json:"public"` } const projectNameMaxLen int = 30 @@ -73,11 +73,8 @@ func (p *ProjectAPI) Post() { p.userID = p.ValidateUser() var req projectReq - var public int p.DecodeJSONReq(&req) - if req.Public { - public = 1 - } + public := req.Public err := validateProjectReq(req) if err != nil { log.Errorf("Invalid project request, error: %v", err) @@ -305,7 +302,6 @@ func (p *ProjectAPI) List() { func (p *ProjectAPI) ToggleProjectPublic() { p.userID = p.ValidateUser() var req projectReq - var public int projectID, err := strconv.ParseInt(p.Ctx.Input.Param(":id"), 10, 64) if err != nil { @@ -315,9 +311,7 @@ func (p *ProjectAPI) ToggleProjectPublic() { } p.DecodeJSONReq(&req) - if req.Public { - public = 1 - } + public := req.Public if !isProjectAdmin(p.userID, projectID) { log.Warningf("Current user, id: %d does not have project admin role for project, id: %d", p.userID, projectID) p.RenderError(http.StatusForbidden, "") diff --git a/api/project_test.go b/api/project_test.go index 43a9f77dd..95b95660c 100644 --- a/api/project_test.go +++ b/api/project_test.go @@ -2,64 +2,257 @@ package api import ( "fmt" - "testing" - "github.com/stretchr/testify/assert" "github.com/vmware/harbor/tests/apitests/apilib" + "strconv" + "testing" + "time" ) +var admin, unknownUsr *usrInfo +var addProject apilib.Project + +func Init() { + admin = &usrInfo{"admin", "Harbor12345"} + unknownUsr = &usrInfo{"unknown", "unknown"} + addProject.ProjectName = "test_project" + addProject.Public = 1 + +} + func TestAddProject(t *testing.T) { - fmt.Println("Testing Add Project(ProjectsPost) API") + fmt.Println("\nTesting Add Project(ProjectsPost) API") assert := assert.New(t) apiTest := newHarborAPI() //prepare for test - - admin := &usrInfo{"admin", "Harbor12345"} - - prjUsr := &usrInfo{"unknown", "unknown"} - - var project apilib.Project - project.ProjectName = "test_project" - project.Public = true + Init() //case 1: admin not login, expect project creation fail. - result, err := apiTest.ProjectsPost(*prjUsr, project) + result, err := apiTest.ProjectsPost(*unknownUsr, addProject) if err != nil { t.Error("Error while creat project", err.Error()) t.Log(err) } else { - assert.Equal(result, int(401), "Case 1: Project creation status should be 401") + assert.Equal(int(401), result, "Case 1: Project creation status should be 401") //t.Log(result) } //case 2: admin successful login, expect project creation success. fmt.Println("case 2: admin successful login, expect project creation success.") - prjUsr = admin + unknownUsr = admin - result, err = apiTest.ProjectsPost(*prjUsr, project) + result, err = apiTest.ProjectsPost(*admin, addProject) if err != nil { t.Error("Error while creat project", err.Error()) t.Log(err) } else { - assert.Equal(result, int(201), "Case 2: Project creation status should be 201") + assert.Equal(int(201), result, "Case 2: Project creation status should be 201") //t.Log(result) } //case 3: duplicate project name, create project fail fmt.Println("case 3: duplicate project name, create project fail") - result, err = apiTest.ProjectsPost(*prjUsr, project) + result, err = apiTest.ProjectsPost(*admin, addProject) if err != nil { t.Error("Error while creat project", err.Error()) t.Log(err) } else { - assert.Equal(result, int(409), "Case 3: Project creation status should be 409") + assert.Equal(int(409), result, "Case 3: Project creation status should be 409") + //t.Log(result) + } + fmt.Printf("\n") + +} + +func TestProGet(t *testing.T) { + fmt.Println("\nTest for Project GET API") + assert := assert.New(t) + + apiTest := newHarborAPI() + var result []apilib.Project + + //----------------------------case 1 : Response Code=200----------------------------// + fmt.Println("case 1: respose code:200") + httpStatusCode, result, err := apiTest.ProjectsGet(addProject.ProjectName, 1) + if err != nil { + t.Error("Error while search project by proName and isPublic", err.Error()) + t.Log(err) + } else { + assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + //find add projectID + addProject.ProjectId = int32(result[0].ProjectId) + } + + //----------------------------case 2 : Response Code=401:is_public=0----------------------------// + fmt.Println("case 2: respose code:401,isPublic = 0") + httpStatusCode, result, err = apiTest.ProjectsGet("library", 0) + if err != nil { + t.Error("Error while search project by proName and isPublic", err.Error()) + t.Log(err) + } else { + assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200") + } + + fmt.Printf("\n") +} + +func TestDeleteProject(t *testing.T) { + + fmt.Println("\nTesting Delete Project(ProjectsPost) API") + assert := assert.New(t) + + apiTest := newHarborAPI() + + projectID := strconv.Itoa(int(addProject.ProjectId)) + //--------------------------case 1: Response Code=200---------------------------------// + + httpStatusCode, err := apiTest.ProjectsDelete(*admin, projectID) + if err != nil { + t.Error("Error while delete project", err.Error()) + t.Log(err) + } else { + assert.Equal(int(200), httpStatusCode, "Case 1: Project creation status should be 200") //t.Log(result) } + fmt.Printf("\n") + +} +func TestProHead(t *testing.T) { + Init() + fmt.Println("\nTest for Project HEAD API") + assert := assert.New(t) + + apiTest := newHarborAPI() + + //----------------------------case 1 : Response Code=200----------------------------// + fmt.Println("case 1: respose code:200") + httpStatusCode, err := apiTest.ProjectsHead(*admin, "library") + if err != nil { + t.Error("Error while search project by proName", err.Error()) + t.Log(err) + } else { + assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + } + + //----------------------------case 2 : Response Code=404:Project name does not exist.----------------------------// + fmt.Println("case 2: respose code:404,Project name does not exist.") + httpStatusCode, err = apiTest.ProjectsHead(*admin, "libra") + if err != nil { + t.Error("Error while search project by proName", err.Error()) + t.Log(err) + } else { + assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404") + } + //----------------------------case 3 : Response Code=401:User need to log in first..----------------------------// + fmt.Println("case 3: respose code:401,User need to log in first..") + httpStatusCode, err = apiTest.ProjectsHead(*unknownUsr, "libra") + if err != nil { + t.Error("Error while search project by proName", err.Error()) + t.Log(err) + } else { + assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401") + } + + fmt.Printf("\n") + +} + +func TestToggleProjectPublicity(t *testing.T) { + fmt.Println("\nTest for Project PUT API: Update properties for a selected project") + assert := assert.New(t) + + apiTest := newHarborAPI() + + //-------------------case1: Response Code=200------------------------------// + fmt.Println("case 1: respose code:200") + httpStatusCode, err := apiTest.ToggleProjectPublicity(*admin, "1", 1) + if err != nil { + t.Error("Error while search project by proId", err.Error()) + t.Log(err) + } else { + assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + } + //-------------------case2: Response Code=401 User need to log in first. ------------------------------// + fmt.Println("case 2: respose code:401, User need to log in first.") + httpStatusCode, err = apiTest.ToggleProjectPublicity(*unknownUsr, "1", 1) + if err != nil { + t.Error("Error while search project by proId", err.Error()) + t.Log(err) + } else { + assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401") + } + //-------------------case3: Response Code=400 Invalid project id------------------------------// + fmt.Println("case 3: respose code:400, Invalid project id") + httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "cc", 1) + if err != nil { + t.Error("Error while search project by proId", err.Error()) + t.Log(err) + } else { + assert.Equal(int(400), httpStatusCode, "httpStatusCode should be 400") + } + //-------------------case4: Response Code=404 Not found the project------------------------------// + fmt.Println("case 4: respose code:404, Not found the project") + httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "0", 1) + if err != nil { + t.Error("Error while search project by proId", err.Error()) + t.Log(err) + } else { + assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404") + } + + fmt.Printf("\n") +} +func TestProjectLogsFilter(t *testing.T) { + fmt.Println("\nTest for search access logs filtered by operations and date time ranges..") + assert := assert.New(t) + + apiTest := newHarborAPI() + + endTimestamp := time.Now().Unix() + startTimestamp := endTimestamp - 3600 + accessLog := &apilib.AccessLogFilter{ + Username: "admin", + Keywords: "", + BeginTimestamp: startTimestamp, + EndTimestamp: endTimestamp, + } + + //-------------------case1: Response Code=200------------------------------// + fmt.Println("case 1: respose code:200") + projectID := "1" + httpStatusCode, _, err := apiTest.ProjectLogsFilter(*admin, projectID, *accessLog) + if err != nil { + t.Error("Error while search access logs") + t.Log(err) + } else { + assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + } + //-------------------case2: Response Code=401:User need to log in first.------------------------------// + fmt.Println("case 2: respose code:401:User need to log in first.") + projectID = "1" + httpStatusCode, _, err = apiTest.ProjectLogsFilter(*unknownUsr, projectID, *accessLog) + if err != nil { + t.Error("Error while search access logs") + t.Log(err) + } else { + assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401") + } + //-------------------case3: Response Code=404:Project does not exist.-------------------------// + fmt.Println("case 3: respose code:404:Illegal format of provided ID value.") + projectID = "11111" + httpStatusCode, _, err = apiTest.ProjectLogsFilter(*admin, projectID, *accessLog) + if err != nil { + t.Error("Error while search access logs") + t.Log(err) + } else { + assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404") + } + fmt.Printf("\n") } diff --git a/tests/apitests/apilib/project.go b/tests/apitests/apilib/project.go index 0026ca0f9..be4b7987a 100644 --- a/tests/apitests/apilib/project.go +++ b/tests/apitests/apilib/project.go @@ -1,10 +1,10 @@ -/* +/* * Harbor API * * These APIs provide services for manipulating Harbor project. * * OpenAPI spec version: 0.3.0 - * + * * Generated by: https://github.com/swagger-api/swagger-codegen.git * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -49,7 +49,7 @@ type Project struct { OwnerName string `json:"owner_name,omitempty"` // The public status of the project. - Public bool `json:"public,omitempty"` + Public int32 `json:"public,omitempty"` // Correspond to the UI about whether the project's publicity is updatable (for UI) Togglable bool `json:"togglable,omitempty"`