diff --git a/api/harborapi_test.go b/api/harborapi_test.go index d7d3405af..7bc1b0e7f 100644 --- a/api/harborapi_test.go +++ b/api/harborapi_test.go @@ -25,8 +25,12 @@ import ( const ( jsonAcceptHeader = "application/json" testAcceptHeader = "text/plain" + adminName = "admin" + adminPwd = "Harbor12345" ) +var admin, unknownUsr *usrInfo + type api struct { basePath string } @@ -57,15 +61,20 @@ func init() { beego.Router("/api/search/", &SearchAPI{}) beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head") - beego.Router("/api/projects/:id/delete", &ProjectAPI{}, "delete:Delete") + beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get") 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") + beego.Router("/api/projects/:pid([0-9]+)/members/?:mid", &ProjectMemberAPI{}, "get:Get") beego.Router("/api/statistics", &StatisticAPI{}) beego.Router("/api/logs", &LogAPI{}) _ = updateInitPassword(1, "Harbor12345") + //Init user Info + admin = &usrInfo{adminName, adminPwd} + unknownUsr = &usrInfo{"unknown", "unknown"} + } func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int, []byte, error) { @@ -92,13 +101,17 @@ func request(_sling *sling.Sling, acceptHeader string, authInfo ...usrInfo) (int //@return []Search //func (a api) SearchGet (q string) (apilib.Search, error) { func (a api) SearchGet(q string) (apilib.Search, error) { + _sling := sling.New().Get(a.basePath) + // create path and map variables path := "/api/search" _sling = _sling.Path(path) + type QueryParams struct { Query string `url:"q,omitempty"` } + _sling = _sling.QueryStruct(&QueryParams{Query: q}) _, body, err := request(_sling, jsonAcceptHeader) @@ -113,11 +126,13 @@ func (a api) SearchGet(q string) (apilib.Search, error) { //@param project New created project. //@return void //func (a api) ProjectsPost (prjUsr usrInfo, project apilib.Project) (int, error) { -func (a api) ProjectsPost(prjUsr usrInfo, project apilib.Project) (int, error) { +func (a api) ProjectsPost(prjUsr usrInfo, project apilib.ProjectReq) (int, error) { _sling := sling.New().Post(a.basePath) + // create path and map variables path := "/api/projects/" + _sling = _sling.Path(path) // body params @@ -136,6 +151,7 @@ func (a api) ProjectsPost(prjUsr usrInfo, project apilib.Project) (int, error) { func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib.Password) int { _sling := sling.New().Put(a.basePath) + // create path and map variables path := "/api/users/" + fmt.Sprintf("%d", userID) + "/password" fmt.Printf("change passwd path: %s\n", path) @@ -144,6 +160,7 @@ func (a api) UsersUserIDPasswordPut(user usrInfo, userID int32, password apilib. // body params _sling = _sling.BodyJSON(password) + httpStatusCode, _, _ := request(_sling, jsonAcceptHeader, user) return httpStatusCode } @@ -232,12 +249,9 @@ func (a api) ProjectsDelete(prjUsr usrInfo, projectID string) (int, error) { _sling := sling.New().Delete(a.basePath) //create api path - path := "api/projects/" + projectID + "/delete" + path := "api/projects/" + projectID _sling = _sling.Path(path) - - //_sling = _sling.BodyJSON(project) - httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) - fmt.Println(string(body)) + httpStatusCode, _, err := request(_sling, jsonAcceptHeader, prjUsr) return httpStatusCode, err } @@ -257,6 +271,23 @@ func (a api) ProjectsHead(prjUsr usrInfo, projectName string) (int, error) { return httpStatusCode, err } +//Return specific project detail infomation +func (a api) ProjectsGetByPID(projectID string) (int, apilib.Project, error) { + _sling := sling.New().Get(a.basePath) + + //create api path + path := "api/projects/" + projectID + _sling = _sling.Path(path) + + var successPayload apilib.Project + + httpStatusCode, body, err := request(_sling, jsonAcceptHeader) + if err == nil && httpStatusCode == 200 { + err = json.Unmarshal(body, &successPayload) + } + return httpStatusCode, successPayload, 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) @@ -311,7 +342,7 @@ func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apili // body params _sling = _sling.BodyJSON(accessLog) - // var successPayload []apilib.AccessLog + //var successPayload []apilib.AccessLog httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) /* @@ -323,17 +354,23 @@ func (a api) ProjectLogsFilter(prjUsr usrInfo, projectID string, accessLog apili // return httpStatusCode, successPayload, err } +//-------------------------Member Test---------------------------------------// + //Return relevant role members of projectID -func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []byte, error) { - _sling := sling.New().Post(a.basePath) +func (a api) GetProjectMembersByProID(prjUsr usrInfo, projectID string) (int, []apilib.User, error) { + _sling := sling.New().Get(a.basePath) path := "/api/projects/" + projectID + "/members/" _sling = _sling.Path(path) - httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) - return httpStatusCode, body, err + var successPayload []apilib.User + httpStatusCode, body, err := request(_sling, jsonAcceptHeader, prjUsr) + if err == nil && httpStatusCode == 200 { + err = json.Unmarshal(body, &successPayload) + } + return httpStatusCode, successPayload, err } //Add project role member accompany with projectID diff --git a/api/log_test.go b/api/log_test.go index 12cede8bc..549fd1a25 100644 --- a/api/log_test.go +++ b/api/log_test.go @@ -18,7 +18,7 @@ func TestLogGet(t *testing.T) { //prepare for test admin := &usrInfo{"admin", "Harbor12345"} - var project apilib.Project + var project apilib.ProjectReq project.ProjectName = "my_project" project.Public = 1 @@ -99,17 +99,18 @@ func TestLogGet(t *testing.T) { //get the project var projects []apilib.Project + var addProjectID int32 httpStatusCode, projects, err := apiTest.ProjectsGet(project.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") - project.ProjectId = projects[0].ProjectId + addProjectID = projects[0].ProjectId } //delete the project - projectID := strconv.Itoa(int(project.ProjectId)) + projectID := strconv.Itoa(int(addProjectID)) httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID) if err != nil { t.Error("Error while delete project", err.Error()) diff --git a/api/member_test.go b/api/member_test.go new file mode 100644 index 000000000..eb645422f --- /dev/null +++ b/api/member_test.go @@ -0,0 +1,54 @@ +package api + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/harbor/tests/apitests/apilib" +) + +func TestMemGet(t *testing.T) { + var result []apilib.User + var httpStatusCode int + var err error + + assert := assert.New(t) + apiTest := newHarborAPI() + projectID := "1" + + fmt.Println("Testing Member Get API") + //-------------------case 1 : response code = 200------------------------// + httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID) + if err != nil { + t.Error("Error whihle get members by projectID", err.Error()) + t.Log(err) + } else { + assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + assert.Equal(int(1), result[0].UserId, "User Id should be 1") + assert.Equal("admin", result[0].Username, "User name should be admin") + } + + //---------case 2: Response Code=401,User need to log in first.----------// + fmt.Println("case 2: Response Code=401,User need to log in first.") + httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*unknownUsr, projectID) + if err != nil { + t.Error("Error while get members by projectID", err.Error()) + t.Log(err) + } else { + assert.Equal(int(401), httpStatusCode, "Case 2: Project creation status should be 401") + } + + //------------case 3: Response Code=404,Project does not exist-----------// + fmt.Println("case 3: Response Code=404,Project does not exist") + projectID = "11" + httpStatusCode, result, err = apiTest.GetProjectMembersByProID(*admin, projectID) + if err != nil { + t.Error("Error while get members by projectID", err.Error()) + t.Log(err) + } else { + assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404") + } + fmt.Printf("\n") + +} diff --git a/api/project_test.go b/api/project_test.go index 95b95660c..2b164da6a 100644 --- a/api/project_test.go +++ b/api/project_test.go @@ -9,15 +9,11 @@ import ( "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 +var addProject *apilib.ProjectReq +var addPID int +func InitAddPro() { + addProject = &apilib.ProjectReq{"test_project", 1} } func TestAddProject(t *testing.T) { @@ -28,11 +24,11 @@ func TestAddProject(t *testing.T) { apiTest := newHarborAPI() //prepare for test - Init() + InitAddPro() //case 1: admin not login, expect project creation fail. - result, err := apiTest.ProjectsPost(*unknownUsr, addProject) + result, err := apiTest.ProjectsPost(*unknownUsr, *addProject) if err != nil { t.Error("Error while creat project", err.Error()) t.Log(err) @@ -44,9 +40,7 @@ func TestAddProject(t *testing.T) { //case 2: admin successful login, expect project creation success. fmt.Println("case 2: admin successful login, expect project creation success.") - unknownUsr = admin - - result, err = apiTest.ProjectsPost(*admin, addProject) + result, err = apiTest.ProjectsPost(*admin, *addProject) if err != nil { t.Error("Error while creat project", err.Error()) t.Log(err) @@ -58,7 +52,7 @@ func TestAddProject(t *testing.T) { //case 3: duplicate project name, create project fail fmt.Println("case 3: duplicate project name, create project fail") - result, err = apiTest.ProjectsPost(*admin, addProject) + result, err = apiTest.ProjectsPost(*admin, *addProject) if err != nil { t.Error("Error while creat project", err.Error()) t.Log(err) @@ -66,12 +60,25 @@ func TestAddProject(t *testing.T) { assert.Equal(int(409), result, "Case 3: Project creation status should be 409") //t.Log(result) } + + //case 4: reponse code = 400 : Project name is illegal in length + fmt.Println("case 4 : reponse code = 400 : Project name is illegal in length ") + + result, err = apiTest.ProjectsPost(*admin, apilib.ProjectReq{"t", 1}) + if err != nil { + t.Error("Error while creat project", err.Error()) + t.Log(err) + } else { + assert.Equal(int(400), result, "case 4 : reponse code = 400 : Project name is illegal in length ") + } + fmt.Printf("\n") } -func TestProGet(t *testing.T) { - fmt.Println("\nTest for Project GET API") +//Get project by proName +func TestProGetByName(t *testing.T) { + fmt.Println("\nTest for Project GET API by project name") assert := assert.New(t) apiTest := newHarborAPI() @@ -85,10 +92,11 @@ func TestProGet(t *testing.T) { t.Log(err) } else { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong") + assert.Equal(int32(1), result[0].Public, "Public is wrong") //find add projectID - addProject.ProjectId = int32(result[0].ProjectId) + addPID = int(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) @@ -98,10 +106,31 @@ func TestProGet(t *testing.T) { } else { assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200") } - fmt.Printf("\n") } +//Get project by proID +func TestProGetByID(t *testing.T) { + fmt.Println("\nTest for Project GET API by project id") + assert := assert.New(t) + + apiTest := newHarborAPI() + var result apilib.Project + projectID := strconv.Itoa(addPID) + + //----------------------------case 1 : Response Code=200----------------------------// + fmt.Println("case 1: respose code:200") + httpStatusCode, result, err := apiTest.ProjectsGetByPID(projectID) + 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") + assert.Equal(addProject.ProjectName, result.ProjectName, "ProjectName is wrong") + assert.Equal(int32(1), result.Public, "Public is wrong") + } + fmt.Printf("\n") +} func TestDeleteProject(t *testing.T) { fmt.Println("\nTesting Delete Project(ProjectsPost) API") @@ -109,23 +138,53 @@ func TestDeleteProject(t *testing.T) { apiTest := newHarborAPI() - projectID := strconv.Itoa(int(addProject.ProjectId)) - //--------------------------case 1: Response Code=200---------------------------------// + projectID := strconv.Itoa(addPID) - httpStatusCode, err := apiTest.ProjectsDelete(*admin, projectID) + //--------------------------case 1: Response Code=401,User need to log in first.-----------------------// + fmt.Println("case 1: Response Code=401,User need to log in first.") + httpStatusCode, err := apiTest.ProjectsDelete(*unknownUsr, 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) + assert.Equal(int(401), httpStatusCode, "Case 1: Project creation status should be 401") } + //--------------------------case 2: Response Code=200---------------------------------// + fmt.Println("case2: respose 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 2: Project creation status should be 200") + } + + //--------------------------case 3: Response Code=404,Project does not exist---------------------------------// + fmt.Println("case 3: Response Code=404,Project does not exist") + projectID = "11" + httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID) + if err != nil { + t.Error("Error while delete project", err.Error()) + t.Log(err) + } else { + assert.Equal(int(404), httpStatusCode, "Case 3: Project creation status should be 404") + } + + //--------------------------case 4: Response Code=400,Invalid project id.---------------------------------// + fmt.Println("case 4: Response Code=400,Invalid project id.") + projectID = "cc" + httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID) + if err != nil { + t.Error("Error while delete project", err.Error()) + t.Log(err) + } else { + assert.Equal(int(400), httpStatusCode, "Case 4: Project creation status should be 400") + } fmt.Printf("\n") } func TestProHead(t *testing.T) { - Init() fmt.Println("\nTest for Project HEAD API") assert := assert.New(t) diff --git a/api/statistic_test.go b/api/statistic_test.go index 58b48e222..94c54b5ac 100644 --- a/api/statistic_test.go +++ b/api/statistic_test.go @@ -34,7 +34,7 @@ func TestStatisticGet(t *testing.T) { totalProCount = result.TotalProjectCount } //post project - var project apilib.Project + var project apilib.ProjectReq project.ProjectName = "statistic_project" project.Public = 1 @@ -65,17 +65,18 @@ func TestStatisticGet(t *testing.T) { //get the project var projects []apilib.Project + var addProjectID int32 httpStatusCode, projects, err := apiTest.ProjectsGet(project.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") - project.ProjectId = projects[0].ProjectId + addProjectID = projects[0].ProjectId } //delete the project - projectID := strconv.Itoa(int(project.ProjectId)) + projectID := strconv.Itoa(int(addProjectID)) httpStatusCode, err = apiTest.ProjectsDelete(*admin, projectID) if err != nil { t.Error("Error while delete project", err.Error()) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 195c327d4..c979e057a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -123,7 +123,7 @@ paths: description: New created project. required: true schema: - $ref: '#/definitions/Project' + $ref: '#/definitions/ProjectReq' tags: - Products responses: @@ -155,13 +155,37 @@ paths: 200: description: Return matched project information. schema: - type: array - items: - $ref: '#/definitions/Project' + $ref: '#/definitions/Project' 401: description: User need to log in first. 500: description: Internal errors. + delete: + summary: Delete project by projectID + description: | + This endpoint is aimed to delete project by project ID. + parameters: + - name: project_id + in: path + description: Project ID of project which will be deleted. + required: true + type: integer + format: int64 + tags: + - Products + responses: + 200: + description: Project is deleted successfully. + 400: + description: Invalid project id. + 403: + description: User need to log in first. + 404: + description: Project does not exist. + 412: + description: Project contains policies, can not be deleted. + 500: + description: Internal errors. /projects/{project_id}/publicity: put: summary: Update properties for a selected project. @@ -682,7 +706,7 @@ paths: 404: description: Repository or tag not found. 403: - description: Forbidden. + description: Forbidden. /repositories/tags: get: summary: Get tags of a relevant repository. @@ -721,7 +745,7 @@ paths: in: query type: string required: false - description: The version of manifest, valid value are "v1" and "v2", default is "v2" + description: The version of manifest, valid value are "v1" and "v2", default is "v2" tags: - Products responses: @@ -1290,6 +1314,16 @@ definitions: project_public: type: integer description: The flag to indicate the publicity of the project that the repository belongs to (1 is public, 0 is not) + ProjectReq: + type: object + properties: + project_name: + type: string + description: The name of the project. + public: + type: integer + format: int + description: The public status of the project. Project: type: object properties: @@ -1301,7 +1335,7 @@ definitions: type: integer format: int32 description: The owner ID of the project always means the creator of the project. - project_name: + name: type: string description: The name of the project. creation_time: @@ -1320,10 +1354,10 @@ definitions: description: A relation field to the user table. owner_name: type: string - description: The owner name of the project. + description: The owner name of the project. public: - type: boolean - format: boolean + type: integer + format: int description: The public status of the project. togglable: type: boolean @@ -1348,21 +1382,37 @@ definitions: properties: user_id: type: integer - format: int32 + format: int description: The ID of the user. username: type: string email: type: string - password: + password: type: string - realname: + realname: type: string comment: type: string deleted: type: integer format: int32 + role_name: + type: string + role_id: + type: integer + format: int + has_admin_role: + type: integer + format: int + reset_uuid: + type: string + Salt: + type: string + creation_time: + type: string + update_time: + type: string Password: type: object properties: @@ -1371,7 +1421,7 @@ definitions: description: The user's existing password. new_password: type: string - description: New password for marking as to be updated. + description: New password for marking as to be updated. AccessLogFilter: type: object properties: @@ -1637,3 +1687,4 @@ definitions: password: type: string description: The target server password. + diff --git a/tests/apitests/apilib/project.go b/tests/apitests/apilib/project.go index be4b7987a..3a4e9d257 100644 --- a/tests/apitests/apilib/project.go +++ b/tests/apitests/apilib/project.go @@ -31,7 +31,7 @@ type Project struct { OwnerId int32 `json:"owner_id,omitempty"` // The name of the project. - ProjectName string `json:"project_name,omitempty"` + ProjectName string `json:"name,omitempty"` // The creation time of the project. CreationTime string `json:"creation_time,omitempty"` diff --git a/tests/apitests/apilib/project_req.go b/tests/apitests/apilib/project_req.go new file mode 100644 index 000000000..544397714 --- /dev/null +++ b/tests/apitests/apilib/project_req.go @@ -0,0 +1,32 @@ +/* + * 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"); + * 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 apilib + +type ProjectReq struct { + + // The name of the project. + ProjectName string `json:"project_name,omitempty"` + + // The public status of the project. + Public int32 `json:"public,omitempty"` +} diff --git a/tests/apitests/apilib/user.go b/tests/apitests/apilib/user.go index ccd6522a3..97d05a12f 100644 --- a/tests/apitests/apilib/user.go +++ b/tests/apitests/apilib/user.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"); @@ -25,7 +25,7 @@ package apilib type User struct { // The ID of the user. - UserId int32 `json:"user_id,omitempty"` + UserId int `json:"user_id,omitempty"` Username string `json:"username,omitempty"` @@ -38,4 +38,18 @@ type User struct { Comment string `json:"comment,omitempty"` Deleted int32 `json:"deleted,omitempty"` + + RoleName string `json:"role_name,omitempty"` + + RoleId int32 `json:"role_id,omitempty"` + + HasAdminRole int32 `json:"has_admin_role,omitempty"` + + ResetUuid string `json:"reset_uuid,omitempty"` + + Salt string `json:"Salt,omitempty"` + + CreationTime string `json:"creation_time,omitempty"` + + UpdateTime string `json:"update_time,omitempty"` }