summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/models/project.rb2
-rw-r--r--config/routes.rb1
-rw-r--r--doc/api/milestones.md40
-rw-r--r--doc/api/notes.md193
-rw-r--r--doc/api/projects.md382
-rw-r--r--doc/api/snippets.md71
-rw-r--r--doc/api/users.md131
-rw-r--r--lib/api.rb13
-rw-r--r--lib/api/helpers.rb6
-rw-r--r--lib/api/merge_requests.rb22
-rw-r--r--lib/api/milestones.rb2
-rw-r--r--lib/api/notes.rb7
-rw-r--r--lib/api/projects.rb96
-rw-r--r--lib/api/users.rb24
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb45
-rw-r--r--spec/requests/api/milestones_spec.rb37
-rw-r--r--spec/requests/api/notes_spec.rb74
-rw-r--r--spec/requests/api/projects_spec.rb225
-rw-r--r--spec/requests/api/users_spec.rb123
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