diff options
| -rw-r--r-- | app/models/project.rb | 2 | ||||
| -rw-r--r-- | config/routes.rb | 1 | ||||
| -rw-r--r-- | doc/api/milestones.md | 40 | ||||
| -rw-r--r-- | doc/api/notes.md | 193 | ||||
| -rw-r--r-- | doc/api/projects.md | 382 | ||||
| -rw-r--r-- | doc/api/snippets.md | 71 | ||||
| -rw-r--r-- | doc/api/users.md | 131 | ||||
| -rw-r--r-- | lib/api.rb | 13 | ||||
| -rw-r--r-- | lib/api/helpers.rb | 6 | ||||
| -rw-r--r-- | lib/api/merge_requests.rb | 22 | ||||
| -rw-r--r-- | lib/api/milestones.rb | 2 | ||||
| -rw-r--r-- | lib/api/notes.rb | 7 | ||||
| -rw-r--r-- | lib/api/projects.rb | 96 | ||||
| -rw-r--r-- | lib/api/users.rb | 24 | ||||
| -rw-r--r-- | spec/models/project_spec.rb | 2 | ||||
| -rw-r--r-- | spec/requests/api/merge_requests_spec.rb | 45 | ||||
| -rw-r--r-- | spec/requests/api/milestones_spec.rb | 37 | ||||
| -rw-r--r-- | spec/requests/api/notes_spec.rb | 74 | ||||
| -rw-r--r-- | spec/requests/api/projects_spec.rb | 225 | ||||
| -rw-r--r-- | spec/requests/api/users_spec.rb | 123 |
20 files changed, 1329 insertions, 167 deletions
diff --git a/app/models/project.rb b/app/models/project.rb index 6ff2a3698df..7587ef189e3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -155,7 +155,7 @@ class Project < ActiveRecord::Base def check_limit unless creator.can_create_project? - errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it") + errors[:limit_reached] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it") end rescue errors[:base] << ("Can't check your ability to create project") diff --git a/config/routes.rb b/config/routes.rb index 7537a11de96..10536a6e529 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ Gitlab::Application.routes.draw do # API require 'api' + Gitlab::API.logger Rails.logger mount Gitlab::API => '/api' constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? } diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 73d29afc37a..9bb27271e95 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -1,6 +1,6 @@ ## List project milestones -Get a list of project milestones. +Returns a list of project milestones. ``` GET /projects/:id/milestones @@ -10,9 +10,16 @@ Parameters: + `id` (required) - The ID of a project -## Single milestone +Return values: -Get a single project milestone. ++ `200 Ok` on success and the list of project milestones ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID not found + + +## Get single milestone + +Gets a single project milestone. ``` GET /projects/:id/milestones/:milestone_id @@ -23,9 +30,16 @@ Parameters: + `id` (required) - The ID of a project + `milestone_id` (required) - The ID of a project milestone -## New milestone +Return values: + ++ `200 Ok` on success and the single milestone ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID not found + + +## Create new milestone -Create a new project milestone. +Creates a new project milestone. ``` POST /projects/:id/milestones @@ -38,9 +52,17 @@ Parameters: + `description` (optional) - The description of the milestone + `due_date` (optional) - The due date of the milestone +Return values: + ++ `201 Created` on success and the new milestone ++ `400 Bad Request` if the required attribute title is not given ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID not found + + ## Edit milestone -Update an existing project milestone. +Updates an existing project milestone. ``` PUT /projects/:id/milestones/:milestone_id @@ -54,3 +76,9 @@ Parameters: + `description` (optional) - The description of a milestone + `due_date` (optional) - The due date of the milestone + `closed` (optional) - The status of the milestone + +Return values: + ++ `200 Ok` on success and the updated milestone ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID or milestone ID not found diff --git a/doc/api/notes.md b/doc/api/notes.md index a4ba2826076..6a6a99aa4f4 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -1,4 +1,4 @@ -## List notes +## Wall ### List project wall notes @@ -30,22 +30,59 @@ Parameters: + `id` (required) - The ID of a project -### List merge request notes +Return values: -Get a list of merge request notes. ++ `200 Ok` on success and a list of notes ++ `401 Unauthorized` if user is not authorized to access this page + + +### Get single wall note + +Returns a single wall note. ``` -GET /projects/:id/merge_requests/:merge_request_id/notes +GET /projects/:id/notes/:note_id +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `note_id` (required) - The ID of a wall note + +Return values: + ++ `200 Ok` on success and the wall note (see example at `GET /projects/:id/notes`) ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if note ID not found + + +### Create new wall note + +Creates a new wall note. + +``` +POST /projects/:id/notes ``` Parameters: + `id` (required) - The ID of a project -+ `merge_request_id` (required) - The ID of an merge request ++ `body` (required) - The content of a note -### List issue notes +Return values: -Get a list of issue notes. ++ `201 Created` on success and the new wall note ++ `400 Bad Request` if attribute body is not given ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if something else fails + + + +## Issues + +### List project issue notes + +Gets a list of all notes for a single issue. ``` GET /projects/:id/issues/:issue_id/notes @@ -56,54 +93,85 @@ Parameters: + `id` (required) - The ID of a project + `issue_id` (required) - The ID of an issue -### List snippet notes +Return values: -Get a list of snippet notes. ++ `200 Ok` on success and a list of notes for a single issue ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID or issue ID not found + + +### Get single issue note + +Returns a single note for a specific project issue ``` -GET /projects/:id/snippets/:snippet_id/notes +GET /projects/:id/issues/:issue_id/notes/:note_id ``` Parameters: + `id` (required) - The ID of a project -+ `snippet_id` (required) - The ID of a snippet ++ `issue_id` (required) - The ID of a project issue ++ `note_id` (required) - The ID of an issue note + +Return values: + ++ `200 Ok` on success and the single issue note ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID, issue ID or note ID is not found -## Single note -### Single wall note +### Create new issue note -Get a wall note. +Creates a new note to a single project issue. ``` -GET /projects/:id/notes/:note_id +POST /projects/:id/issues/:issue_id/notes ``` Parameters: + `id` (required) - The ID of a project -+ `note_id` (required) - The ID of a wall note ++ `issue_id` (required) - The ID of an issue ++ `body` (required) - The content of a note + +Return values: + ++ `201 Created` on succes and the created note ++ `400 Bad Request` if the required attribute body is not given ++ `401 Unauthorized` if the user is not authenticated ++ `404 Not Found` if the project ID or the issue ID not found -### Single issue note -Get an issue note. + +## Snippets + +### List all snippet notes + +Gets a list of all notes for a single snippet. Snippet notes are comments users can post to a snippet. ``` -GET /projects/:id/issues/:issue_id/:notes/:note_id +GET /projects/:id/snippets/:snippet_id/notes ``` Parameters: + `id` (required) - The ID of a project -+ `issue_id` (required) - The ID of a project issue -+ `note_id` (required) - The ID of an issue note ++ `snippet_id` (required) - The ID of a project snippet + +Return values: + ++ `200 Ok` on success and a list of notes for a single snippet ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID or issue ID not found + -### Single snippet note +### Get single snippet note -Get a snippet note. +Returns a single note for a given snippet. ``` -GET /projects/:id/issues/:snippet_id/:notes/:note_id +GET /projects/:id/snippets/:snippet_id/notes/:note_id ``` Parameters: @@ -112,52 +180,97 @@ Parameters: + `snippet_id` (required) - The ID of a project snippet + `note_id` (required) - The ID of an snippet note -## New note +Return values: -### New wall note ++ `200 Ok` on success and the single snippet note ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID, snippet ID or note ID is not found -Create a new wall note. + +### Create new snippet note + +Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet. ``` -POST /projects/:id/notes +POST /projects/:id/snippets/:snippet_id/notes ``` Parameters: + `id` (required) - The ID of a project ++ `snippet_id` (required) - The ID of an snippet + `body` (required) - The content of a note -Will return created note with status `201 Created` on success, or `404 Not found` on fail. +Return values: ++ `201 Created` on success and the new snippet note ++ `400 Bad Request` if the required attribute body not given ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID or snippet ID not found -### New issue note -Create a new issue note. + +## Merge Requests + +### List all merge request notes + +Gets a list of all notes for a single merge request. ``` -POST /projects/:id/issues/:issue_id/notes +GET /projects/:id/merge_requests/:merge_request_id/notes ``` Parameters: + `id` (required) - The ID of a project -+ `issue_id` (required) - The ID of an issue -+ `body` (required) - The content of a note ++ `merge_request_id` (required) - The ID of a project merge request + +Return values: -Will return created note with status `201 Created` on success, or `404 Not found` on fail. ++ `200 Ok` on success and a list of notes for a single merge request ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID or merge request ID not found -### New snippet note -Create a new snippet note. +### Get single merge request note + +Returns a single note for a given merge request. ``` -POST /projects/:id/snippets/:snippet_id/notes +GET /projects/:id/merge_requests/:merge_request_id/notes/:note_id ``` Parameters: + `id` (required) - The ID of a project -+ `snippet_id` (required) - The ID of an snippet ++ `merge_request_id` (required) - The ID of a project merge request ++ `note_id` (required) - The ID of a merge request note + +Return values: + ++ `200 Ok` on success and the single merge request note ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID, merge request ID or note ID is not found + + +### Create new merge request note + +Creates a new note for a single merge request. + +``` +POST /projects/:id/merge_requests/:merge_request_id/notes +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `merge_request_id` (required) - The ID of a merge request + `body` (required) - The content of a note -Will return created note with status `201 Created` on success, or `404 Not found` on fail. +Return values: + ++ `201 Created` on success and the new merge request note ++ `400 Bad Request` if the required attribute body not given ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID or merge request ID not found + diff --git a/doc/api/projects.md b/doc/api/projects.md index ed9690f09a2..2e13ccb88d4 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1,4 +1,6 @@ -## List projects +## Projects + +### List projects Get a list of projects owned by the authenticated user. @@ -55,7 +57,13 @@ GET /projects ] ``` -## Single project +Return values: + ++ `200 Ok` on success and a list of projects ++ `401 Unauthorized` if the user is not allowed to access projects + + +### Get single project Get a specific project, identified by project ID, which is owned by the authentication user. @@ -92,9 +100,15 @@ Parameters: } ``` -## Create project +Return Values: + ++ `200 Ok` if the project with given ID is found and the JSON response ++ `404 Not Found` if no project with ID found + -Create new project owned by user +### Create project + +Creates new project owned by user. ``` POST /projects @@ -110,10 +124,15 @@ Parameters: + `merge_requests_enabled` (optional) - enabled by default + `wiki_enabled` (optional) - enabled by default -Will return created project with status `201 Created` on success, or `404 Not -found` on fail. +Return values: + ++ `201 Created` on success with the project data (see example at `GET /projects/:id`) ++ `400 Bad Request` if the required attribute name is not given ++ `403 Forbidden` if the user is not allowed to create a project, e.g. reached the project limit already ++ `404 Not Found` if something else fails + -## List project team members +### List project members Get a list of project team members. @@ -126,7 +145,15 @@ Parameters: + `id` (required) - The ID of a project + `query` - Query string -## Get project team member +Return Values: + ++ `200 Ok` on success and a list of found team members ++ `404 Not Found` if project with ID not found + + +## Team members + +### Get project team member Get a project team member. @@ -141,7 +168,6 @@ Parameters: ```json { - "id": 1, "username": "john_smith", "email": "john@example.com", @@ -152,9 +178,17 @@ Parameters: } ``` -## Add project team member +Return Values: + ++ `200 Ok` on success and the team member, see example ++ `404 Not Found` if either the project or the team member could not be found + -Add a user to a project team. +### Add project team member + +Adds a user to a project team. This is an idempotent method and can be called multiple times +with the same parameters. Adding team membership to a user that is already a member does not +affect the membership. ``` POST /projects/:id/members @@ -166,9 +200,16 @@ Parameters: + `user_id` (required) - The ID of a user to add + `access_level` (required) - Project access level -Will return status `201 Created` on success, or `404 Not found` on fail. +Return Values: + ++ `201 Created` on success and the added user is returned, even if the user is already team member ++ `400 Bad Request` if the required attribute access_level is not given ++ `401 Unauthorized` if the user is not allowed to add a new team member ++ `404 Not Found` if a resource can not be found, e.g. project with ID not available ++ `422 Unprocessable Entity` if an unknown access_level is given + -## Edit project team member +### Edit project team member Update project team member to specified access level. @@ -182,9 +223,16 @@ Parameters: + `user_id` (required) - The ID of a team member + `access_level` (required) - Project access level -Will return status `200 OK` on success, or `404 Not found` on fail. +Return Values: + ++ `200 Ok` on succes and the modified team member ++ `400 Bad Request` if the required attribute access_level is not given ++ `401 Unauthorized` if the user is not allowed to modify a team member ++ `404 Not Found` if a resource can not be found, e.g. project with ID not available ++ `422 Unprocessable Entity` if an unknown access_level is given + -## Remove project team member +### Remove project team member Removes user from project team. @@ -197,11 +245,23 @@ Parameters: + `id` (required) - The ID of a project + `user_id` (required) - The ID of a team member -Status code `200` will be returned on success. +Return Values: -## List project hooks ++ `200 Ok` on success ++ `401 Unauthorized` if user is not allowed to remove a team member ++ `404 Not Found` if either project or user can not be found -Get list for project hooks +This method is idempotent and can be called multiple times with the same parameters. +Revoking team membership for a user who is not currently a team member is considered success. +Please note that the returned JSON currently differs slightly. Thus you should not +rely on the returned JSON structure. + + +## Hooks + +### List project hooks + +Get list of project hooks. ``` GET /projects/:id/hooks @@ -211,11 +271,16 @@ Parameters: + `id` (required) - The ID of a project -Will return hooks with status `200 OK` on success, or `404 Not found` on fail. +Return values: + ++ `200 Ok` on success with a list of hooks ++ `401 Unauthorized` if user is not allowed to get list of hooks ++ `404 Not Found` if project can not be found -## Get project hook -Get hook for project +### Get project hook + +Get a specific hook for project. ``` GET /projects/:id/hooks/:hook_id @@ -226,11 +291,23 @@ Parameters: + `id` (required) - The ID of a project + `hook_id` (required) - The ID of a project hook -Will return hook with status `200 OK` on success, or `404 Not found` on fail. +```json +{ + "id": 1, + "url": "http://example.com/hook", + "created_at": "2012-10-12T17:04:47Z" +} +``` + +Return values: + ++ `200 Ok` on sucess and the hook with the given ID ++ `404 Not Found` if the hook can not be found -## Add project hook -Add hook to project +### Add project hook + +Adds a hook to project. ``` POST /projects/:id/hooks @@ -241,11 +318,17 @@ Parameters: + `id` (required) - The ID of a project + `url` (required) - The hook URL -Will return status `201 Created` on success, or `404 Not found` on fail. +Return values: + ++ `201 Created` on success and the newly created hook ++ `400 Bad Request` if url is not given ++ `404 Not Found` if project with ID not found ++ `422 Unprocessable Entity` if the url is invalid (must begin with `http` or `https`) -## Edit project hook -Edit hook for project +### Edit project hook + +Edits a hook for project. ``` PUT /projects/:id/hooks/:hook_id @@ -257,12 +340,18 @@ Parameters: + `hook_id` (required) - The ID of a project hook + `url` (required) - The hook URL -Will return status `201 Created` on success, or `404 Not found` on fail. +Return values: + ++ `200 Ok` on success and the modified hook (see JSON response above) ++ `400 Bad Request` if the url attribute is not given ++ `404 Not Found` if project or hook can not be found ++ `422 Unprocessable Entity` if the url is invalid (must begin with `http` or `https`) -## Delete project hook +### Delete project hook -Delete hook from project +Removes a hook from project. This is an idempotent method and can be called multiple times. +Either the hook is available or not. ``` DELETE /projects/:id/hooks/:hook_id @@ -273,4 +362,235 @@ Parameters: + `id` (required) - The ID of a project + `hook_id` (required) - The ID of hook to delete -Will return status `200 OK` on success, or `404 Not found` on fail. +Return values: + ++ `200 Ok` on succes ++ `404 Not Found` if the project can not be found + +Note the JSON response differs if the hook is available or not. If the project hook +is available before it is returned in the JSON response or an empty response is returned. + + +## Branches + +### List branches + +Lists all branches of a project. + +``` +GET /projects/:id/repository/branches +``` + +Parameters: + ++ `id` (required) - The ID of the project + +Return values: + ++ `200 Ok` on success and a list of branches ++ `404 Not Found` if project is not found + + +### List single branch + +Lists a specific branch of a project. + +``` +GET /projects/:id/repository/branches/:branch +``` + +Parameters: + ++ `id` (required) - The ID of the project. ++ `branch` (required) - The name of the branch. + +Return values: + ++ `200 Ok` on success ++ `404 Not Found` if either project with ID or branch could not be found + + +### Protect single branch + +Protects a single branch of a project. + +``` +PUT /projects/:id/repository/branches/:branch/protect +``` + +Parameters: + ++ `id` (required) - The ID of the project. ++ `branch` (required) - The name of the branch. + +Return values: + ++ `200 Ok` on success ++ `404 Not Found` if either project or branch could not be found + + +### Unprotect single branch + +Unprotects a single branch of a project. + +``` +PUT /projects/:id/repository/branches/:branch/unprotect +``` + +Parameters: + ++ `id` (required) - The ID of the project. ++ `branch` (required) - The name of the branch. + +Return values: + ++ `200 Ok` on success ++ `404 Not Found` if either project or branch could not be found + + +### List tags + +Lists all tags of a project. + +``` +GET /projects/:id/repository/tags +``` + +Parameters: + ++ `id` (required) - The ID of the project + +Return values: + ++ `200 Ok` on success and a list of tags ++ `404 Not Found` if project with id not found + + +### List commits + +Lists all commits with pagination. If the optional `ref_name` name is not given the commits of +the default branch (usually master) are returned. + +``` +GET /projects/:id/repository/commits +``` + +Parameters: + ++ `id` (required) - The Id of the project ++ `ref_name` (optional) - The name of a repository branch or tag ++ `page` (optional) - The page of commits to return (`0` default) ++ `per_page` (optional) - The number of commits per page (`20` default) + +Returns values: + ++ `200 Ok` on success and a list with commits ++ `404 Not Found` if project with id or the branch with `ref_name` not found + + +## Snippets + +### List snippets + +Lists the snippets of a project. + +``` +GET /projects/:id/snippets +``` + +Parameters: + ++ `id` (required) - The ID of the project + +Return values: + ++ `200 Ok` on success and the list of snippets ++ `404 Not Found` if project with id not found + + +### List single snippet + +Lists a single snippet of a project + +``` +GET /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID of the project ++ `snippet_id` (required) - The ID of the snippet + +Return values: + ++ `200 Ok` on success and the project snippet ++ `404 Not Found` if project ID or snippet ID not found + + +### Create snippet + +Creates a new project snippet. + +``` +POST /projects/:id/snippets +``` + +Parameters: + ++ `id` (required) - The ID of the project ++ `title` (required) - The title of the new snippet ++ `file_name` (required) - The file name of the snippet ++ `code` (required) - The content of the snippet ++ `lifetime` (optional) - The expiration date of a snippet + +Return values: + ++ `201 Created` on success and the new snippet ++ `400 Bad Request` if one of the required attributes is missing ++ `401 Unauthorized` if it is not allowed to post a new snippet ++ `404 Not Found` if the project ID is not found + + +### Update snippet + +Updates an existing project snippet. + +``` +PUT /projects/:id/snippets/:snippet_id +``` + +Parameters: + ++ `id` (required) - The ID of the project ++ `snippet_id` (required) - The id of the project snippet ++ `title` (optional) - The new title of the project snippet ++ `file_name` (optional) - The new file name of the project snippet ++ `lifetime` (optional) - The new expiration date of the snippet ++ `code` (optional) - The content of the snippet + +Return values: + ++ `200 Ok` on success and the content of the updated snippet ++ `401 Unauthorized` if the user is not allowed to modify the snippet ++ `404 Not Found` if project ID or snippet ID is not found + + +## Delete snippet + +Deletes a project snippet. This is an idempotent function call and returns `200 Ok` +even if the snippet with the id is not available. + +``` +DELETE /projects/:id/snippets/:snippet_id +``` + +Paramaters: + ++ `id` (required) - The ID of the project ++ `snippet_id` (required) - The ID of the snippet + +Return values: + ++ `200 Ok` on success, if the snippet got deleted it is returned, if not available then an empty JSON response ++ `401 Unauthorized` if the user is not allowed to remove the snippet ++ `404 Not Found` if the project ID not found diff --git a/doc/api/snippets.md b/doc/api/snippets.md index ceb8a63d06f..61dbb5e454b 100644 --- a/doc/api/snippets.md +++ b/doc/api/snippets.md @@ -10,9 +10,15 @@ Parameters: + `id` (required) - The ID of a project +Return values: + ++ `200 Ok` on success and a list of project snippets ++ `401 Unauthorized` if user is not authenticated + + ## Single snippet -Get a project snippet. +Get a single project snippet. ``` GET /projects/:id/snippets/:snippet_id @@ -42,22 +48,16 @@ Parameters: } ``` -## Snippet content +Return values: -Get a raw project snippet. ++ `200 Ok` on success and the project snippet ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if snippet ID not found -``` -GET /projects/:id/snippets/:snippet_id/raw -``` -Parameters: +## Create new snippet -+ `id` (required) - The ID of a project -+ `snippet_id` (required) - The ID of a project's snippet - -## New snippet - -Create a new project snippet. +Creates a new project snippet. ``` POST /projects/:id/snippets @@ -71,11 +71,17 @@ Parameters: + `lifetime` (optional) - The expiration date of a snippet + `code` (required) - The content of a snippet -Will return created snippet with status `201 Created` on success, or `404 Not found` on fail. +Return values: + ++ `201 Created` if snippet was successfully created and the snippet as JSON payload ++ `400 Bad Request` if one of the required attributes is not given ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID not found + ## Edit snippet -Update an existing project snippet. +Updates an existing project snippet. ``` PUT /projects/:id/snippets/:snippet_id @@ -90,11 +96,17 @@ Parameters: + `lifetime` (optional) - The expiration date of a snippet + `code` (optional) - The content of a snippet -Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail. +Return values: + ++ `200 Ok` on success and the updated project snippet ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID not found + ## Delete snippet -Delete existing project snippet. +Deletes an existing project snippet. This is an idempotent function and deleting a non-existent +snippet still returns a `200 Ok` status code. ``` DELETE /projects/:id/snippets/:snippet_id @@ -105,5 +117,28 @@ Parameters: + `id` (required) - The ID of a project + `snippet_id` (required) - The ID of a project's snippet -Status code `200` will be returned on success. +Return values: + ++ `200 Ok` on success and if the snippet was deleted its content ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID not found + + +## Snippet content + +Get a raw project snippet. + +``` +GET /projects/:id/snippets/:snippet_id/raw +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `snippet_id` (required) - The ID of a project's snippet + +Return values: ++ `200 Ok` on success and the raw snippet ++ `401 Unauthorized` if user is not authenticated ++ `404 Not Found` if project ID or snippet ID is not found
\ No newline at end of file diff --git a/doc/api/users.md b/doc/api/users.md index b94d7c0f789..96aebffafd8 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -43,6 +43,12 @@ GET /users ] ``` +Return values: + ++ `200 Ok` on success and a list with all users ++ `401 Unauthorized` if user is not allowed to access the list + + ## Single user Get a single user. @@ -74,37 +80,55 @@ Parameters: } ``` +Return values: + ++ `200 Ok` on success and the user entry ++ `401 Unauthorized` if it is not allowed to access the user ++ `404 Not Found` if the user with ID is not found + + ## User creation -Create user. Available only for admin + +Creates a new user. Note only administrators can create new users. ``` POST /users ``` Parameters: -+ `email` (required) - Email -+ `password` (required) - Password -+ `username` (required) - Username -+ `name` (required) - Name -+ `skype` - Skype ID -+ `linkedin` - Linkedin -+ `twitter` - Twitter account -+ `projects_limit` - Number of projects user can create -+ `extern_uid` - External UID -+ `provider` - External provider name -+ `bio` - User's bio -Will return created user with status `201 Created` on success, or `404 Not -found` on fail. ++ `email` (required) - Email ++ `password` (required) - Password ++ `username` (required) - Username ++ `name` (required) - Name ++ `skype` (optional) - Skype ID ++ `linkedin` (optional) - Linkedin ++ `twitter` (optional) - Twitter account ++ `projects_limit` (optional) - Number of projects user can create ++ `extern_uid` (optional) - External UID ++ `provider` (optional) - External provider name ++ `bio` (optional) - User's bio + +Return values: + ++ `201 Created` on success and returns the new user ++ `400 Bad Request` if one of the required attributes is missing from the request ++ `401 Unauthorized` if the user is not authorized ++ `403 Forbidden` if the user is not allowed to create a new user (must be admin) ++ `404 Not Found` if something else fails ++ `409 Conflict` if a user with the same email address or username already exists + ## User modification -Modify user. Available only for admin + +Modifies an existing user. Only administrators can change attributes of a user. ``` PUT /users/:id ``` Parameters: + + `email` - Email + `username` - Username + `name` - Name @@ -117,23 +141,42 @@ Parameters: + `provider` - External provider name + `bio` - User's bio +Return values: + ++ `200 Ok` on success and returns the new user ++ `401 Unauthorized` if the user is not authorized ++ `403 Forbidden` if the user is not allowed to create a new user (must be admin) ++ `404 Not Found` if something else fails + +Note, at the moment this method does only return a 404 error, even in cases where a 409 (Conflict) would +be more appropriate, e.g. when renaming the email address to some exsisting one. -Will return created user with status `200 OK` on success, or `404 Not -found` on fail. ## User deletion -Delete user. Available only for admin + +Deletes a user. Available only for administrators. This is an idempotent function, calling this function +for a non-existent user id still returns a status code `200 Ok`. The JSON response differs if the user +was actually deleted or not. In the former the user is returned and in the latter not. ``` DELETE /users/:id ``` -Will return deleted user with status `200 OK` on success, or `404 Not -found` on fail. +Parameters: + ++ `id` (required) - The ID of the user + +Return values: + ++ `200 Ok` on success and returns the deleted user ++ `401 Unauthorized` if the user is not authorized ++ `403 Forbidden` if the user is not allowed to create a new user (must be admin) ++ `404 Not Found` if user with ID not found or something else fails + ## Current user -Get currently authenticated user. +Gets currently authenticated user. ``` GET /user @@ -156,6 +199,13 @@ GET /user } ``` +Return values: + ++ `200 Ok` on success and returns the current user ++ `401 Unauthorized` if the user is not authorized ++ `404 Not Found` if something else fails + + ## List SSH keys Get a list of currently authenticated user's SSH keys. @@ -183,6 +233,17 @@ GET /user/keys ] ``` +Parameters: + ++ **none** + +Return values: + ++ `200 Ok` on success and a list of ssh keys ++ `401 Unauthorized` if the user is not authenticated ++ `404 Not Found` if something else fails + + ## Single SSH key Get a single key. @@ -204,9 +265,17 @@ Parameters: soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" } ``` + +Return values: + ++ `200 Ok` on success and the ssh key with ID ++ `401 Unauthorized` if it is not allowed to access the user ++ `404 Not Found` if the ssh key with ID not found + + ## Add SSH key -Create new key owned by currently authenticated user +Creates a new key owned by the currently authenticated user. ``` POST /user/keys @@ -217,12 +286,18 @@ Parameters: + `title` (required) - new SSH Key's title + `key` (required) - new SSH key -Will return created key with status `201 Created` on success, or `404 Not -found` on fail. +Return values: + ++ `201 Created` on success and the added key ++ `400 Bad Request` if one of the required attributes is not given ++ `401 Unauthorized` if user is not authorized to add ssh key ++ `404 Not Found` if something else fails + ## Delete SSH key -Delete key owned by currently authenticated user +Deletes key owned by currently authenticated user. This is an idempotent function and calling it on a key that is already +deleted or not available results in `200 Ok`. ``` DELETE /user/keys/:id @@ -232,4 +307,8 @@ Parameters: + `id` (required) - SSH key ID -Will return `200 OK` on success, or `404 Not Found` on fail. +Return values: + ++ `200 Ok` on success ++ `401 Unauthorized` if user is not allowed to delete they key ++ `404 Not Found` if something else fails diff --git a/lib/api.rb b/lib/api.rb index d9dce7c70cc..ffd980ca7e0 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -8,6 +8,19 @@ module Gitlab rack_response({'message' => '404 Not found'}.to_json, 404) end + rescue_from :all do |exception| + # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 + # why is this not wrapped in something reusable? + trace = exception.backtrace + + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << trace.join("\n ") + + API.logger.add Logger::FATAL, message + rack_response({'message' => '500 Internal Server Error'}, 500) + end + format :json error_format :json helpers APIHelpers diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6bd8111c2b2..becb3bce5b0 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -55,6 +55,12 @@ module Gitlab render_api_error!('403 Forbidden', 403) end + def bad_request!(attribute) + message = ["400 (Bad request)"] + message << "\"" + attribute.to_s + "\" not given" + render_api_error!(message.join(' '), 400) + end + def not_found!(resource = nil) message = ["404"] message << resource if resource diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 7f763eb49d5..4b28094f1a4 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -5,6 +5,23 @@ module Gitlab resource :projects do + helpers do + # If an error occurred this helper method provides an appropriate status code + # + # Parameters: + # merge_request_errors (required) - The errors collection of MR + # + def handle_merge_request_error(merge_request_errors) + if merge_request_errors[:target_branch].any? + bad_request!(:target_branch) + elsif merge_request_errors[:source_branch].any? + bad_request!(:source_branch) + elsif merge_request_errors[:base].any? + error!(merge_request_errors[:base], 422) + end + end + end + # List merge requests # # Parameters: @@ -60,6 +77,7 @@ module Gitlab merge_request.reload_code present merge_request, with: Entities::MergeRequest else + handle_merge_request_error(merge_request.errors) not_found! end end @@ -88,6 +106,7 @@ module Gitlab merge_request.mark_as_unchecked present merge_request, with: Entities::MergeRequest else + handle_merge_request_error(merge_request.errors) not_found! end end @@ -109,6 +128,9 @@ module Gitlab if note.save present note, with: Entities::MRNote else + if note.errors[:note].any? + bad_request!(:note) + end not_found! end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index eaf0d37c18b..ff98f005180 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -42,6 +42,8 @@ module Gitlab post ":id/milestones" do authorize! :admin_milestone, user_project + bad_request!(:title) unless params[:title].present? + attrs = attributes_for_keys [:title, :description, :due_date] @milestone = user_project.milestones.new attrs if @milestone.save diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 70344d6e381..953514b6f04 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -37,12 +37,16 @@ module Gitlab # Example Request: # POST /projects/:id/notes post ":id/notes" do + bad_request!(:body) unless params[:body].present? + @note = user_project.notes.new(note: params[:body]) @note.author = current_user if @note.save present @note, with: Entities::Note else + # :note is exposed as :body, but :note is set on error + bad_request!(:note) if @note.errors[:note].any? not_found! end end @@ -89,6 +93,9 @@ module Gitlab # POST /projects/:id/issues/:noteable_id/notes # POST /projects/:id/snippets/:noteable_id/notes post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + bad_request!(:"#{noteable_id_str}") unless params[:"#{noteable_id_str}"].present? + bad_request!(:body) unless params[:body].present? + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) @note = @noteable.notes.new(note: params[:body]) @note.author = current_user diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 631ed535459..65381dac6ac 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -4,6 +4,15 @@ module Gitlab before { authenticate! } resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + # Get a projects list for authenticated user # # Example Request: @@ -36,6 +45,7 @@ module Gitlab # Example Request # POST /projects post do + bad_request!(:name) if !params.has_key? :name attrs = attributes_for_keys [:name, :description, :default_branch, @@ -43,10 +53,14 @@ module Gitlab :wall_enabled, :merge_requests_enabled, :wiki_enabled] + @project = ::Projects::CreateContext.new(current_user, attrs).execute if @project.saved? present @project, with: Entities::Project else + if @project.errors[:limit_reached].present? + error!(@project.errors[:limit_reached], 403) + end not_found! end end @@ -89,16 +103,24 @@ module Gitlab # POST /projects/:id/members post ":id/members" do authorize! :admin_project, user_project - users_project = user_project.users_projects.new( - user_id: params[:user_id], - project_access: params[:access_level] - ) - if users_project.save - @member = users_project.user + bad_request!(:user_id) if !params.has_key? :user_id + bad_request!(:access_level) if !params.has_key? :access_level + + # either the user is already a team member or a new one + team_member = user_project.team_member_by_id(params[:user_id]) + if team_member.nil? + team_member = user_project.users_projects.new( + user_id: params[:user_id], + project_access: params[:access_level] + ) + end + + if team_member.save + @member = team_member.user present @member, with: Entities::ProjectMember, project: user_project else - not_found! + handle_project_member_errors team_member.errors end end @@ -112,13 +134,16 @@ module Gitlab # PUT /projects/:id/members/:user_id put ":id/members/:user_id" do authorize! :admin_project, user_project - users_project = user_project.users_projects.find_by_user_id params[:user_id] - if users_project.update_attributes(project_access: params[:access_level]) - @member = users_project.user + team_member = user_project.users_projects.find_by_user_id(params[:user_id]) + bad_request!(:access_level) if !params.has_key? :access_level + not_found!("User can not be found") if team_member.nil? + + if team_member.update_attributes(project_access: params[:access_level]) + @member = team_member.user present @member, with: Entities::ProjectMember, project: user_project else - not_found! + handle_project_member_errors team_member.errors end end @@ -131,8 +156,12 @@ module Gitlab # DELETE /projects/:id/members/:user_id delete ":id/members/:user_id" do authorize! :admin_project, user_project - users_project = user_project.users_projects.find_by_user_id params[:user_id] - users_project.destroy + team_member = user_project.users_projects.find_by_user_id(params[:user_id]) + unless team_member.nil? + team_member.destroy + else + {:message => "Access revoked", :id => params[:user_id].to_i} + end end # Get project hooks @@ -170,11 +199,17 @@ module Gitlab # POST /projects/:id/hooks post ":id/hooks" do authorize! :admin_project, user_project + + bad_request!(:url) unless params.has_key? :url + @hook = user_project.hooks.new({"url" => params[:url]}) if @hook.save present @hook, with: Entities::Hook else - error!({'message' => '404 Not found'}, 404) + if @hook.errors[:url].present? + error!("Invalid url given", 422) + end + not_found! end end @@ -190,11 +225,15 @@ module Gitlab @hook = user_project.hooks.find(params[:hook_id]) authorize! :admin_project, user_project - attrs = attributes_for_keys [:url] + bad_request!(:url) unless params.has_key? :url + attrs = attributes_for_keys [:url] if @hook.update_attributes attrs present @hook, with: Entities::Hook else + if @hook.errors[:url].present? + error!("Invalid url given", 422) + end not_found! end end @@ -208,8 +247,13 @@ module Gitlab # DELETE /projects/:id/hooks/:hook_id delete ":id/hooks/:hook_id" do authorize! :admin_project, user_project - @hook = user_project.hooks.find(params[:hook_id]) - @hook.destroy + bad_request!(:hook_id) unless params.has_key? :hook_id + + begin + @hook = ProjectHook.find(params[:hook_id]) + @hook.destroy + rescue + end end # Get a project repository branches @@ -244,6 +288,7 @@ module Gitlab # PUT /projects/:id/repository/branches/:branch/protect put ":id/repository/branches/:branch/protect" do @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } + not_found! unless @branch protected = user_project.protected_branches.find_by_name(@branch.name) unless protected @@ -262,6 +307,7 @@ module Gitlab # PUT /projects/:id/repository/branches/:branch/unprotect put ":id/repository/branches/:branch/unprotect" do @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } + not_found! unless @branch protected = user_project.protected_branches.find_by_name(@branch.name) if protected @@ -334,6 +380,10 @@ module Gitlab post ":id/snippets" do authorize! :write_snippet, user_project + bad_request!(:title) if !params[:title].present? + bad_request!(:file_name) if !params[:file_name].present? + bad_request!(:code) if !params[:code].present? + attrs = attributes_for_keys [:title, :file_name] attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? attrs[:content] = params[:code] if params[:code].present? @@ -381,10 +431,12 @@ module Gitlab # Example Request: # DELETE /projects/:id/snippets/:snippet_id delete ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.find(params[:snippet_id]) - authorize! :modify_snippet, @snippet - - @snippet.destroy + begin + @snippet = user_project.snippets.find(params[:snippet_id]) + authorize! :modify_snippet, user_project + @snippet.destroy + rescue + end end # Get a raw project snippet @@ -411,6 +463,8 @@ module Gitlab get ":id/repository/commits/:sha/blob" do authorize! :download_code, user_project + bad_request!(:filepath) if !params.has_key? :filepath + ref = params[:sha] commit = user_project.repository.commit ref diff --git a/lib/api/users.rb b/lib/api/users.rb index 7ea90c75e9e..b9dce58a13d 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -41,6 +41,12 @@ module Gitlab # POST /users post do authenticated_as_admin! + + bad_request!(:email) if !params.has_key? :email + bad_request!(:password) if !params.has_key? :password + bad_request!(:name) if !params.has_key? :name + bad_request!(:username) if !params.has_key? :username + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] user = User.new attrs, as: :admin if user.save @@ -67,10 +73,12 @@ module Gitlab # PUT /users/:id put ":id" do authenticated_as_admin! + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] - user = User.find_by_id(params[:id]) + user = User.find(params[:id]) + not_found!("User not found") unless user - if user && user.update_attributes(attrs) + if user.update_attributes(attrs) present user, with: Entities::User else not_found! @@ -127,6 +135,9 @@ module Gitlab # Example Request: # POST /user/keys post "keys" do + bad_request!(:title) unless params[:title].present? + bad_request!(:key) unless params[:key].present? + attrs = attributes_for_keys [:title, :key] key = current_user.keys.new attrs if key.save @@ -136,15 +147,18 @@ module Gitlab end end - # Delete existed ssh key of currently authenticated user + # Delete existing ssh key of currently authenticated user # # Parameters: # id (required) - SSH Key ID # Example Request: # DELETE /user/keys/:id delete "keys/:id" do - key = current_user.keys.find params[:id] - key.delete + begin + key = current_user.keys.find params[:id] + key.delete + rescue + end end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 48432eac147..23f1c6df0cf 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -64,7 +64,7 @@ describe Project do it "should not allow new projects beyond user limits" do project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1)) project.should_not be_valid - project.errors[:base].first.should match(/Your own projects limit is 1/) + project.errors[:limit_reached].first.should match(/Your own projects limit is 1/) end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 1abd7a20dec..8de06c33394 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -32,6 +32,11 @@ describe Gitlab::API do response.status.should == 200 json_response['title'].should == merge_request.title end + + it "should return a 404 error if merge_request_id not found" do + get api("/projects/#{project.id}/merge_request/999", user) + response.status.should == 404 + end end describe "POST /projects/:id/merge_requests" do @@ -41,6 +46,24 @@ describe Gitlab::API do response.status.should == 201 json_response['title'].should == 'Test merge_request' end + + it "should return 422 when source_branch equals target_branch" do + post api("/projects/#{project.id}/merge_requests", user), + title: "Test merge_request", source_branch: "master", target_branch: "master", author: user + response.status.should == 422 + end + + it "should return 400 when source_branch is missing" do + post api("/projects/#{project.id}/merge_requests", user), + title: "Test merge_request", target_branch: "master", author: user + response.status.should == 400 + end + + it "should return 400 when target_branch is missing" do + post api("/projects/#{project.id}/merge_requests", user), + title: "Test merge_request", source_branch: "stable", author: user + response.status.should == 400 + end end describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do @@ -66,6 +89,18 @@ describe Gitlab::API do response.status.should == 200 json_response['title'].should == 'New title' end + + it "should return 422 when source_branch and target_branch are renamed the same" do + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), + source_branch: "master", target_branch: "master" + response.status.should == 422 + end + + it "should return merge_request with renamed target_branch" do + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "test" + response.status.should == 200 + json_response['target_branch'].should == 'test' + end end describe "POST /projects/:id/merge_request/:merge_request_id/comments" do @@ -74,6 +109,16 @@ describe Gitlab::API do response.status.should == 201 json_response['note'].should == 'My comment' end + + it "should return 400 if note is missing" do + post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user) + response.status.should == 400 + end + + it "should return 404 if note is attached to non existent merge request" do + post api("/projects/#{project.id}/merge_request/111/comments", user), note: "My comment" + response.status.should == 404 + end end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index d1b5e449bc5..c379e8a5307 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -16,6 +16,11 @@ describe Gitlab::API do json_response.should be_an Array json_response.first['title'].should == milestone.title end + + it "should return a 401 error if user not authenticated" do + get api("/projects/#{project.id}/milestones") + response.status.should == 401 + end end describe "GET /projects/:id/milestones/:milestone_id" do @@ -24,16 +29,38 @@ describe Gitlab::API do response.status.should == 200 json_response['title'].should == milestone.title end + + it "should return 401 error if user not authenticated" do + get api("/projects/#{project.id}/milestones/#{milestone.id}") + response.status.should == 401 + end + + it "should return a 404 error if milestone id not found" do + get api("/projects/#{project.id}/milestones/1234", user) + response.status.should == 404 + end end describe "POST /projects/:id/milestones" do it "should create a new project milestone" do - post api("/projects/#{project.id}/milestones", user), - title: 'new milestone' + post api("/projects/#{project.id}/milestones", user), title: 'new milestone' response.status.should == 201 json_response['title'].should == 'new milestone' json_response['description'].should be_nil end + + it "should create a new project milestone with description and due date" do + post api("/projects/#{project.id}/milestones", user), + title: 'new milestone', description: 'release', due_date: '2013-03-02' + response.status.should == 201 + json_response['description'].should == 'release' + json_response['due_date'].should == '2013-03-02' + end + + it "should return a 400 error if title is missing" do + post api("/projects/#{project.id}/milestones", user) + response.status.should == 400 + end end describe "PUT /projects/:id/milestones/:milestone_id" do @@ -43,6 +70,12 @@ describe Gitlab::API do response.status.should == 200 json_response['title'].should == 'updated title' end + + it "should return a 404 error if milestone id not found" do + put api("/projects/#{project.id}/milestones/1234", user), + title: 'updated title' + response.status.should == 404 + end end describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index ee99d85df4d..92ac5befc3a 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -38,6 +38,11 @@ describe Gitlab::API do response.status.should == 200 json_response['body'].should == wall_note.note end + + it "should return a 404 error if note not found" do + get api("/projects/#{project.id}/notes/123", user) + response.status.should == 404 + end end describe "POST /projects/:id/notes" do @@ -46,6 +51,16 @@ describe Gitlab::API do response.status.should == 201 json_response['body'].should == 'hi!' end + + it "should return 401 unauthorized error" do + post api("/projects/#{project.id}/notes") + response.status.should == 401 + end + + it "should return a 400 bad request if body is missing" do + post api("/projects/#{project.id}/notes", user) + response.status.should == 400 + end end describe "GET /projects/:id/noteable/:noteable_id/notes" do @@ -56,6 +71,11 @@ describe Gitlab::API do json_response.should be_an Array json_response.first['body'].should == issue_note.note end + + it "should return a 404 error when issue id not found" do + get api("/projects/#{project.id}/issues/123/notes", user) + response.status.should == 404 + end end context "when noteable is a Snippet" do @@ -65,6 +85,11 @@ describe Gitlab::API do json_response.should be_an Array json_response.first['body'].should == snippet_note.note end + + it "should return a 404 error when snippet id not found" do + get api("/projects/#{project.id}/snippets/42/notes", user) + response.status.should == 404 + end end context "when noteable is a Merge Request" do @@ -74,6 +99,18 @@ describe Gitlab::API do json_response.should be_an Array json_response.first['body'].should == merge_request_note.note end + + it "should return a 404 error if merge request id not found" do + get api("/projects/#{project.id}/merge_requests/4444/notes", user) + response.status.should == 404 + end + end + + context "when notable is invalid" do + it "should return a 404 error" do + get api("/projects/#{project.id}/unknown/#{snippet.id}/notes", user) + response.status.should == 404 + end end end @@ -84,6 +121,11 @@ describe Gitlab::API do response.status.should == 200 json_response['body'].should == issue_note.note end + + it "should return a 404 error if issue note not found" do + get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) + response.status.should == 404 + end end context "when noteable is a Snippet" do @@ -92,6 +134,11 @@ describe Gitlab::API do response.status.should == 200 json_response['body'].should == snippet_note.note end + + it "should return a 404 error if snippet note not found" do + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user) + response.status.should == 404 + end end end @@ -103,6 +150,16 @@ describe Gitlab::API do json_response['body'].should == 'hi!' json_response['author']['email'].should == user.email end + + it "should return a 400 bad request error if body not given" do + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user) + response.status.should == 400 + end + + it "should return a 401 unauthorized error if user not authenticated" do + post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!' + response.status.should == 401 + end end context "when noteable is a Snippet" do @@ -112,6 +169,23 @@ describe Gitlab::API do json_response['body'].should == 'hi!' json_response['author']['email'].should == user.email end + + it "should return a 400 bad request error if body not given" do + post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) + response.status.should == 400 + end + + it "should return a 401 unauthorized error if user not authenticated" do + post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' + response.status.should == 401 + end + end + + context "when noteable is invalid" do + it "should return a 404 error" do + post api("/projects/#{project.id}/invalid/#{snippet.id}/notes", user) + response.status.should == 404 + end end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index d410885bd22..8ab7d825a20 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -6,8 +6,8 @@ describe Gitlab::API do let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } - let!(:hook) { create(:project_hook, project: project, url: "http://example.com") } let!(:project) { create(:project, namespace: user.namespace ) } + let!(:hook) { create(:project_hook, project: project, url: "http://example.com") } let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') } let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } @@ -55,6 +55,11 @@ describe Gitlab::API do expect { post api("/projects", user) }.to_not change {Project.count} end + it "should return a 400 error if name not given" do + post api("/projects", user) + response.status.should == 400 + end + it "should create last project before reaching project limit" do (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" } post api("/projects", user2), name: "foo" @@ -66,9 +71,17 @@ describe Gitlab::API do response.status.should == 201 end - it "should respond with 404 on failure" do + it "should respond with 400 if name is not given" do post api("/projects", user) - response.status.should == 404 + response.status.should == 400 + end + + it "should return a 403 error if project limit reached" do + (1..user.projects_limit).each do |p| + post api("/projects", user), name: "foo#{p}" + end + post api("/projects", user), name: 'bar' + response.status.should == 403 end it "should assign attributes to project" do @@ -109,6 +122,12 @@ describe Gitlab::API do response.status.should == 404 json_response['message'].should == '404 Not Found' end + + it "should return a 404 error if user is not a member" do + other_user = create(:user) + get api("/projects/#{project.id}", other_user) + response.status.should == 404 + end end describe "GET /projects/:id/repository/branches" do @@ -145,6 +164,17 @@ describe Gitlab::API do json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' json_response['protected'].should == true end + + it "should return a 404 error if branch not found" do + put api("/projects/#{project.id}/repository/branches/unknown/protect", user) + response.status.should == 404 + end + + it "should return success when protect branch again" do + put api("/projects/#{project.id}/repository/branches/new_design/protect", user) + put api("/projects/#{project.id}/repository/branches/new_design/protect", user) + response.status.should == 200 + end end describe "PUT /projects/:id/repository/branches/:branch/unprotect" do @@ -156,6 +186,17 @@ describe Gitlab::API do json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' json_response['protected'].should == false end + + it "should return success when unprotect branch" do + put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) + response.status.should == 404 + end + + it "should return success when unprotect branch again" do + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user) + response.status.should == 200 + end end describe "GET /projects/:id/members" do @@ -174,6 +215,11 @@ describe Gitlab::API do json_response.count.should == 1 json_response.first['email'].should == user.email end + + it "should return a 404 error if id not found" do + get api("/projects/9999/members", user) + response.status.should == 404 + end end describe "GET /projects/:id/members/:user_id" do @@ -183,6 +229,11 @@ describe Gitlab::API do json_response['email'].should == user.email json_response['access_level'].should == UsersProject::MASTER end + + it "should return a 404 error if user id not found" do + get api("/projects/#{project.id}/members/1234", user) + response.status.should == 404 + end end describe "POST /projects/:id/members" do @@ -196,6 +247,34 @@ describe Gitlab::API do json_response['email'].should == user2.email json_response['access_level'].should == UsersProject::DEVELOPER end + + it "should return a 201 status if user is already project member" do + post api("/projects/#{project.id}/members", user), user_id: user2.id, + access_level: UsersProject::DEVELOPER + expect { + post api("/projects/#{project.id}/members", user), user_id: user2.id, + access_level: UsersProject::DEVELOPER + }.not_to change { UsersProject.count }.by(1) + + response.status.should == 201 + json_response['email'].should == user2.email + json_response['access_level'].should == UsersProject::DEVELOPER + end + + it "should return a 400 error when user id is not given" do + post api("/projects/#{project.id}/members", user), access_level: UsersProject::MASTER + response.status.should == 400 + end + + it "should return a 400 error when access level is not given" do + post api("/projects/#{project.id}/members", user), user_id: user2.id + response.status.should == 400 + end + + it "should return a 422 error when access level is not known" do + post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234 + response.status.should == 422 + end end describe "PUT /projects/:id/members/:user_id" do @@ -205,6 +284,21 @@ describe Gitlab::API do json_response['email'].should == user3.email json_response['access_level'].should == UsersProject::MASTER end + + it "should return a 404 error if user_id is not found" do + put api("/projects/#{project.id}/members/1234", user), access_level: UsersProject::MASTER + response.status.should == 404 + end + + it "should return a 400 error when access level is not given" do + put api("/projects/#{project.id}/members/#{user3.id}", user) + response.status.should == 400 + end + + it "should return a 422 error when access level is not known" do + put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123 + response.status.should == 422 + end end describe "DELETE /projects/:id/members/:user_id" do @@ -213,6 +307,30 @@ describe Gitlab::API do delete api("/projects/#{project.id}/members/#{user3.id}", user) }.to change { UsersProject.count }.by(-1) end + + it "should return 200 if team member is not part of a project" do + delete api("/projects/#{project.id}/members/#{user3.id}", user) + expect { + delete api("/projects/#{project.id}/members/#{user3.id}", user) + }.to_not change { UsersProject.count }.by(1) + end + + it "should return 200 if team member already removed" do + delete api("/projects/#{project.id}/members/#{user3.id}", user) + delete api("/projects/#{project.id}/members/#{user3.id}", user) + response.status.should == 200 + end + end + + describe "DELETE /projects/:id/members/:user_id" do + it "should return 200 OK when the user was not member" do + expect { + delete api("/projects/#{project.id}/members/1000000", user) + }.to change { UsersProject.count }.by(0) + response.status.should == 200 + json_response['message'].should == "Access revoked" + json_response['id'].should == 1000000 + end end describe "GET /projects/:id/hooks" do @@ -255,6 +373,11 @@ describe Gitlab::API do response.status.should == 403 end end + + it "should return a 404 error if hook id is not available" do + get api("/projects/#{project.id}/hooks/1234", user) + response.status.should == 404 + end end describe "POST /projects/:id/hooks" do @@ -263,6 +386,17 @@ describe Gitlab::API do post api("/projects/#{project.id}/hooks", user), url: "http://example.com" }.to change {project.hooks.count}.by(1) + response.status.should == 201 + end + + it "should return a 400 error if url not given" do + post api("/projects/#{project.id}/hooks", user) + response.status.should == 400 + end + + it "should return a 422 error if url not valid" do + post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" + response.status.should == 422 end end @@ -273,6 +407,21 @@ describe Gitlab::API do response.status.should == 200 json_response['url'].should == 'http://example.org' end + + it "should return 404 error if hook id not found" do + put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' + response.status.should == 404 + end + + it "should return 400 error if url is not given" do + put api("/projects/#{project.id}/hooks/#{hook.id}", user) + response.status.should == 400 + end + + it "should return a 422 error if url is not valid" do + put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' + response.status.should == 422 + end end describe "DELETE /projects/:id/hooks/:hook_id" do @@ -280,6 +429,17 @@ describe Gitlab::API do expect { delete api("/projects/#{project.id}/hooks/#{hook.id}", user) }.to change {project.hooks.count}.by(-1) + response.status.should == 200 + end + + it "should return success when deleting hook" do + delete api("/projects/#{project.id}/hooks/#{hook.id}", user) + response.status.should == 200 + end + + it "should return success when deleting non existent hook" do + delete api("/projects/#{project.id}/hooks/42", user) + response.status.should == 200 end end @@ -320,6 +480,11 @@ describe Gitlab::API do json_response.should be_an Array json_response.first['title'].should == snippet.title end + + it "should return 401 error if user not authenticated" do + get api("/projects/#{project.id}/snippets") + response.status.should == 401 + end end describe "GET /projects/:id/snippets/:snippet_id" do @@ -328,6 +493,11 @@ describe Gitlab::API do response.status.should == 200 json_response['title'].should == snippet.title end + + it "should return a 404 error if snippet id not found" do + get api("/projects/#{project.id}/snippets/1234", user) + response.status.should == 404 + end end describe "POST /projects/:id/snippets" do @@ -337,6 +507,30 @@ describe Gitlab::API do response.status.should == 201 json_response['title'].should == 'api test' end + + it "should return a 400 error if title is not given" do + post api("/projects/#{project.id}/snippets", user), + file_name: 'sample.rb', code: 'test' + response.status.should == 400 + end + + it "should return a 400 error if file_name not given" do + post api("/projects/#{project.id}/snippets", user), + title: 'api test', code: 'test' + response.status.should == 400 + end + + it "should return a 400 error if code not given" do + post api("/projects/#{project.id}/snippets", user), + title: 'api test', file_name: 'sample.rb' + response.status.should == 400 + end + + it "should return a 401 error if user not authenticated" do + post api("/projects/#{project.id}/snippets"), + title: 'api test', file_name: 'sample.rb', code: 'i=0' + response.status.should == 401 + end end describe "PUT /projects/:id/snippets/:shippet_id" do @@ -347,6 +541,13 @@ describe Gitlab::API do json_response['title'].should == 'example' snippet.reload.content.should == 'updated code' end + + it "should update an existing project snippet with new title" do + put api("/projects/#{project.id}/snippets/#{snippet.id}", user), + title: 'other api test' + response.status.should == 200 + json_response['title'].should == 'other api test' + end end describe "DELETE /projects/:id/snippets/:snippet_id" do @@ -354,6 +555,12 @@ describe Gitlab::API do expect { delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) }.to change { Snippet.count }.by(-1) + response.status.should == 200 + end + + it "should return success when deleting unknown snippet id" do + delete api("/projects/#{project.id}/snippets/1234", user) + response.status.should == 200 end end @@ -362,9 +569,14 @@ describe Gitlab::API do get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) response.status.should == 200 end + + it "should return a 404 error if raw project snippet not found" do + get api("/projects/#{project.id}/snippets/5555/raw", user) + response.status.should == 404 + end end - describe "GET /projects/:id/:sha/blob" do + describe "GET /projects/:id/repository/commits/:sha/blob" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user) response.status.should == 200 @@ -379,5 +591,10 @@ describe Gitlab::API do get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.invalid", user) response.status.should == 404 end + + it "should return a 400 error if filepath is missing" do + get api("/projects/#{project.id}/repository/commits/master/blob", user) + response.status.should == 400 + end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 33254eed31c..25ce2d2fc3d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -31,15 +31,20 @@ describe Gitlab::API do response.status.should == 200 json_response['email'].should == user.email end - end - describe "POST /users" do - before{ admin } + it "should return a 401 if unauthenticated" do + get api("/users/9998") + response.status.should == 401 + end - it "should not create invalid user" do - post api("/users", admin), { email: "invalid email" } + it "should return a 404 error if user id not found" do + get api("/users/9999", user) response.status.should == 404 end + end + + describe "POST /users" do + before{ admin } it "should create user" do expect { @@ -47,10 +52,48 @@ describe Gitlab::API do }.to change { User.count }.by(1) end + it "should return 201 Created on success" do + post api("/users", admin), attributes_for(:user, projects_limit: 3) + response.status.should == 201 + end + + it "should not create user with invalid email" do + post api("/users", admin), { email: "invalid email", password: 'password' } + response.status.should == 400 + end + + it "should return 400 error if password not given" do + post api("/users", admin), { email: 'test@example.com' } + response.status.should == 400 + end + + it "should return 400 error if email not given" do + post api("/users", admin), { password: 'pass1234' } + response.status.should == 400 + end + it "shouldn't available for non admin users" do post api("/users", user), attributes_for(:user) response.status.should == 403 end + + context "with existing user" do + before { post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test' } } + + it "should not create user with same email" do + expect { + post api("/users", admin), { email: 'test@example.com', password: 'password' } + }.to change { User.count }.by(0) + end + + it "should return 409 conflict error if user with email exists" do + post api("/users", admin), { email: 'test@example.com', password: 'password' } + end + + it "should return 409 conflict error if same username exists" do + post api("/users", admin), { email: 'foo@example.com', password: 'pass', username: 'test' } + end + end end describe "GET /users/sign_up" do @@ -81,7 +124,7 @@ describe Gitlab::API do describe "PUT /users/:id" do before { admin } - it "should update user" do + it "should update user with new bio" do put api("/users/#{user.id}", admin), {bio: 'new test bio'} response.status.should == 200 json_response['bio'].should == 'new test bio' @@ -103,6 +146,25 @@ describe Gitlab::API do put api("/users/999999", admin), {bio: 'update should fail'} response.status.should == 404 end + + context "with existing user" do + before { + post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' } + post api("/users", admin), { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' } + @user_id = User.all.last.id + } + +# it "should return 409 conflict error if email address exists" do +# put api("/users/#{@user_id}", admin), { email: 'test@example.com' } +# response.status.should == 409 +# end +# +# it "should return 409 conflict error if username taken" do +# @user_id = User.all.last.id +# put api("/users/#{@user_id}", admin), { username: 'test' } +# response.status.should == 409 +# end + end end describe "DELETE /users/:id" do @@ -115,6 +177,11 @@ describe Gitlab::API do json_response['email'].should == user.email end + it "should not delete for unauthenticated user" do + delete api("/users/#{user.id}") + response.status.should == 401 + end + it "shouldn't available for non admin users" do delete api("/users/#{user.id}", user) response.status.should == 403 @@ -132,6 +199,11 @@ describe Gitlab::API do response.status.should == 200 json_response['email'].should == user.email end + + it "should return 401 error if user is unauthenticated" do + get api("/user") + response.status.should == 401 + end end describe "GET /user/keys" do @@ -167,19 +239,38 @@ describe Gitlab::API do get api("/user/keys/42", user) response.status.should == 404 end - end - describe "POST /user/keys" do - it "should not create invalid ssh key" do - post api("/user/keys", user), { title: "invalid key" } + it "should return 404 error if admin accesses user's ssh key" do + user.keys << key + user.save + admin + get api("/user/keys/#{key.id}", admin) response.status.should == 404 end + end + describe "POST /user/keys" do it "should create ssh key" do key_attrs = attributes_for :key expect { post api("/user/keys", user), key_attrs }.to change{ user.keys.count }.by(1) + response.status.should == 201 + end + + it "should return a 401 error if unauthorized" do + post api("/user/keys"), title: 'some title', key: 'some key' + response.status.should == 401 + end + + it "should not create ssh key without key" do + post api("/user/keys", user), title: 'title' + response.status.should == 400 + end + + it "should not create ssh key without title" do + post api("/user/keys", user), key: "somekey" + response.status.should == 400 end end @@ -190,11 +281,19 @@ describe Gitlab::API do expect { delete api("/user/keys/#{key.id}", user) }.to change{user.keys.count}.by(-1) + response.status.should == 200 end - it "should return 404 Not Found within invalid ID" do + it "should return sucess if key ID not found" do delete api("/user/keys/42", user) - response.status.should == 404 + response.status.should == 200 + end + + it "should return 401 error if unauthorized" do + user.keys << key + user.save + delete api("/user/keys/#{key.id}") + response.status.should == 401 end end end |
