Commit a7055be1fdecc51afc4e8f0e94267fcd9d9ef0c1

Authored by Dmitriy Zaporozhets
2 parents d2cec126 ecf53bb9

Merge pull request #2835 from Asquera/fixes/api

Fix API return codes
app/models/merge_request.rb
@@ -91,7 +91,7 @@ class MergeRequest < ActiveRecord::Base @@ -91,7 +91,7 @@ class MergeRequest < ActiveRecord::Base
91 91
92 def validate_branches 92 def validate_branches
93 if target_branch == source_branch 93 if target_branch == source_branch
94 - errors.add :base, "You can not use same branch for source and target branches" 94 + errors.add :branch_conflict, "You can not use same branch for source and target branches"
95 end 95 end
96 end 96 end
97 97
app/models/project.rb
@@ -160,7 +160,7 @@ class Project < ActiveRecord::Base @@ -160,7 +160,7 @@ class Project < ActiveRecord::Base
160 160
161 def check_limit 161 def check_limit
162 unless creator.can_create_project? 162 unless creator.can_create_project?
163 - errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it") 163 + errors[:limit_reached] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
164 end 164 end
165 rescue 165 rescue
166 errors[:base] << ("Can't check your ability to create project") 166 errors[:base] << ("Can't check your ability to create project")
config/routes.rb
@@ -8,6 +8,7 @@ Gitlab::Application.routes.draw do @@ -8,6 +8,7 @@ Gitlab::Application.routes.draw do
8 8
9 # API 9 # API
10 require 'api' 10 require 'api'
  11 + Gitlab::API.logger Rails.logger
11 mount Gitlab::API => '/api' 12 mount Gitlab::API => '/api'
12 13
13 constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? } 14 constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? }
doc/api/README.md
1 # GitLab API 1 # GitLab API
2 2
3 -All API requests require authentication. You need to pass a `private_token` parameter by url or header. You can find or reset your private token in your profile. 3 +All API requests require authentication. You need to pass a `private_token` parameter by url or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
4 4
5 If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401: 5 If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
6 6
@@ -18,8 +18,48 @@ Example of a valid API request: @@ -18,8 +18,48 @@ Example of a valid API request:
18 GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U 18 GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U
19 ``` 19 ```
20 20
  21 +Example for a valid API request using curl and authentication via header:
  22 +
  23 +```
  24 +curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/projects"
  25 +```
  26 +
  27 +
21 The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL. 28 The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
22 29
  30 +
  31 +
  32 +## Status codes
  33 +
  34 +The API is designed to return different status codes according to context and action. In this way
  35 +if a request results in an error the caller is able to get insight into what went wrong, e.g.
  36 +status code `400 Bad Request` is returned if a required attribute is missing from the request.
  37 +The following list gives an overview of how the API functions generally behave.
  38 +
  39 +API request types:
  40 +
  41 +* `GET` requests access one or more resources and return the result as JSON
  42 +* `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
  43 +* `GET`, `PUT` and `DELETE` return `200 Ok` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
  44 +* `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 Ok` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
  45 +
  46 +
  47 +The following list shows the possible return codes for API requests.
  48 +
  49 +Return values:
  50 +
  51 +* `200 Ok` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
  52 +* `201 Created` - The `POST` request was successful and the resource is returned as JSON
  53 +* `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
  54 +* `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
  55 +* `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
  56 +* `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
  57 +* `405 Method Not Allowed` - The request is not supported
  58 +* `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
  59 +* `500 Server Error` - While handling the request something went wrong on the server side
  60 +
  61 +
  62 +
23 #### Pagination 63 #### Pagination
24 64
25 When listing resources you can pass the following parameters: 65 When listing resources you can pass the following parameters:
doc/api/groups.md
@@ -17,7 +17,8 @@ GET /groups @@ -17,7 +17,8 @@ GET /groups
17 ] 17 ]
18 ``` 18 ```
19 19
20 -## Details of group 20 +
  21 +## Details of a group
21 22
22 Get all details of a group. 23 Get all details of a group.
23 24
@@ -29,19 +30,19 @@ Parameters: @@ -29,19 +30,19 @@ Parameters:
29 30
30 + `id` (required) - The ID of a group 31 + `id` (required) - The ID of a group
31 32
  33 +
32 ## New group 34 ## New group
33 35
34 -Create a new project group. Available only for admin 36 +Creates a new project group. Available only for admin.
35 37
36 ``` 38 ```
37 POST /groups 39 POST /groups
38 ``` 40 ```
39 41
40 Parameters: 42 Parameters:
41 -+ `name` (required) - Email  
42 -+ `path` - Password  
43 43
44 -Will return created group with status `201 Created` on success, or `404 Not found` on fail. 44 ++ `name` (required) - The name of the group
  45 ++ `path` (required) - The path of the group
45 46
46 ## Transfer project to group 47 ## Transfer project to group
47 48
doc/api/issues.md
1 ## List issues 1 ## List issues
2 2
3 -Get all issues created by authenticed user. 3 +Get all issues created by authenticed user. This function takes pagination parameters
  4 +`page` and `per_page` to restrict the list of issues.
4 5
5 ``` 6 ```
6 GET /issues 7 GET /issues
@@ -68,9 +69,11 @@ GET /issues @@ -68,9 +69,11 @@ GET /issues
68 ] 69 ]
69 ``` 70 ```
70 71
  72 +
71 ## List project issues 73 ## List project issues
72 74
73 -Get a list of project issues. 75 +Get a list of project issues. This function accepts pagination parameters `page` and `per_page`
  76 +to return the list of project issues.
74 77
75 ``` 78 ```
76 GET /projects/:id/issues 79 GET /projects/:id/issues
@@ -80,9 +83,10 @@ Parameters: @@ -80,9 +83,10 @@ Parameters:
80 83
81 + `id` (required) - The ID of a project 84 + `id` (required) - The ID of a project
82 85
  86 +
83 ## Single issue 87 ## Single issue
84 88
85 -Get a project issue. 89 +Gets a single project issue.
86 90
87 ``` 91 ```
88 GET /projects/:id/issues/:issue_id 92 GET /projects/:id/issues/:issue_id
@@ -133,9 +137,10 @@ Parameters: @@ -133,9 +137,10 @@ Parameters:
133 } 137 }
134 ``` 138 ```
135 139
  140 +
136 ## New issue 141 ## New issue
137 142
138 -Create a new project issue. 143 +Creates a new project issue.
139 144
140 ``` 145 ```
141 POST /projects/:id/issues 146 POST /projects/:id/issues
@@ -150,11 +155,10 @@ Parameters: @@ -150,11 +155,10 @@ Parameters:
150 + `milestone_id` (optional) - The ID of a milestone to assign issue 155 + `milestone_id` (optional) - The ID of a milestone to assign issue
151 + `labels` (optional) - Comma-separated label names for an issue 156 + `labels` (optional) - Comma-separated label names for an issue
152 157
153 -Will return created issue with status `201 Created` on success, or `404 Not found` on fail.  
154 158
155 ## Edit issue 159 ## Edit issue
156 160
157 -Update an existing project issue. 161 +Updates an existing project issue. This function is also used to mark an issue as closed.
158 162
159 ``` 163 ```
160 PUT /projects/:id/issues/:issue_id 164 PUT /projects/:id/issues/:issue_id
@@ -171,5 +175,19 @@ Parameters: @@ -171,5 +175,19 @@ Parameters:
171 + `labels` (optional) - Comma-separated label names for an issue 175 + `labels` (optional) - Comma-separated label names for an issue
172 + `closed` (optional) - The state of an issue (0 = false, 1 = true) 176 + `closed` (optional) - The state of an issue (0 = false, 1 = true)
173 177
174 -Will return updated issue with status `200 OK` on success, or `404 Not found` on fail. 178 +
  179 +## Delete existing issue (**Deprecated**)
  180 +
  181 +The function is deprecated and returns a `405 Method Not Allowed`
  182 +error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with
  183 +parameter `closed` set to 1.
  184 +
  185 +```
  186 +DELETE /projects/:id/issues/:issue_id
  187 +```
  188 +
  189 +Parameters:
  190 +
  191 ++ `id` (required) - The project ID
  192 ++ `issue_id` (required) - The ID of the issue
175 193
doc/api/merge_requests.md
1 ## List merge requests 1 ## List merge requests
2 2
3 -Get all MR for this project. 3 +Get all merge requests for this project. This function takes pagination parameters
  4 +`page` and `per_page` to restrict the list of merge requests.
4 5
5 ``` 6 ```
6 GET /projects/:id/merge_requests 7 GET /projects/:id/merge_requests
@@ -40,9 +41,10 @@ Parameters: @@ -40,9 +41,10 @@ Parameters:
40 ] 41 ]
41 ``` 42 ```
42 43
43 -## Show MR  
44 44
45 -Show information about MR. 45 +## Get single MR
  46 +
  47 +Shows information about a single merge request.
46 48
47 ``` 49 ```
48 GET /projects/:id/merge_request/:merge_request_id 50 GET /projects/:id/merge_request/:merge_request_id
@@ -84,7 +86,7 @@ Parameters: @@ -84,7 +86,7 @@ Parameters:
84 86
85 ## Create MR 87 ## Create MR
86 88
87 -Create MR. 89 +Creates a new merge request.
88 90
89 ``` 91 ```
90 POST /projects/:id/merge_requests 92 POST /projects/:id/merge_requests
@@ -126,9 +128,10 @@ Parameters: @@ -126,9 +128,10 @@ Parameters:
126 } 128 }
127 ``` 129 ```
128 130
  131 +
129 ## Update MR 132 ## Update MR
130 133
131 -Update MR. You can change branches, title, or even close the MR. 134 +Updates an existing merge request. You can change branches, title, or even close the MR.
132 135
133 ``` 136 ```
134 PUT /projects/:id/merge_request/:merge_request_id 137 PUT /projects/:id/merge_request/:merge_request_id
@@ -172,9 +175,11 @@ Parameters: @@ -172,9 +175,11 @@ Parameters:
172 } 175 }
173 } 176 }
174 ``` 177 ```
  178 +
  179 +
175 ## Post comment to MR 180 ## Post comment to MR
176 181
177 -Post comment to MR 182 +Adds a comment to a merge request.
178 183
179 ``` 184 ```
180 POST /projects/:id/merge_request/:merge_request_id/comments 185 POST /projects/:id/merge_request/:merge_request_id/comments
@@ -183,10 +188,9 @@ POST /projects/:id/merge_request/:merge_request_id/comments @@ -183,10 +188,9 @@ POST /projects/:id/merge_request/:merge_request_id/comments
183 Parameters: 188 Parameters:
184 189
185 + `id` (required) - The ID of a project 190 + `id` (required) - The ID of a project
186 -+ `merge_request_id` (required) - ID of MR 191 ++ `merge_request_id` (required) - ID of merge request
187 + `note` (required) - Text of comment 192 + `note` (required) - Text of comment
188 193
189 -Will return created note with status `201 Created` on success, or `404 Not found` on fail.  
190 194
191 ```json 195 ```json
192 { 196 {
doc/api/milestones.md
1 ## List project milestones 1 ## List project milestones
2 2
3 -Get a list of project milestones. 3 +Returns a list of project milestones.
4 4
5 ``` 5 ```
6 GET /projects/:id/milestones 6 GET /projects/:id/milestones
@@ -10,9 +10,10 @@ Parameters: @@ -10,9 +10,10 @@ Parameters:
10 10
11 + `id` (required) - The ID of a project 11 + `id` (required) - The ID of a project
12 12
13 -## Single milestone  
14 13
15 -Get a single project milestone. 14 +## Get single milestone
  15 +
  16 +Gets a single project milestone.
16 17
17 ``` 18 ```
18 GET /projects/:id/milestones/:milestone_id 19 GET /projects/:id/milestones/:milestone_id
@@ -23,9 +24,10 @@ Parameters: @@ -23,9 +24,10 @@ Parameters:
23 + `id` (required) - The ID of a project 24 + `id` (required) - The ID of a project
24 + `milestone_id` (required) - The ID of a project milestone 25 + `milestone_id` (required) - The ID of a project milestone
25 26
26 -## New milestone  
27 27
28 -Create a new project milestone. 28 +## Create new milestone
  29 +
  30 +Creates a new project milestone.
29 31
30 ``` 32 ```
31 POST /projects/:id/milestones 33 POST /projects/:id/milestones
@@ -38,9 +40,10 @@ Parameters: @@ -38,9 +40,10 @@ Parameters:
38 + `description` (optional) - The description of the milestone 40 + `description` (optional) - The description of the milestone
39 + `due_date` (optional) - The due date of the milestone 41 + `due_date` (optional) - The due date of the milestone
40 42
  43 +
41 ## Edit milestone 44 ## Edit milestone
42 45
43 -Update an existing project milestone. 46 +Updates an existing project milestone.
44 47
45 ``` 48 ```
46 PUT /projects/:id/milestones/:milestone_id 49 PUT /projects/:id/milestones/:milestone_id
@@ -54,3 +57,4 @@ Parameters: @@ -54,3 +57,4 @@ Parameters:
54 + `description` (optional) - The description of a milestone 57 + `description` (optional) - The description of a milestone
55 + `due_date` (optional) - The due date of the milestone 58 + `due_date` (optional) - The due date of the milestone
56 + `closed` (optional) - The status of the milestone 59 + `closed` (optional) - The status of the milestone
  60 +
doc/api/notes.md
1 -## List notes 1 +## Wall
2 2
3 ### List project wall notes 3 ### List project wall notes
4 4
@@ -30,22 +30,40 @@ Parameters: @@ -30,22 +30,40 @@ Parameters:
30 30
31 + `id` (required) - The ID of a project 31 + `id` (required) - The ID of a project
32 32
33 -### List merge request notes  
34 33
35 -Get a list of merge request notes. 34 +### Get single wall note
  35 +
  36 +Returns a single wall note.
36 37
37 ``` 38 ```
38 -GET /projects/:id/merge_requests/:merge_request_id/notes 39 +GET /projects/:id/notes/:note_id
39 ``` 40 ```
40 41
41 Parameters: 42 Parameters:
42 43
43 + `id` (required) - The ID of a project 44 + `id` (required) - The ID of a project
44 -+ `merge_request_id` (required) - The ID of an merge request 45 ++ `note_id` (required) - The ID of a wall note
45 46
46 -### List issue notes  
47 47
48 -Get a list of issue notes. 48 +### Create new wall note
  49 +
  50 +Creates a new wall note.
  51 +
  52 +```
  53 +POST /projects/:id/notes
  54 +```
  55 +
  56 +Parameters:
  57 +
  58 ++ `id` (required) - The ID of a project
  59 ++ `body` (required) - The content of a note
  60 +
  61 +
  62 +## Issues
  63 +
  64 +### List project issue notes
  65 +
  66 +Gets a list of all notes for a single issue.
49 67
50 ``` 68 ```
51 GET /projects/:id/issues/:issue_id/notes 69 GET /projects/:id/issues/:issue_id/notes
@@ -56,54 +74,59 @@ Parameters: @@ -56,54 +74,59 @@ Parameters:
56 + `id` (required) - The ID of a project 74 + `id` (required) - The ID of a project
57 + `issue_id` (required) - The ID of an issue 75 + `issue_id` (required) - The ID of an issue
58 76
59 -### List snippet notes  
60 77
61 -Get a list of snippet notes. 78 +### Get single issue note
  79 +
  80 +Returns a single note for a specific project issue
62 81
63 ``` 82 ```
64 -GET /projects/:id/snippets/:snippet_id/notes 83 +GET /projects/:id/issues/:issue_id/notes/:note_id
65 ``` 84 ```
66 85
67 Parameters: 86 Parameters:
68 87
69 + `id` (required) - The ID of a project 88 + `id` (required) - The ID of a project
70 -+ `snippet_id` (required) - The ID of a snippet 89 ++ `issue_id` (required) - The ID of a project issue
  90 ++ `note_id` (required) - The ID of an issue note
71 91
72 -## Single note  
73 92
74 -### Single wall note 93 +### Create new issue note
75 94
76 -Get a wall note. 95 +Creates a new note to a single project issue.
77 96
78 ``` 97 ```
79 -GET /projects/:id/notes/:note_id 98 +POST /projects/:id/issues/:issue_id/notes
80 ``` 99 ```
81 100
82 Parameters: 101 Parameters:
83 102
84 + `id` (required) - The ID of a project 103 + `id` (required) - The ID of a project
85 -+ `note_id` (required) - The ID of a wall note 104 ++ `issue_id` (required) - The ID of an issue
  105 ++ `body` (required) - The content of a note
86 106
87 -### Single issue note  
88 107
89 -Get an issue note. 108 +## Snippets
  109 +
  110 +### List all snippet notes
  111 +
  112 +Gets a list of all notes for a single snippet. Snippet notes are comments users can post to a snippet.
90 113
91 ``` 114 ```
92 -GET /projects/:id/issues/:issue_id/:notes/:note_id 115 +GET /projects/:id/snippets/:snippet_id/notes
93 ``` 116 ```
94 117
95 Parameters: 118 Parameters:
96 119
97 + `id` (required) - The ID of a project 120 + `id` (required) - The ID of a project
98 -+ `issue_id` (required) - The ID of a project issue  
99 -+ `note_id` (required) - The ID of an issue note 121 ++ `snippet_id` (required) - The ID of a project snippet
  122 +
100 123
101 -### Single snippet note 124 +### Get single snippet note
102 125
103 -Get a snippet note. 126 +Returns a single note for a given snippet.
104 127
105 ``` 128 ```
106 -GET /projects/:id/issues/:snippet_id/:notes/:note_id 129 +GET /projects/:id/snippets/:snippet_id/notes/:note_id
107 ``` 130 ```
108 131
109 Parameters: 132 Parameters:
@@ -112,52 +135,64 @@ Parameters: @@ -112,52 +135,64 @@ Parameters:
112 + `snippet_id` (required) - The ID of a project snippet 135 + `snippet_id` (required) - The ID of a project snippet
113 + `note_id` (required) - The ID of an snippet note 136 + `note_id` (required) - The ID of an snippet note
114 137
115 -## New note  
116 138
117 -### New wall note 139 +### Create new snippet note
118 140
119 -Create a new wall note. 141 +Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
120 142
121 ``` 143 ```
122 -POST /projects/:id/notes 144 +POST /projects/:id/snippets/:snippet_id/notes
123 ``` 145 ```
124 146
125 Parameters: 147 Parameters:
126 148
127 + `id` (required) - The ID of a project 149 + `id` (required) - The ID of a project
  150 ++ `snippet_id` (required) - The ID of an snippet
128 + `body` (required) - The content of a note 151 + `body` (required) - The content of a note
129 152
130 -Will return created note with status `201 Created` on success, or `404 Not found` on fail.  
131 153
  154 +## Merge Requests
132 155
133 -### New issue note 156 +### List all merge request notes
134 157
135 -Create a new issue note. 158 +Gets a list of all notes for a single merge request.
136 159
137 ``` 160 ```
138 -POST /projects/:id/issues/:issue_id/notes 161 +GET /projects/:id/merge_requests/:merge_request_id/notes
139 ``` 162 ```
140 163
141 Parameters: 164 Parameters:
142 165
143 + `id` (required) - The ID of a project 166 + `id` (required) - The ID of a project
144 -+ `issue_id` (required) - The ID of an issue  
145 -+ `body` (required) - The content of a note 167 ++ `merge_request_id` (required) - The ID of a project merge request
146 168
147 -Will return created note with status `201 Created` on success, or `404 Not found` on fail.  
148 169
149 -### New snippet note 170 +### Get single merge request note
150 171
151 -Create a new snippet note. 172 +Returns a single note for a given merge request.
152 173
153 ``` 174 ```
154 -POST /projects/:id/snippets/:snippet_id/notes 175 +GET /projects/:id/merge_requests/:merge_request_id/notes/:note_id
155 ``` 176 ```
156 177
157 Parameters: 178 Parameters:
158 179
159 + `id` (required) - The ID of a project 180 + `id` (required) - The ID of a project
160 -+ `snippet_id` (required) - The ID of an snippet 181 ++ `merge_request_id` (required) - The ID of a project merge request
  182 ++ `note_id` (required) - The ID of a merge request note
  183 +
  184 +
  185 +### Create new merge request note
  186 +
  187 +Creates a new note for a single merge request.
  188 +
  189 +```
  190 +POST /projects/:id/merge_requests/:merge_request_id/notes
  191 +```
  192 +
  193 +Parameters:
  194 +
  195 ++ `id` (required) - The ID of a project
  196 ++ `merge_request_id` (required) - The ID of a merge request
161 + `body` (required) - The content of a note 197 + `body` (required) - The content of a note
162 198
163 -Will return created note with status `201 Created` on success, or `404 Not found` on fail.  
doc/api/projects.md
1 -## List projects 1 +## Projects
  2 +
  3 +### List projects
2 4
3 Get a list of projects owned by the authenticated user. 5 Get a list of projects owned by the authenticated user.
4 6
@@ -55,9 +57,11 @@ GET /projects @@ -55,9 +57,11 @@ GET /projects
55 ] 57 ]
56 ``` 58 ```
57 59
58 -## Single project  
59 60
60 -Get a specific project, identified by project ID, which is owned by the authentication user. 61 +### Get single project
  62 +
  63 +Get a specific project, identified by project ID or NAME, which is owned by the authentication user.
  64 +Currently namespaced projects cannot retrieved by name.
61 65
62 ``` 66 ```
63 GET /projects/:id 67 GET /projects/:id
@@ -65,7 +69,7 @@ GET /projects/:id @@ -65,7 +69,7 @@ GET /projects/:id
65 69
66 Parameters: 70 Parameters:
67 71
68 -+ `id` (required) - The ID of a project 72 ++ `id` (required) - The ID or NAME of a project
69 73
70 ```json 74 ```json
71 { 75 {
@@ -92,9 +96,10 @@ Parameters: @@ -92,9 +96,10 @@ Parameters:
92 } 96 }
93 ``` 97 ```
94 98
95 -## Create project  
96 99
97 -Create new project owned by user 100 +### Create project
  101 +
  102 +Creates new project owned by user.
98 103
99 ``` 104 ```
100 POST /projects 105 POST /projects
@@ -110,12 +115,21 @@ Parameters: @@ -110,12 +115,21 @@ Parameters:
110 + `merge_requests_enabled` (optional) - enabled by default 115 + `merge_requests_enabled` (optional) - enabled by default
111 + `wiki_enabled` (optional) - enabled by default 116 + `wiki_enabled` (optional) - enabled by default
112 117
113 -Will return created project with status `201 Created` on success, or `404 Not  
114 -found` on fail. 118 +**Project access levels**
115 119
116 -## Create project for user 120 +The project access levels are defined in the `user_project.rb` class. Currently, these levels are recoginized:
  121 +
  122 +```
  123 + GUEST = 10
  124 + REPORTER = 20
  125 + DEVELOPER = 30
  126 + MASTER = 40
  127 +```
117 128
118 -Create new project owned by user. Available only for admin 129 +
  130 +### Create project for user
  131 +
  132 +Creates a new project owned by user. Available only for admins.
119 133
120 ``` 134 ```
121 POST /projects/user/:user_id 135 POST /projects/user/:user_id
@@ -132,10 +146,11 @@ Parameters: @@ -132,10 +146,11 @@ Parameters:
132 + `merge_requests_enabled` (optional) - enabled by default 146 + `merge_requests_enabled` (optional) - enabled by default
133 + `wiki_enabled` (optional) - enabled by default 147 + `wiki_enabled` (optional) - enabled by default
134 148
135 -Will return created project with status `201 Created` on success, or `404 Not  
136 -found` on fail.  
137 149
138 -## List project team members 150 +
  151 +## Team members
  152 +
  153 +### List project team members
139 154
140 Get a list of project team members. 155 Get a list of project team members.
141 156
@@ -145,12 +160,13 @@ GET /projects/:id/members @@ -145,12 +160,13 @@ GET /projects/:id/members
145 160
146 Parameters: 161 Parameters:
147 162
148 -+ `id` (required) - The ID of a project  
149 -+ `query` - Query string 163 ++ `id` (required) - The ID or NAME of a project
  164 ++ `query` (optional) - Query string to search for members
  165 +
150 166
151 -## Get project team member 167 +### Get project team member
152 168
153 -Get a project team member. 169 +Gets a project team member.
154 170
155 ``` 171 ```
156 GET /projects/:id/members/:user_id 172 GET /projects/:id/members/:user_id
@@ -158,12 +174,11 @@ GET /projects/:id/members/:user_id @@ -158,12 +174,11 @@ GET /projects/:id/members/:user_id
158 174
159 Parameters: 175 Parameters:
160 176
161 -+ `id` (required) - The ID of a project 177 ++ `id` (required) - The ID or NAME of a project
162 + `user_id` (required) - The ID of a user 178 + `user_id` (required) - The ID of a user
163 179
164 ```json 180 ```json
165 { 181 {
166 -  
167 "id": 1, 182 "id": 1,
168 "username": "john_smith", 183 "username": "john_smith",
169 "email": "john@example.com", 184 "email": "john@example.com",
@@ -174,9 +189,12 @@ Parameters: @@ -174,9 +189,12 @@ Parameters:
174 } 189 }
175 ``` 190 ```
176 191
177 -## Add project team member  
178 192
179 -Add a user to a project team. 193 +### Add project team member
  194 +
  195 +Adds a user to a project team. This is an idempotent method and can be called multiple times
  196 +with the same parameters. Adding team membership to a user that is already a member does not
  197 +affect the existing membership.
180 198
181 ``` 199 ```
182 POST /projects/:id/members 200 POST /projects/:id/members
@@ -184,15 +202,14 @@ POST /projects/:id/members @@ -184,15 +202,14 @@ POST /projects/:id/members
184 202
185 Parameters: 203 Parameters:
186 204
187 -+ `id` (required) - The ID of a project 205 ++ `id` (required) - The ID or NAME of a project
188 + `user_id` (required) - The ID of a user to add 206 + `user_id` (required) - The ID of a user to add
189 + `access_level` (required) - Project access level 207 + `access_level` (required) - Project access level
190 208
191 -Will return status `201 Created` on success, or `404 Not found` on fail.  
192 209
193 -## Edit project team member 210 +### Edit project team member
194 211
195 -Update project team member to specified access level. 212 +Updates project team member to a specified access level.
196 213
197 ``` 214 ```
198 PUT /projects/:id/members/:user_id 215 PUT /projects/:id/members/:user_id
@@ -200,13 +217,12 @@ PUT /projects/:id/members/:user_id @@ -200,13 +217,12 @@ PUT /projects/:id/members/:user_id
200 217
201 Parameters: 218 Parameters:
202 219
203 -+ `id` (required) - The ID of a project 220 ++ `id` (required) - The ID or NAME of a project
204 + `user_id` (required) - The ID of a team member 221 + `user_id` (required) - The ID of a team member
205 + `access_level` (required) - Project access level 222 + `access_level` (required) - Project access level
206 223
207 -Will return status `200 OK` on success, or `404 Not found` on fail.  
208 224
209 -## Remove project team member 225 +### Remove project team member
210 226
211 Removes user from project team. 227 Removes user from project team.
212 228
@@ -216,14 +232,20 @@ DELETE /projects/:id/members/:user_id @@ -216,14 +232,20 @@ DELETE /projects/:id/members/:user_id
216 232
217 Parameters: 233 Parameters:
218 234
219 -+ `id` (required) - The ID of a project 235 ++ `id` (required) - The ID or NAME of a project
220 + `user_id` (required) - The ID of a team member 236 + `user_id` (required) - The ID of a team member
221 237
222 -Status code `200` will be returned on success. 238 +This method is idempotent and can be called multiple times with the same parameters.
  239 +Revoking team membership for a user who is not currently a team member is considered success.
  240 +Please note that the returned JSON currently differs slightly. Thus you should not
  241 +rely on the returned JSON structure.
  242 +
223 243
224 -## List project hooks 244 +## Hooks
225 245
226 -Get list for project hooks 246 +### List project hooks
  247 +
  248 +Get list of project hooks.
227 249
228 ``` 250 ```
229 GET /projects/:id/hooks 251 GET /projects/:id/hooks
@@ -231,13 +253,12 @@ GET /projects/:id/hooks @@ -231,13 +253,12 @@ GET /projects/:id/hooks
231 253
232 Parameters: 254 Parameters:
233 255
234 -+ `id` (required) - The ID of a project 256 ++ `id` (required) - The ID or NAME of a project
235 257
236 -Will return hooks with status `200 OK` on success, or `404 Not found` on fail.  
237 258
238 -## Get project hook 259 +### Get project hook
239 260
240 -Get hook for project 261 +Get a specific hook for project.
241 262
242 ``` 263 ```
243 GET /projects/:id/hooks/:hook_id 264 GET /projects/:id/hooks/:hook_id
@@ -245,14 +266,21 @@ GET /projects/:id/hooks/:hook_id @@ -245,14 +266,21 @@ GET /projects/:id/hooks/:hook_id
245 266
246 Parameters: 267 Parameters:
247 268
248 -+ `id` (required) - The ID of a project 269 ++ `id` (required) - The ID or NAME of a project
249 + `hook_id` (required) - The ID of a project hook 270 + `hook_id` (required) - The ID of a project hook
250 271
251 -Will return hook with status `200 OK` on success, or `404 Not found` on fail. 272 +```json
  273 +{
  274 + "id": 1,
  275 + "url": "http://example.com/hook",
  276 + "created_at": "2012-10-12T17:04:47Z"
  277 +}
  278 +```
252 279
253 -## Add project hook  
254 280
255 -Add hook to project 281 +### Add project hook
  282 +
  283 +Adds a hook to project.
256 284
257 ``` 285 ```
258 POST /projects/:id/hooks 286 POST /projects/:id/hooks
@@ -260,14 +288,13 @@ POST /projects/:id/hooks @@ -260,14 +288,13 @@ POST /projects/:id/hooks
260 288
261 Parameters: 289 Parameters:
262 290
263 -+ `id` (required) - The ID of a project 291 ++ `id` (required) - The ID or NAME of a project
264 + `url` (required) - The hook URL 292 + `url` (required) - The hook URL
265 293
266 -Will return status `201 Created` on success, or `404 Not found` on fail.  
267 294
268 -## Edit project hook 295 +### Edit project hook
269 296
270 -Edit hook for project 297 +Edits a hook for project.
271 298
272 ``` 299 ```
273 PUT /projects/:id/hooks/:hook_id 300 PUT /projects/:id/hooks/:hook_id
@@ -275,30 +302,125 @@ PUT /projects/:id/hooks/:hook_id @@ -275,30 +302,125 @@ PUT /projects/:id/hooks/:hook_id
275 302
276 Parameters: 303 Parameters:
277 304
278 -+ `id` (required) - The ID of a project 305 ++ `id` (required) - The ID or NAME of a project
279 + `hook_id` (required) - The ID of a project hook 306 + `hook_id` (required) - The ID of a project hook
280 + `url` (required) - The hook URL 307 + `url` (required) - The hook URL
281 308
282 -Will return status `201 Created` on success, or `404 Not found` on fail.  
283 -  
284 309
285 -## Delete project hook 310 +### Delete project hook
286 311
287 -Delete hook from project 312 +Removes a hook from project. This is an idempotent method and can be called multiple times.
  313 +Either the hook is available or not.
288 314
289 ``` 315 ```
290 -DELETE /projects/:id/hooks/:hook_id 316 +DELETE /projects/:id/hooks/
291 ``` 317 ```
292 318
293 Parameters: 319 Parameters:
294 320
295 -+ `id` (required) - The ID of a project 321 ++ `id` (required) - The ID or NAME of a project
296 + `hook_id` (required) - The ID of hook to delete 322 + `hook_id` (required) - The ID of hook to delete
297 323
298 -Will return status `200 OK` on success, or `404 Not found` on fail. 324 +Note the JSON response differs if the hook is available or not. If the project hook
  325 +is available before it is returned in the JSON response or an empty response is returned.
299 326
300 327
301 -## List deploy keys 328 +## Branches
  329 +
  330 +### List branches
  331 +
  332 +Lists all branches of a project.
  333 +
  334 +```
  335 +GET /projects/:id/repository/branches
  336 +```
  337 +
  338 +Parameters:
  339 +
  340 ++ `id` (required) - The ID of the project
  341 +
  342 +
  343 +### List single branch
  344 +
  345 +Lists a specific branch of a project.
  346 +
  347 +```
  348 +GET /projects/:id/repository/branches/:branch
  349 +```
  350 +
  351 +Parameters:
  352 +
  353 ++ `id` (required) - The ID of the project.
  354 ++ `branch` (required) - The name of the branch.
  355 +
  356 +
  357 +### Protect single branch
  358 +
  359 +Protects a single branch of a project.
  360 +
  361 +```
  362 +PUT /projects/:id/repository/branches/:branch/protect
  363 +```
  364 +
  365 +Parameters:
  366 +
  367 ++ `id` (required) - The ID of the project.
  368 ++ `branch` (required) - The name of the branch.
  369 +
  370 +
  371 +### Unprotect single branch
  372 +
  373 +Unprotects a single branch of a project.
  374 +
  375 +```
  376 +PUT /projects/:id/repository/branches/:branch/unprotect
  377 +```
  378 +
  379 +Parameters:
  380 +
  381 ++ `id` (required) - The ID of the project.
  382 ++ `branch` (required) - The name of the branch.
  383 +
  384 +
  385 +### List tags
  386 +
  387 +Lists all tags of a project.
  388 +
  389 +```
  390 +GET /projects/:id/repository/tags
  391 +```
  392 +
  393 +Parameters:
  394 +
  395 ++ `id` (required) - The ID of the project
  396 +
  397 +
  398 +### List commits
  399 +
  400 +Lists all commits with pagination. If the optional `ref_name` name is not given the commits of
  401 +the default branch (usually master) are returned.
  402 +
  403 +```
  404 +GET /projects/:id/repository/commits
  405 +```
  406 +
  407 +Parameters:
  408 +
  409 ++ `id` (required) - The Id of the project
  410 ++ `ref_name` (optional) - The name of a repository branch or tag
  411 ++ `page` (optional) - The page of commits to return (`0` default)
  412 ++ `per_page` (optional) - The number of commits per page (`20` default)
  413 +
  414 +Returns values:
  415 +
  416 ++ `200 Ok` on success and a list with commits
  417 ++ `404 Not Found` if project with id or the branch with `ref_name` not found
  418 +
  419 +
  420 +
  421 +## Deploy Keys
  422 +
  423 +### List deploy keys
302 424
303 Get a list of a project's deploy keys. 425 Get a list of a project's deploy keys.
304 426
@@ -306,6 +428,10 @@ Get a list of a project&#39;s deploy keys. @@ -306,6 +428,10 @@ Get a list of a project&#39;s deploy keys.
306 GET /projects/:id/keys 428 GET /projects/:id/keys
307 ``` 429 ```
308 430
  431 +Parameters:
  432 +
  433 ++ `id` (required) - The ID of the project
  434 +
309 ```json 435 ```json
310 [ 436 [
311 { 437 {
@@ -325,7 +451,8 @@ GET /projects/:id/keys @@ -325,7 +451,8 @@ GET /projects/:id/keys
325 ] 451 ]
326 ``` 452 ```
327 453
328 -## Single deploy key 454 +
  455 +### Single deploy key
329 456
330 Get a single key. 457 Get a single key.
331 458
@@ -335,7 +462,8 @@ GET /projects/:id/keys/:key_id @@ -335,7 +462,8 @@ GET /projects/:id/keys/:key_id
335 462
336 Parameters: 463 Parameters:
337 464
338 -+ `id` (required) - The ID of an deploy key 465 ++ `id` (required) - The ID of the project
  466 ++ `key_id` (required) - The ID of the deploy key
339 467
340 ```json 468 ```json
341 { 469 {
@@ -346,9 +474,11 @@ Parameters: @@ -346,9 +474,11 @@ Parameters:
346 soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" 474 soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
347 } 475 }
348 ``` 476 ```
349 -## Add deploy key  
350 477
351 -Create new deploy key for a project 478 +
  479 +### Add deploy key
  480 +
  481 +Creates a new deploy key for a project.
352 482
353 ``` 483 ```
354 POST /projects/:id/keys 484 POST /projects/:id/keys
@@ -356,13 +486,12 @@ POST /projects/:id/keys @@ -356,13 +486,12 @@ POST /projects/:id/keys
356 486
357 Parameters: 487 Parameters:
358 488
359 -+ `title` (required) - new deploy key's title  
360 -+ `key` (required) - new deploy key 489 ++ `id` (required) - The ID of the project
  490 ++ `title` (required) - New deploy key's title
  491 ++ `key` (required) - New deploy key
361 492
362 -Will return created key with status `201 Created` on success, or `404 Not  
363 -found` on fail.  
364 493
365 -## Delete deploy key 494 +### Delete deploy key
366 495
367 Delete a deploy key from a project 496 Delete a deploy key from a project
368 497
@@ -372,6 +501,6 @@ DELETE /projects/:id/keys/:key_id @@ -372,6 +501,6 @@ DELETE /projects/:id/keys/:key_id
372 501
373 Parameters: 502 Parameters:
374 503
375 -+ `id` (required) - Deploy key ID 504 ++ `id` (required) - The ID of the project
  505 ++ `key_id` (required) - The ID of the deploy key
376 506
377 -Will return `200 OK` on success, or `404 Not Found` on fail.  
doc/api/repositories.md
1 -## Project repository branches 1 +## List repository branches
2 2
3 Get a list of repository branches from a project, sorted by name alphabetically. 3 Get a list of repository branches from a project, sorted by name alphabetically.
4 4
@@ -39,7 +39,8 @@ Parameters: @@ -39,7 +39,8 @@ Parameters:
39 ] 39 ]
40 ``` 40 ```
41 41
42 -## Project repository branch 42 +
  43 +## Get single repository branch
43 44
44 Get a single project repository branch. 45 Get a single project repository branch.
45 46
@@ -79,12 +80,11 @@ Parameters: @@ -79,12 +80,11 @@ Parameters:
79 } 80 }
80 ``` 81 ```
81 82
82 -Will return status code `200` on success or `404 Not found` if the branch is not available.  
83 -  
84 83
85 -## Protect a project repository branch 84 +## Protect repository branch
86 85
87 -Protect a single project repository branch. 86 +Protects a single project repository branch. This is an idempotent function, protecting an already
  87 +protected repository branch still returns a `200 Ok` status code.
88 88
89 ``` 89 ```
90 PUT /projects/:id/repository/branches/:branch/protect 90 PUT /projects/:id/repository/branches/:branch/protect
@@ -122,9 +122,11 @@ Parameters: @@ -122,9 +122,11 @@ Parameters:
122 } 122 }
123 ``` 123 ```
124 124
125 -## Unprotect a project repository branch  
126 125
127 -Unprotect a single project repository branch. 126 +## Unprotect repository branch
  127 +
  128 +Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
  129 +unprotected repository branch still returns a `200 Ok` status code.
128 130
129 ``` 131 ```
130 PUT /projects/:id/repository/branches/:branch/unprotect 132 PUT /projects/:id/repository/branches/:branch/unprotect
@@ -162,7 +164,8 @@ Parameters: @@ -162,7 +164,8 @@ Parameters:
162 } 164 }
163 ``` 165 ```
164 166
165 -## Project repository tags 167 +
  168 +## List project repository tags
166 169
167 Get a list of repository tags from a project, sorted by name in reverse alphabetical order. 170 Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
168 171
@@ -201,7 +204,8 @@ Parameters: @@ -201,7 +204,8 @@ Parameters:
201 ] 204 ]
202 ``` 205 ```
203 206
204 -## Project repository commits 207 +
  208 +## List repository commits
205 209
206 Get a list of repository commits in a project. 210 Get a list of repository commits in a project.
207 211
@@ -212,7 +216,7 @@ GET /projects/:id/repository/commits @@ -212,7 +216,7 @@ GET /projects/:id/repository/commits
212 Parameters: 216 Parameters:
213 217
214 + `id` (required) - The ID of a project 218 + `id` (required) - The ID of a project
215 -+ `ref_name` (optional) - The name of a repository branch or tag 219 ++ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
216 220
217 ```json 221 ```json
218 [ 222 [
@@ -235,6 +239,7 @@ Parameters: @@ -235,6 +239,7 @@ Parameters:
235 ] 239 ]
236 ``` 240 ```
237 241
  242 +
238 ## Raw blob content 243 ## Raw blob content
239 244
240 Get the raw file contents for a file. 245 Get the raw file contents for a file.
@@ -248,5 +253,3 @@ Parameters: @@ -248,5 +253,3 @@ Parameters:
248 + `id` (required) - The ID of a project 253 + `id` (required) - The ID of a project
249 + `sha` (required) - The commit or branch name 254 + `sha` (required) - The commit or branch name
250 + `filepath` (required) - The path the file 255 + `filepath` (required) - The path the file
251 -  
252 -Will return the raw file contents.  
doc/api/snippets.md
@@ -10,9 +10,10 @@ Parameters: @@ -10,9 +10,10 @@ Parameters:
10 10
11 + `id` (required) - The ID of a project 11 + `id` (required) - The ID of a project
12 12
  13 +
13 ## Single snippet 14 ## Single snippet
14 15
15 -Get a project snippet. 16 +Get a single project snippet.
16 17
17 ``` 18 ```
18 GET /projects/:id/snippets/:snippet_id 19 GET /projects/:id/snippets/:snippet_id
@@ -42,22 +43,10 @@ Parameters: @@ -42,22 +43,10 @@ Parameters:
42 } 43 }
43 ``` 44 ```
44 45
45 -## Snippet content  
46 -  
47 -Get a raw project snippet.  
48 -  
49 -```  
50 -GET /projects/:id/snippets/:snippet_id/raw  
51 -```  
52 -  
53 -Parameters:  
54 -  
55 -+ `id` (required) - The ID of a project  
56 -+ `snippet_id` (required) - The ID of a project's snippet  
57 46
58 -## New snippet 47 +## Create new snippet
59 48
60 -Create a new project snippet. 49 +Creates a new project snippet. The user must have permission to create new snippets.
61 50
62 ``` 51 ```
63 POST /projects/:id/snippets 52 POST /projects/:id/snippets
@@ -71,11 +60,10 @@ Parameters: @@ -71,11 +60,10 @@ Parameters:
71 + `lifetime` (optional) - The expiration date of a snippet 60 + `lifetime` (optional) - The expiration date of a snippet
72 + `code` (required) - The content of a snippet 61 + `code` (required) - The content of a snippet
73 62
74 -Will return created snippet with status `201 Created` on success, or `404 Not found` on fail.  
75 63
76 -## Edit snippet 64 +## Update snippet
77 65
78 -Update an existing project snippet. 66 +Updates an existing project snippet. The user must have permission to change an existing snippet.
79 67
80 ``` 68 ```
81 PUT /projects/:id/snippets/:snippet_id 69 PUT /projects/:id/snippets/:snippet_id
@@ -90,11 +78,11 @@ Parameters: @@ -90,11 +78,11 @@ Parameters:
90 + `lifetime` (optional) - The expiration date of a snippet 78 + `lifetime` (optional) - The expiration date of a snippet
91 + `code` (optional) - The content of a snippet 79 + `code` (optional) - The content of a snippet
92 80
93 -Will return updated snippet with status `200 OK` on success, or `404 Not found` on fail.  
94 81
95 ## Delete snippet 82 ## Delete snippet
96 83
97 -Delete existing project snippet. 84 +Deletes an existing project snippet. This is an idempotent function and deleting a non-existent
  85 +snippet still returns a `200 Ok` status code.
98 86
99 ``` 87 ```
100 DELETE /projects/:id/snippets/:snippet_id 88 DELETE /projects/:id/snippets/:snippet_id
@@ -105,5 +93,16 @@ Parameters: @@ -105,5 +93,16 @@ Parameters:
105 + `id` (required) - The ID of a project 93 + `id` (required) - The ID of a project
106 + `snippet_id` (required) - The ID of a project's snippet 94 + `snippet_id` (required) - The ID of a project's snippet
107 95
108 -Status code `200` will be returned on success.  
109 96
  97 +## Snippet content
  98 +
  99 +Returns the raw project snippet as plain text.
  100 +
  101 +```
  102 +GET /projects/:id/snippets/:snippet_id/raw
  103 +```
  104 +
  105 +Parameters:
  106 +
  107 ++ `id` (required) - The ID of a project
  108 ++ `snippet_id` (required) - The ID of a project's snippet
doc/api/users.md
@@ -43,6 +43,7 @@ GET /users @@ -43,6 +43,7 @@ GET /users
43 ] 43 ]
44 ``` 44 ```
45 45
  46 +
46 ## Single user 47 ## Single user
47 48
48 Get a single user. 49 Get a single user.
@@ -74,37 +75,40 @@ Parameters: @@ -74,37 +75,40 @@ Parameters:
74 } 75 }
75 ``` 76 ```
76 77
  78 +
77 ## User creation 79 ## User creation
78 -Create user. Available only for admin 80 +
  81 +Creates a new user. Note only administrators can create new users.
79 82
80 ``` 83 ```
81 POST /users 84 POST /users
82 ``` 85 ```
83 86
84 Parameters: 87 Parameters:
85 -+ `email` (required) - Email  
86 -+ `password` (required) - Password  
87 -+ `username` (required) - Username  
88 -+ `name` (required) - Name  
89 -+ `skype` - Skype ID  
90 -+ `linkedin` - Linkedin  
91 -+ `twitter` - Twitter account  
92 -+ `projects_limit` - Number of projects user can create  
93 -+ `extern_uid` - External UID  
94 -+ `provider` - External provider name  
95 -+ `bio` - User's bio  
96 88
97 -Will return created user with status `201 Created` on success, or `404 Not  
98 -found` on fail. 89 ++ `email` (required) - Email
  90 ++ `password` (required) - Password
  91 ++ `username` (required) - Username
  92 ++ `name` (required) - Name
  93 ++ `skype` (optional) - Skype ID
  94 ++ `linkedin` (optional) - Linkedin
  95 ++ `twitter` (optional) - Twitter account
  96 ++ `projects_limit` (optional) - Number of projects user can create
  97 ++ `extern_uid` (optional) - External UID
  98 ++ `provider` (optional) - External provider name
  99 ++ `bio` (optional) - User's bio
  100 +
99 101
100 ## User modification 102 ## User modification
101 -Modify user. Available only for admin 103 +
  104 +Modifies an existing user. Only administrators can change attributes of a user.
102 105
103 ``` 106 ```
104 PUT /users/:id 107 PUT /users/:id
105 ``` 108 ```
106 109
107 Parameters: 110 Parameters:
  111 +
108 + `email` - Email 112 + `email` - Email
109 + `username` - Username 113 + `username` - Username
110 + `name` - Name 114 + `name` - Name
@@ -117,23 +121,28 @@ Parameters: @@ -117,23 +121,28 @@ Parameters:
117 + `provider` - External provider name 121 + `provider` - External provider name
118 + `bio` - User's bio 122 + `bio` - User's bio
119 123
  124 +Note, at the moment this method does only return a 404 error, even in cases where a 409 (Conflict) would
  125 +be more appropriate, e.g. when renaming the email address to some exsisting one.
120 126
121 -Will return created user with status `200 OK` on success, or `404 Not  
122 -found` on fail.  
123 127
124 ## User deletion 128 ## User deletion
125 -Delete user. Available only for admin 129 +
  130 +Deletes a user. Available only for administrators. This is an idempotent function, calling this function
  131 +for a non-existent user id still returns a status code `200 Ok`. The JSON response differs if the user
  132 +was actually deleted or not. In the former the user is returned and in the latter not.
126 133
127 ``` 134 ```
128 DELETE /users/:id 135 DELETE /users/:id
129 ``` 136 ```
130 137
131 -Will return deleted user with status `200 OK` on success, or `404 Not  
132 -found` on fail. 138 +Parameters:
  139 +
  140 ++ `id` (required) - The ID of the user
  141 +
133 142
134 ## Current user 143 ## Current user
135 144
136 -Get currently authenticated user. 145 +Gets currently authenticated user.
137 146
138 ``` 147 ```
139 GET /user 148 GET /user
@@ -156,6 +165,7 @@ GET /user @@ -156,6 +165,7 @@ GET /user
156 } 165 }
157 ``` 166 ```
158 167
  168 +
159 ## List SSH keys 169 ## List SSH keys
160 170
161 Get a list of currently authenticated user's SSH keys. 171 Get a list of currently authenticated user's SSH keys.
@@ -183,6 +193,11 @@ GET /user/keys @@ -183,6 +193,11 @@ GET /user/keys
183 ] 193 ]
184 ``` 194 ```
185 195
  196 +Parameters:
  197 +
  198 ++ **none**
  199 +
  200 +
186 ## Single SSH key 201 ## Single SSH key
187 202
188 Get a single key. 203 Get a single key.
@@ -204,9 +219,11 @@ Parameters: @@ -204,9 +219,11 @@ Parameters:
204 soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" 219 soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
205 } 220 }
206 ``` 221 ```
  222 +
  223 +
207 ## Add SSH key 224 ## Add SSH key
208 225
209 -Create new key owned by currently authenticated user 226 +Creates a new key owned by the currently authenticated user.
210 227
211 ``` 228 ```
212 POST /user/keys 229 POST /user/keys
@@ -217,8 +234,6 @@ Parameters: @@ -217,8 +234,6 @@ Parameters:
217 + `title` (required) - new SSH Key's title 234 + `title` (required) - new SSH Key's title
218 + `key` (required) - new SSH key 235 + `key` (required) - new SSH key
219 236
220 -Will return created key with status `201 Created` on success, or `404 Not  
221 -found` on fail.  
222 237
223 ## Add SSH key for user 238 ## Add SSH key for user
224 239
@@ -239,7 +254,8 @@ found` on fail. @@ -239,7 +254,8 @@ found` on fail.
239 254
240 ## Delete SSH key 255 ## Delete SSH key
241 256
242 -Delete key owned by currently authenticated user 257 +Deletes key owned by currently authenticated user. This is an idempotent function and calling it on a key that is already
  258 +deleted or not available results in `200 Ok`.
243 259
244 ``` 260 ```
245 DELETE /user/keys/:id 261 DELETE /user/keys/:id
@@ -249,4 +265,3 @@ Parameters: @@ -249,4 +265,3 @@ Parameters:
249 265
250 + `id` (required) - SSH key ID 266 + `id` (required) - SSH key ID
251 267
252 -Will return `200 OK` on success, or `404 Not Found` on fail.  
@@ -8,6 +8,19 @@ module Gitlab @@ -8,6 +8,19 @@ module Gitlab
8 rack_response({'message' => '404 Not found'}.to_json, 404) 8 rack_response({'message' => '404 Not found'}.to_json, 404)
9 end 9 end
10 10
  11 + rescue_from :all do |exception|
  12 + # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
  13 + # why is this not wrapped in something reusable?
  14 + trace = exception.backtrace
  15 +
  16 + message = "\n#{exception.class} (#{exception.message}):\n"
  17 + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
  18 + message << " " << trace.join("\n ")
  19 +
  20 + API.logger.add Logger::FATAL, message
  21 + rack_response({'message' => '500 Internal Server Error'}, 500)
  22 + end
  23 +
11 format :json 24 format :json
12 helpers APIHelpers 25 helpers APIHelpers
13 26
lib/api/groups.rb
@@ -20,12 +20,14 @@ module Gitlab @@ -20,12 +20,14 @@ module Gitlab
20 # Create group. Available only for admin 20 # Create group. Available only for admin
21 # 21 #
22 # Parameters: 22 # Parameters:
23 - # name (required) - Name  
24 - # path (required) - Path 23 + # name (required) - The name of the group
  24 + # path (required) - The path of the group
25 # Example Request: 25 # Example Request:
26 # POST /groups 26 # POST /groups
27 post do 27 post do
28 authenticated_as_admin! 28 authenticated_as_admin!
  29 + required_attributes! [:name, :path]
  30 +
29 attrs = attributes_for_keys [:name, :path] 31 attrs = attributes_for_keys [:name, :path]
30 @group = Group.new(attrs) 32 @group = Group.new(attrs)
31 @group.owner = current_user 33 @group.owner = current_user
lib/api/helpers.rb
@@ -41,6 +41,17 @@ module Gitlab @@ -41,6 +41,17 @@ module Gitlab
41 abilities.allowed?(object, action, subject) 41 abilities.allowed?(object, action, subject)
42 end 42 end
43 43
  44 + # Checks the occurrences of required attributes, each attribute must be present in the params hash
  45 + # or a Bad Request error is invoked.
  46 + #
  47 + # Parameters:
  48 + # keys (required) - A hash consisting of keys that must be present
  49 + def required_attributes!(keys)
  50 + keys.each do |key|
  51 + bad_request!(key) unless params[key].present?
  52 + end
  53 + end
  54 +
44 def attributes_for_keys(keys) 55 def attributes_for_keys(keys)
45 attrs = {} 56 attrs = {}
46 keys.each do |key| 57 keys.each do |key|
@@ -55,6 +66,12 @@ module Gitlab @@ -55,6 +66,12 @@ module Gitlab
55 render_api_error!('403 Forbidden', 403) 66 render_api_error!('403 Forbidden', 403)
56 end 67 end
57 68
  69 + def bad_request!(attribute)
  70 + message = ["400 (Bad request)"]
  71 + message << "\"" + attribute.to_s + "\" not given"
  72 + render_api_error!(message.join(' '), 400)
  73 + end
  74 +
58 def not_found!(resource = nil) 75 def not_found!(resource = nil)
59 message = ["404"] 76 message = ["404"]
60 message << resource if resource 77 message << resource if resource
lib/api/issues.rb
@@ -48,6 +48,7 @@ module Gitlab @@ -48,6 +48,7 @@ module Gitlab
48 # Example Request: 48 # Example Request:
49 # POST /projects/:id/issues 49 # POST /projects/:id/issues
50 post ":id/issues" do 50 post ":id/issues" do
  51 + required_attributes! [:title]
51 attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] 52 attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
52 attrs[:label_list] = params[:labels] if params[:labels].present? 53 attrs[:label_list] = params[:labels] if params[:labels].present?
53 @issue = user_project.issues.new attrs 54 @issue = user_project.issues.new attrs
lib/api/merge_requests.rb
@@ -4,6 +4,16 @@ module Gitlab @@ -4,6 +4,16 @@ module Gitlab
4 before { authenticate! } 4 before { authenticate! }
5 5
6 resource :projects do 6 resource :projects do
  7 + helpers do
  8 + def handle_merge_request_errors!(errors)
  9 + if errors[:project_access].any?
  10 + error!(errors[:project_access], 422)
  11 + elsif errors[:branch_conflict].any?
  12 + error!(errors[:branch_conflict], 422)
  13 + end
  14 + not_found!
  15 + end
  16 + end
7 17
8 # List merge requests 18 # List merge requests
9 # 19 #
@@ -51,6 +61,7 @@ module Gitlab @@ -51,6 +61,7 @@ module Gitlab
51 # 61 #
52 post ":id/merge_requests" do 62 post ":id/merge_requests" do
53 authorize! :write_merge_request, user_project 63 authorize! :write_merge_request, user_project
  64 + required_attributes! [:source_branch, :target_branch, :title]
54 65
55 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] 66 attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
56 merge_request = user_project.merge_requests.new(attrs) 67 merge_request = user_project.merge_requests.new(attrs)
@@ -60,7 +71,7 @@ module Gitlab @@ -60,7 +71,7 @@ module Gitlab
60 merge_request.reload_code 71 merge_request.reload_code
61 present merge_request, with: Entities::MergeRequest 72 present merge_request, with: Entities::MergeRequest
62 else 73 else
63 - not_found! 74 + handle_merge_request_errors! merge_request.errors
64 end 75 end
65 end 76 end
66 77
@@ -88,7 +99,7 @@ module Gitlab @@ -88,7 +99,7 @@ module Gitlab
88 merge_request.mark_as_unchecked 99 merge_request.mark_as_unchecked
89 present merge_request, with: Entities::MergeRequest 100 present merge_request, with: Entities::MergeRequest
90 else 101 else
91 - not_found! 102 + handle_merge_request_errors! merge_request.errors
92 end 103 end
93 end 104 end
94 105
@@ -102,6 +113,8 @@ module Gitlab @@ -102,6 +113,8 @@ module Gitlab
102 # POST /projects/:id/merge_request/:merge_request_id/comments 113 # POST /projects/:id/merge_request/:merge_request_id/comments
103 # 114 #
104 post ":id/merge_request/:merge_request_id/comments" do 115 post ":id/merge_request/:merge_request_id/comments" do
  116 + required_attributes! [:note]
  117 +
105 merge_request = user_project.merge_requests.find(params[:merge_request_id]) 118 merge_request = user_project.merge_requests.find(params[:merge_request_id])
106 note = merge_request.notes.new(note: params[:note], project_id: user_project.id) 119 note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
107 note.author = current_user 120 note.author = current_user
lib/api/milestones.rb
@@ -41,6 +41,7 @@ module Gitlab @@ -41,6 +41,7 @@ module Gitlab
41 # POST /projects/:id/milestones 41 # POST /projects/:id/milestones
42 post ":id/milestones" do 42 post ":id/milestones" do
43 authorize! :admin_milestone, user_project 43 authorize! :admin_milestone, user_project
  44 + required_attributes! [:title]
44 45
45 attrs = attributes_for_keys [:title, :description, :due_date] 46 attrs = attributes_for_keys [:title, :description, :due_date]
46 @milestone = user_project.milestones.new attrs 47 @milestone = user_project.milestones.new attrs
lib/api/notes.rb
@@ -37,12 +37,16 @@ module Gitlab @@ -37,12 +37,16 @@ module Gitlab
37 # Example Request: 37 # Example Request:
38 # POST /projects/:id/notes 38 # POST /projects/:id/notes
39 post ":id/notes" do 39 post ":id/notes" do
  40 + required_attributes! [:body]
  41 +
40 @note = user_project.notes.new(note: params[:body]) 42 @note = user_project.notes.new(note: params[:body])
41 @note.author = current_user 43 @note.author = current_user
42 44
43 if @note.save 45 if @note.save
44 present @note, with: Entities::Note 46 present @note, with: Entities::Note
45 else 47 else
  48 + # :note is exposed as :body, but :note is set on error
  49 + bad_request!(:note) if @note.errors[:note].any?
46 not_found! 50 not_found!
47 end 51 end
48 end 52 end
@@ -89,6 +93,8 @@ module Gitlab @@ -89,6 +93,8 @@ module Gitlab
89 # POST /projects/:id/issues/:noteable_id/notes 93 # POST /projects/:id/issues/:noteable_id/notes
90 # POST /projects/:id/snippets/:noteable_id/notes 94 # POST /projects/:id/snippets/:noteable_id/notes
91 post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do 95 post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
  96 + required_attributes! [:body]
  97 +
92 @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) 98 @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
93 @note = @noteable.notes.new(note: params[:body]) 99 @note = @noteable.notes.new(note: params[:body])
94 @note.author = current_user 100 @note.author = current_user
lib/api/projects.rb
@@ -4,6 +4,15 @@ module Gitlab @@ -4,6 +4,15 @@ module Gitlab
4 before { authenticate! } 4 before { authenticate! }
5 5
6 resource :projects do 6 resource :projects do
  7 + helpers do
  8 + def handle_project_member_errors(errors)
  9 + if errors[:project_access].any?
  10 + error!(errors[:project_access], 422)
  11 + end
  12 + not_found!
  13 + end
  14 + end
  15 +
7 # Get a projects list for authenticated user 16 # Get a projects list for authenticated user
8 # 17 #
9 # Example Request: 18 # Example Request:
@@ -33,9 +42,11 @@ module Gitlab @@ -33,9 +42,11 @@ module Gitlab
33 # wall_enabled (optional) - enabled by default 42 # wall_enabled (optional) - enabled by default
34 # merge_requests_enabled (optional) - enabled by default 43 # merge_requests_enabled (optional) - enabled by default
35 # wiki_enabled (optional) - enabled by default 44 # wiki_enabled (optional) - enabled by default
  45 + # namespace_id (optional) - defaults to user namespace
36 # Example Request 46 # Example Request
37 # POST /projects 47 # POST /projects
38 post do 48 post do
  49 + required_attributes! [:name]
39 attrs = attributes_for_keys [:name, 50 attrs = attributes_for_keys [:name,
40 :description, 51 :description,
41 :default_branch, 52 :default_branch,
@@ -48,6 +59,9 @@ module Gitlab @@ -48,6 +59,9 @@ module Gitlab
48 if @project.saved? 59 if @project.saved?
49 present @project, with: Entities::Project 60 present @project, with: Entities::Project
50 else 61 else
  62 + if @project.errors[:limit_reached].present?
  63 + error!(@project.errors[:limit_reached], 403)
  64 + end
51 not_found! 65 not_found!
52 end 66 end
53 end 67 end
@@ -122,16 +136,22 @@ module Gitlab @@ -122,16 +136,22 @@ module Gitlab
122 # POST /projects/:id/members 136 # POST /projects/:id/members
123 post ":id/members" do 137 post ":id/members" do
124 authorize! :admin_project, user_project 138 authorize! :admin_project, user_project
125 - users_project = user_project.users_projects.new(  
126 - user_id: params[:user_id],  
127 - project_access: params[:access_level]  
128 - ) 139 + required_attributes! [:user_id, :access_level]
  140 +
  141 + # either the user is already a team member or a new one
  142 + team_member = user_project.team_member_by_id(params[:user_id])
  143 + if team_member.nil?
  144 + team_member = user_project.users_projects.new(
  145 + user_id: params[:user_id],
  146 + project_access: params[:access_level]
  147 + )
  148 + end
129 149
130 - if users_project.save  
131 - @member = users_project.user 150 + if team_member.save
  151 + @member = team_member.user
132 present @member, with: Entities::ProjectMember, project: user_project 152 present @member, with: Entities::ProjectMember, project: user_project
133 else 153 else
134 - not_found! 154 + handle_project_member_errors team_member.errors
135 end 155 end
136 end 156 end
137 157
@@ -145,13 +165,16 @@ module Gitlab @@ -145,13 +165,16 @@ module Gitlab
145 # PUT /projects/:id/members/:user_id 165 # PUT /projects/:id/members/:user_id
146 put ":id/members/:user_id" do 166 put ":id/members/:user_id" do
147 authorize! :admin_project, user_project 167 authorize! :admin_project, user_project
148 - users_project = user_project.users_projects.find_by_user_id params[:user_id] 168 + required_attributes! [:access_level]
  169 +
  170 + team_member = user_project.users_projects.find_by_user_id(params[:user_id])
  171 + not_found!("User can not be found") if team_member.nil?
149 172
150 - if users_project.update_attributes(project_access: params[:access_level])  
151 - @member = users_project.user 173 + if team_member.update_attributes(project_access: params[:access_level])
  174 + @member = team_member.user
152 present @member, with: Entities::ProjectMember, project: user_project 175 present @member, with: Entities::ProjectMember, project: user_project
153 else 176 else
154 - not_found! 177 + handle_project_member_errors team_member.errors
155 end 178 end
156 end 179 end
157 180
@@ -164,8 +187,12 @@ module Gitlab @@ -164,8 +187,12 @@ module Gitlab
164 # DELETE /projects/:id/members/:user_id 187 # DELETE /projects/:id/members/:user_id
165 delete ":id/members/:user_id" do 188 delete ":id/members/:user_id" do
166 authorize! :admin_project, user_project 189 authorize! :admin_project, user_project
167 - users_project = user_project.users_projects.find_by_user_id params[:user_id]  
168 - users_project.destroy 190 + team_member = user_project.users_projects.find_by_user_id(params[:user_id])
  191 + unless team_member.nil?
  192 + team_member.destroy
  193 + else
  194 + {:message => "Access revoked", :id => params[:user_id].to_i}
  195 + end
169 end 196 end
170 197
171 # Get project hooks 198 # Get project hooks
@@ -203,11 +230,16 @@ module Gitlab @@ -203,11 +230,16 @@ module Gitlab
203 # POST /projects/:id/hooks 230 # POST /projects/:id/hooks
204 post ":id/hooks" do 231 post ":id/hooks" do
205 authorize! :admin_project, user_project 232 authorize! :admin_project, user_project
  233 + required_attributes! [:url]
  234 +
206 @hook = user_project.hooks.new({"url" => params[:url]}) 235 @hook = user_project.hooks.new({"url" => params[:url]})
207 if @hook.save 236 if @hook.save
208 present @hook, with: Entities::Hook 237 present @hook, with: Entities::Hook
209 else 238 else
210 - error!({'message' => '404 Not found'}, 404) 239 + if @hook.errors[:url].present?
  240 + error!("Invalid url given", 422)
  241 + end
  242 + not_found!
211 end 243 end
212 end 244 end
213 245
@@ -222,27 +254,36 @@ module Gitlab @@ -222,27 +254,36 @@ module Gitlab
222 put ":id/hooks/:hook_id" do 254 put ":id/hooks/:hook_id" do
223 @hook = user_project.hooks.find(params[:hook_id]) 255 @hook = user_project.hooks.find(params[:hook_id])
224 authorize! :admin_project, user_project 256 authorize! :admin_project, user_project
  257 + required_attributes! [:url]
225 258
226 attrs = attributes_for_keys [:url] 259 attrs = attributes_for_keys [:url]
227 -  
228 if @hook.update_attributes attrs 260 if @hook.update_attributes attrs
229 present @hook, with: Entities::Hook 261 present @hook, with: Entities::Hook
230 else 262 else
  263 + if @hook.errors[:url].present?
  264 + error!("Invalid url given", 422)
  265 + end
231 not_found! 266 not_found!
232 end 267 end
233 end 268 end
234 269
235 - # Delete project hook 270 + # Deletes project hook. This is an idempotent function.
236 # 271 #
237 # Parameters: 272 # Parameters:
238 # id (required) - The ID of a project 273 # id (required) - The ID of a project
239 # hook_id (required) - The ID of hook to delete 274 # hook_id (required) - The ID of hook to delete
240 # Example Request: 275 # Example Request:
241 # DELETE /projects/:id/hooks/:hook_id 276 # DELETE /projects/:id/hooks/:hook_id
242 - delete ":id/hooks/:hook_id" do 277 + delete ":id/hooks" do
243 authorize! :admin_project, user_project 278 authorize! :admin_project, user_project
244 - @hook = user_project.hooks.find(params[:hook_id])  
245 - @hook.destroy 279 + required_attributes! [:hook_id]
  280 +
  281 + begin
  282 + @hook = ProjectHook.find(params[:hook_id])
  283 + @hook.destroy
  284 + rescue
  285 + # ProjectHook can raise Error if hook_id not found
  286 + end
246 end 287 end
247 288
248 # Get a project repository branches 289 # Get a project repository branches
@@ -277,6 +318,7 @@ module Gitlab @@ -277,6 +318,7 @@ module Gitlab
277 # PUT /projects/:id/repository/branches/:branch/protect 318 # PUT /projects/:id/repository/branches/:branch/protect
278 put ":id/repository/branches/:branch/protect" do 319 put ":id/repository/branches/:branch/protect" do
279 @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } 320 @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
  321 + not_found! unless @branch
280 protected = user_project.protected_branches.find_by_name(@branch.name) 322 protected = user_project.protected_branches.find_by_name(@branch.name)
281 323
282 unless protected 324 unless protected
@@ -295,6 +337,7 @@ module Gitlab @@ -295,6 +337,7 @@ module Gitlab
295 # PUT /projects/:id/repository/branches/:branch/unprotect 337 # PUT /projects/:id/repository/branches/:branch/unprotect
296 put ":id/repository/branches/:branch/unprotect" do 338 put ":id/repository/branches/:branch/unprotect" do
297 @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } 339 @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
  340 + not_found! unless @branch
298 protected = user_project.protected_branches.find_by_name(@branch.name) 341 protected = user_project.protected_branches.find_by_name(@branch.name)
299 342
300 if protected 343 if protected
@@ -318,7 +361,7 @@ module Gitlab @@ -318,7 +361,7 @@ module Gitlab
318 # 361 #
319 # Parameters: 362 # Parameters:
320 # id (required) - The ID of a project 363 # id (required) - The ID of a project
321 - # ref_name (optional) - The name of a repository branch or tag 364 + # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
322 # Example Request: 365 # Example Request:
323 # GET /projects/:id/repository/commits 366 # GET /projects/:id/repository/commits
324 get ":id/repository/commits" do 367 get ":id/repository/commits" do
@@ -366,6 +409,7 @@ module Gitlab @@ -366,6 +409,7 @@ module Gitlab
366 # POST /projects/:id/snippets 409 # POST /projects/:id/snippets
367 post ":id/snippets" do 410 post ":id/snippets" do
368 authorize! :write_snippet, user_project 411 authorize! :write_snippet, user_project
  412 + required_attributes! [:title, :file_name, :code]
369 413
370 attrs = attributes_for_keys [:title, :file_name] 414 attrs = attributes_for_keys [:title, :file_name]
371 attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? 415 attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
@@ -414,10 +458,12 @@ module Gitlab @@ -414,10 +458,12 @@ module Gitlab
414 # Example Request: 458 # Example Request:
415 # DELETE /projects/:id/snippets/:snippet_id 459 # DELETE /projects/:id/snippets/:snippet_id
416 delete ":id/snippets/:snippet_id" do 460 delete ":id/snippets/:snippet_id" do
417 - @snippet = user_project.snippets.find(params[:snippet_id])  
418 - authorize! :modify_snippet, @snippet  
419 -  
420 - @snippet.destroy 461 + begin
  462 + @snippet = user_project.snippets.find(params[:snippet_id])
  463 + authorize! :modify_snippet, user_project
  464 + @snippet.destroy
  465 + rescue
  466 + end
421 end 467 end
422 468
423 # Get a raw project snippet 469 # Get a raw project snippet
@@ -443,6 +489,7 @@ module Gitlab @@ -443,6 +489,7 @@ module Gitlab
443 # GET /projects/:id/repository/commits/:sha/blob 489 # GET /projects/:id/repository/commits/:sha/blob
444 get ":id/repository/commits/:sha/blob" do 490 get ":id/repository/commits/:sha/blob" do
445 authorize! :download_code, user_project 491 authorize! :download_code, user_project
  492 + required_attributes! [:filepath]
446 493
447 ref = params[:sha] 494 ref = params[:sha]
448 495
lib/api/users.rb
@@ -41,6 +41,8 @@ module Gitlab @@ -41,6 +41,8 @@ module Gitlab
41 # POST /users 41 # POST /users
42 post do 42 post do
43 authenticated_as_admin! 43 authenticated_as_admin!
  44 + required_attributes! [:email, :password, :name, :username]
  45 +
44 attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] 46 attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
45 user = User.new attrs, as: :admin 47 user = User.new attrs, as: :admin
46 if user.save 48 if user.save
@@ -67,10 +69,12 @@ module Gitlab @@ -67,10 +69,12 @@ module Gitlab
67 # PUT /users/:id 69 # PUT /users/:id
68 put ":id" do 70 put ":id" do
69 authenticated_as_admin! 71 authenticated_as_admin!
  72 +
70 attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] 73 attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
71 - user = User.find_by_id(params[:id]) 74 + user = User.find(params[:id])
  75 + not_found!("User not found") unless user
72 76
73 - if user && user.update_attributes(attrs) 77 + if user.update_attributes(attrs)
74 present user, with: Entities::User 78 present user, with: Entities::User
75 else 79 else
76 not_found! 80 not_found!
@@ -147,6 +151,8 @@ module Gitlab @@ -147,6 +151,8 @@ module Gitlab
147 # Example Request: 151 # Example Request:
148 # POST /user/keys 152 # POST /user/keys
149 post "keys" do 153 post "keys" do
  154 + required_attributes! [:title, :key]
  155 +
150 attrs = attributes_for_keys [:title, :key] 156 attrs = attributes_for_keys [:title, :key]
151 key = current_user.keys.new attrs 157 key = current_user.keys.new attrs
152 if key.save 158 if key.save
@@ -156,15 +162,18 @@ module Gitlab @@ -156,15 +162,18 @@ module Gitlab
156 end 162 end
157 end 163 end
158 164
159 - # Delete existed ssh key of currently authenticated user 165 + # Delete existing ssh key of currently authenticated user
160 # 166 #
161 # Parameters: 167 # Parameters:
162 # id (required) - SSH Key ID 168 # id (required) - SSH Key ID
163 # Example Request: 169 # Example Request:
164 # DELETE /user/keys/:id 170 # DELETE /user/keys/:id
165 delete "keys/:id" do 171 delete "keys/:id" do
166 - key = current_user.keys.find params[:id]  
167 - key.delete 172 + begin
  173 + key = current_user.keys.find params[:id]
  174 + key.delete
  175 + rescue
  176 + end
168 end 177 end
169 end 178 end
170 end 179 end
spec/models/project_spec.rb
@@ -65,7 +65,7 @@ describe Project do @@ -65,7 +65,7 @@ describe Project do
65 it "should not allow new projects beyond user limits" do 65 it "should not allow new projects beyond user limits" do
66 project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1)) 66 project.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 1))
67 project.should_not be_valid 67 project.should_not be_valid
68 - project.errors[:base].first.should match(/Your own projects limit is 1/) 68 + project.errors[:limit_reached].first.should match(/Your own projects limit is 1/)
69 end 69 end
70 end 70 end
71 71
spec/requests/api/groups_spec.rb
@@ -88,6 +88,16 @@ describe Gitlab::API do @@ -88,6 +88,16 @@ describe Gitlab::API do
88 post api("/groups", admin), {:name => "Duplicate Test", :path => group2.path} 88 post api("/groups", admin), {:name => "Duplicate Test", :path => group2.path}
89 response.status.should == 404 89 response.status.should == 404
90 end 90 end
  91 +
  92 + it "should return 400 bad request error if name not given" do
  93 + post api("/groups", admin), { :path => group2.path }
  94 + response.status.should == 400
  95 + end
  96 +
  97 + it "should return 400 bad request error if path not given" do
  98 + post api("/groups", admin), { :name => 'test' }
  99 + response.status.should == 400
  100 + end
91 end 101 end
92 end 102 end
93 103
spec/requests/api/issues_spec.rb
@@ -41,6 +41,11 @@ describe Gitlab::API do @@ -41,6 +41,11 @@ describe Gitlab::API do
41 response.status.should == 200 41 response.status.should == 200
42 json_response['title'].should == issue.title 42 json_response['title'].should == issue.title
43 end 43 end
  44 +
  45 + it "should return 404 if issue id not found" do
  46 + get api("/projects/#{project.id}/issues/54321", user)
  47 + response.status.should == 404
  48 + end
44 end 49 end
45 50
46 describe "POST /projects/:id/issues" do 51 describe "POST /projects/:id/issues" do
@@ -52,6 +57,11 @@ describe Gitlab::API do @@ -52,6 +57,11 @@ describe Gitlab::API do
52 json_response['description'].should be_nil 57 json_response['description'].should be_nil
53 json_response['labels'].should == ['label', 'label2'] 58 json_response['labels'].should == ['label', 'label2']
54 end 59 end
  60 +
  61 + it "should return a 400 bad request if title not given" do
  62 + post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
  63 + response.status.should == 400
  64 + end
55 end 65 end
56 66
57 describe "PUT /projects/:id/issues/:issue_id to update only title" do 67 describe "PUT /projects/:id/issues/:issue_id to update only title" do
@@ -62,6 +72,12 @@ describe Gitlab::API do @@ -62,6 +72,12 @@ describe Gitlab::API do
62 72
63 json_response['title'].should == 'updated title' 73 json_response['title'].should == 'updated title'
64 end 74 end
  75 +
  76 + it "should return 404 error if issue id not found" do
  77 + put api("/projects/#{project.id}/issues/44444", user),
  78 + title: 'updated title'
  79 + response.status.should == 404
  80 + end
65 end 81 end
66 82
67 describe "PUT /projects/:id/issues/:issue_id to update state and label" do 83 describe "PUT /projects/:id/issues/:issue_id to update state and label" do
spec/requests/api/merge_requests_spec.rb
@@ -32,6 +32,11 @@ describe Gitlab::API do @@ -32,6 +32,11 @@ describe Gitlab::API do
32 response.status.should == 200 32 response.status.should == 200
33 json_response['title'].should == merge_request.title 33 json_response['title'].should == merge_request.title
34 end 34 end
  35 +
  36 + it "should return a 404 error if merge_request_id not found" do
  37 + get api("/projects/#{project.id}/merge_request/999", user)
  38 + response.status.should == 404
  39 + end
35 end 40 end
36 41
37 describe "POST /projects/:id/merge_requests" do 42 describe "POST /projects/:id/merge_requests" do
@@ -41,6 +46,30 @@ describe Gitlab::API do @@ -41,6 +46,30 @@ describe Gitlab::API do
41 response.status.should == 201 46 response.status.should == 201
42 json_response['title'].should == 'Test merge_request' 47 json_response['title'].should == 'Test merge_request'
43 end 48 end
  49 +
  50 + it "should return 422 when source_branch equals target_branch" do
  51 + post api("/projects/#{project.id}/merge_requests", user),
  52 + title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
  53 + response.status.should == 422
  54 + end
  55 +
  56 + it "should return 400 when source_branch is missing" do
  57 + post api("/projects/#{project.id}/merge_requests", user),
  58 + title: "Test merge_request", target_branch: "master", author: user
  59 + response.status.should == 400
  60 + end
  61 +
  62 + it "should return 400 when target_branch is missing" do
  63 + post api("/projects/#{project.id}/merge_requests", user),
  64 + title: "Test merge_request", source_branch: "stable", author: user
  65 + response.status.should == 400
  66 + end
  67 +
  68 + it "should return 400 when title is missing" do
  69 + post api("/projects/#{project.id}/merge_requests", user),
  70 + target_branch: 'master', source_branch: 'stable'
  71 + response.status.should == 400
  72 + end
44 end 73 end
45 74
46 describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do 75 describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
@@ -59,13 +88,24 @@ describe Gitlab::API do @@ -59,13 +88,24 @@ describe Gitlab::API do
59 end 88 end
60 end 89 end
61 90
62 -  
63 describe "PUT /projects/:id/merge_request/:merge_request_id" do 91 describe "PUT /projects/:id/merge_request/:merge_request_id" do
64 it "should return merge_request" do 92 it "should return merge_request" do
65 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title" 93 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
66 response.status.should == 200 94 response.status.should == 200
67 json_response['title'].should == 'New title' 95 json_response['title'].should == 'New title'
68 end 96 end
  97 +
  98 + it "should return 422 when source_branch and target_branch are renamed the same" do
  99 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
  100 + source_branch: "master", target_branch: "master"
  101 + response.status.should == 422
  102 + end
  103 +
  104 + it "should return merge_request with renamed target_branch" do
  105 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "test"
  106 + response.status.should == 200
  107 + json_response['target_branch'].should == 'test'
  108 + end
69 end 109 end
70 110
71 describe "POST /projects/:id/merge_request/:merge_request_id/comments" do 111 describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
@@ -74,6 +114,16 @@ describe Gitlab::API do @@ -74,6 +114,16 @@ describe Gitlab::API do
74 response.status.should == 201 114 response.status.should == 201
75 json_response['note'].should == 'My comment' 115 json_response['note'].should == 'My comment'
76 end 116 end
  117 +
  118 + it "should return 400 if note is missing" do
  119 + post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
  120 + response.status.should == 400
  121 + end
  122 +
  123 + it "should return 404 if note is attached to non existent merge request" do
  124 + post api("/projects/#{project.id}/merge_request/111/comments", user), note: "My comment"
  125 + response.status.should == 404
  126 + end
77 end 127 end
78 128
79 end 129 end
spec/requests/api/milestones_spec.rb
@@ -16,6 +16,11 @@ describe Gitlab::API do @@ -16,6 +16,11 @@ describe Gitlab::API do
16 json_response.should be_an Array 16 json_response.should be_an Array
17 json_response.first['title'].should == milestone.title 17 json_response.first['title'].should == milestone.title
18 end 18 end
  19 +
  20 + it "should return a 401 error if user not authenticated" do
  21 + get api("/projects/#{project.id}/milestones")
  22 + response.status.should == 401
  23 + end
19 end 24 end
20 25
21 describe "GET /projects/:id/milestones/:milestone_id" do 26 describe "GET /projects/:id/milestones/:milestone_id" do
@@ -24,16 +29,38 @@ describe Gitlab::API do @@ -24,16 +29,38 @@ describe Gitlab::API do
24 response.status.should == 200 29 response.status.should == 200
25 json_response['title'].should == milestone.title 30 json_response['title'].should == milestone.title
26 end 31 end
  32 +
  33 + it "should return 401 error if user not authenticated" do
  34 + get api("/projects/#{project.id}/milestones/#{milestone.id}")
  35 + response.status.should == 401
  36 + end
  37 +
  38 + it "should return a 404 error if milestone id not found" do
  39 + get api("/projects/#{project.id}/milestones/1234", user)
  40 + response.status.should == 404
  41 + end
27 end 42 end
28 43
29 describe "POST /projects/:id/milestones" do 44 describe "POST /projects/:id/milestones" do
30 it "should create a new project milestone" do 45 it "should create a new project milestone" do
31 - post api("/projects/#{project.id}/milestones", user),  
32 - title: 'new milestone' 46 + post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
33 response.status.should == 201 47 response.status.should == 201
34 json_response['title'].should == 'new milestone' 48 json_response['title'].should == 'new milestone'
35 json_response['description'].should be_nil 49 json_response['description'].should be_nil
36 end 50 end
  51 +
  52 + it "should create a new project milestone with description and due date" do
  53 + post api("/projects/#{project.id}/milestones", user),
  54 + title: 'new milestone', description: 'release', due_date: '2013-03-02'
  55 + response.status.should == 201
  56 + json_response['description'].should == 'release'
  57 + json_response['due_date'].should == '2013-03-02'
  58 + end
  59 +
  60 + it "should return a 400 error if title is missing" do
  61 + post api("/projects/#{project.id}/milestones", user)
  62 + response.status.should == 400
  63 + end
37 end 64 end
38 65
39 describe "PUT /projects/:id/milestones/:milestone_id" do 66 describe "PUT /projects/:id/milestones/:milestone_id" do
@@ -43,6 +70,12 @@ describe Gitlab::API do @@ -43,6 +70,12 @@ describe Gitlab::API do
43 response.status.should == 200 70 response.status.should == 200
44 json_response['title'].should == 'updated title' 71 json_response['title'].should == 'updated title'
45 end 72 end
  73 +
  74 + it "should return a 404 error if milestone id not found" do
  75 + put api("/projects/#{project.id}/milestones/1234", user),
  76 + title: 'updated title'
  77 + response.status.should == 404
  78 + end
46 end 79 end
47 80
48 describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do 81 describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
spec/requests/api/notes_spec.rb
@@ -38,6 +38,11 @@ describe Gitlab::API do @@ -38,6 +38,11 @@ describe Gitlab::API do
38 response.status.should == 200 38 response.status.should == 200
39 json_response['body'].should == wall_note.note 39 json_response['body'].should == wall_note.note
40 end 40 end
  41 +
  42 + it "should return a 404 error if note not found" do
  43 + get api("/projects/#{project.id}/notes/123", user)
  44 + response.status.should == 404
  45 + end
41 end 46 end
42 47
43 describe "POST /projects/:id/notes" do 48 describe "POST /projects/:id/notes" do
@@ -46,6 +51,16 @@ describe Gitlab::API do @@ -46,6 +51,16 @@ describe Gitlab::API do
46 response.status.should == 201 51 response.status.should == 201
47 json_response['body'].should == 'hi!' 52 json_response['body'].should == 'hi!'
48 end 53 end
  54 +
  55 + it "should return 401 unauthorized error" do
  56 + post api("/projects/#{project.id}/notes")
  57 + response.status.should == 401
  58 + end
  59 +
  60 + it "should return a 400 bad request if body is missing" do
  61 + post api("/projects/#{project.id}/notes", user)
  62 + response.status.should == 400
  63 + end
49 end 64 end
50 65
51 describe "GET /projects/:id/noteable/:noteable_id/notes" do 66 describe "GET /projects/:id/noteable/:noteable_id/notes" do
@@ -56,6 +71,11 @@ describe Gitlab::API do @@ -56,6 +71,11 @@ describe Gitlab::API do
56 json_response.should be_an Array 71 json_response.should be_an Array
57 json_response.first['body'].should == issue_note.note 72 json_response.first['body'].should == issue_note.note
58 end 73 end
  74 +
  75 + it "should return a 404 error when issue id not found" do
  76 + get api("/projects/#{project.id}/issues/123/notes", user)
  77 + response.status.should == 404
  78 + end
59 end 79 end
60 80
61 context "when noteable is a Snippet" do 81 context "when noteable is a Snippet" do
@@ -65,6 +85,11 @@ describe Gitlab::API do @@ -65,6 +85,11 @@ describe Gitlab::API do
65 json_response.should be_an Array 85 json_response.should be_an Array
66 json_response.first['body'].should == snippet_note.note 86 json_response.first['body'].should == snippet_note.note
67 end 87 end
  88 +
  89 + it "should return a 404 error when snippet id not found" do
  90 + get api("/projects/#{project.id}/snippets/42/notes", user)
  91 + response.status.should == 404
  92 + end
68 end 93 end
69 94
70 context "when noteable is a Merge Request" do 95 context "when noteable is a Merge Request" do
@@ -74,6 +99,11 @@ describe Gitlab::API do @@ -74,6 +99,11 @@ describe Gitlab::API do
74 json_response.should be_an Array 99 json_response.should be_an Array
75 json_response.first['body'].should == merge_request_note.note 100 json_response.first['body'].should == merge_request_note.note
76 end 101 end
  102 +
  103 + it "should return a 404 error if merge request id not found" do
  104 + get api("/projects/#{project.id}/merge_requests/4444/notes", user)
  105 + response.status.should == 404
  106 + end
77 end 107 end
78 end 108 end
79 109
@@ -84,6 +114,11 @@ describe Gitlab::API do @@ -84,6 +114,11 @@ describe Gitlab::API do
84 response.status.should == 200 114 response.status.should == 200
85 json_response['body'].should == issue_note.note 115 json_response['body'].should == issue_note.note
86 end 116 end
  117 +
  118 + it "should return a 404 error if issue note not found" do
  119 + get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
  120 + response.status.should == 404
  121 + end
87 end 122 end
88 123
89 context "when noteable is a Snippet" do 124 context "when noteable is a Snippet" do
@@ -92,6 +127,11 @@ describe Gitlab::API do @@ -92,6 +127,11 @@ describe Gitlab::API do
92 response.status.should == 200 127 response.status.should == 200
93 json_response['body'].should == snippet_note.note 128 json_response['body'].should == snippet_note.note
94 end 129 end
  130 +
  131 + it "should return a 404 error if snippet note not found" do
  132 + get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
  133 + response.status.should == 404
  134 + end
95 end 135 end
96 end 136 end
97 137
@@ -103,6 +143,16 @@ describe Gitlab::API do @@ -103,6 +143,16 @@ describe Gitlab::API do
103 json_response['body'].should == 'hi!' 143 json_response['body'].should == 'hi!'
104 json_response['author']['email'].should == user.email 144 json_response['author']['email'].should == user.email
105 end 145 end
  146 +
  147 + it "should return a 400 bad request error if body not given" do
  148 + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
  149 + response.status.should == 400
  150 + end
  151 +
  152 + it "should return a 401 unauthorized error if user not authenticated" do
  153 + post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!'
  154 + response.status.should == 401
  155 + end
106 end 156 end
107 157
108 context "when noteable is a Snippet" do 158 context "when noteable is a Snippet" do
@@ -112,6 +162,16 @@ describe Gitlab::API do @@ -112,6 +162,16 @@ describe Gitlab::API do
112 json_response['body'].should == 'hi!' 162 json_response['body'].should == 'hi!'
113 json_response['author']['email'].should == user.email 163 json_response['author']['email'].should == user.email
114 end 164 end
  165 +
  166 + it "should return a 400 bad request error if body not given" do
  167 + post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
  168 + response.status.should == 400
  169 + end
  170 +
  171 + it "should return a 401 unauthorized error if user not authenticated" do
  172 + post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
  173 + response.status.should == 401
  174 + end
115 end 175 end
116 end 176 end
117 end 177 end
spec/requests/api/projects_spec.rb
@@ -7,8 +7,8 @@ describe Gitlab::API do @@ -7,8 +7,8 @@ describe Gitlab::API do
7 let(:user2) { create(:user) } 7 let(:user2) { create(:user) }
8 let(:user3) { create(:user) } 8 let(:user3) { create(:user) }
9 let(:admin) { create(:admin) } 9 let(:admin) { create(:admin) }
10 - let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }  
11 let!(:project) { create(:project, namespace: user.namespace ) } 10 let!(:project) { create(:project, namespace: user.namespace ) }
  11 + let!(:hook) { create(:project_hook, project: project, url: "http://example.com") }
12 let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') } 12 let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') }
13 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } 13 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
14 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } 14 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
@@ -58,6 +58,11 @@ describe Gitlab::API do @@ -58,6 +58,11 @@ describe Gitlab::API do
58 expect { post api("/projects", user) }.to_not change {Project.count} 58 expect { post api("/projects", user) }.to_not change {Project.count}
59 end 59 end
60 60
  61 + it "should return a 400 error if name not given" do
  62 + post api("/projects", user)
  63 + response.status.should == 400
  64 + end
  65 +
61 it "should create last project before reaching project limit" do 66 it "should create last project before reaching project limit" do
62 (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" } 67 (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" }
63 post api("/projects", user2), name: "foo" 68 post api("/projects", user2), name: "foo"
@@ -69,9 +74,17 @@ describe Gitlab::API do @@ -69,9 +74,17 @@ describe Gitlab::API do
69 response.status.should == 201 74 response.status.should == 201
70 end 75 end
71 76
72 - it "should respond with 404 on failure" do 77 + it "should respond with 400 if name is not given" do
73 post api("/projects", user) 78 post api("/projects", user)
74 - response.status.should == 404 79 + response.status.should == 400
  80 + end
  81 +
  82 + it "should return a 403 error if project limit reached" do
  83 + (1..user.projects_limit).each do |p|
  84 + post api("/projects", user), name: "foo#{p}"
  85 + end
  86 + post api("/projects", user), name: 'bar'
  87 + response.status.should == 403
75 end 88 end
76 89
77 it "should assign attributes to project" do 90 it "should assign attributes to project" do
@@ -152,6 +165,12 @@ describe Gitlab::API do @@ -152,6 +165,12 @@ describe Gitlab::API do
152 response.status.should == 404 165 response.status.should == 404
153 json_response['message'].should == '404 Not Found' 166 json_response['message'].should == '404 Not Found'
154 end 167 end
  168 +
  169 + it "should return a 404 error if user is not a member" do
  170 + other_user = create(:user)
  171 + get api("/projects/#{project.id}", other_user)
  172 + response.status.should == 404
  173 + end
155 end 174 end
156 175
157 describe "GET /projects/:id/repository/branches" do 176 describe "GET /projects/:id/repository/branches" do
@@ -188,6 +207,17 @@ describe Gitlab::API do @@ -188,6 +207,17 @@ describe Gitlab::API do
188 json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' 207 json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
189 json_response['protected'].should == true 208 json_response['protected'].should == true
190 end 209 end
  210 +
  211 + it "should return a 404 error if branch not found" do
  212 + put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
  213 + response.status.should == 404
  214 + end
  215 +
  216 + it "should return success when protect branch again" do
  217 + put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
  218 + put api("/projects/#{project.id}/repository/branches/new_design/protect", user)
  219 + response.status.should == 200
  220 + end
191 end 221 end
192 222
193 describe "PUT /projects/:id/repository/branches/:branch/unprotect" do 223 describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
@@ -199,6 +229,17 @@ describe Gitlab::API do @@ -199,6 +229,17 @@ describe Gitlab::API do
199 json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1' 229 json_response['commit']['id'].should == '621491c677087aa243f165eab467bfdfbee00be1'
200 json_response['protected'].should == false 230 json_response['protected'].should == false
201 end 231 end
  232 +
  233 + it "should return success when unprotect branch" do
  234 + put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
  235 + response.status.should == 404
  236 + end
  237 +
  238 + it "should return success when unprotect branch again" do
  239 + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
  240 + put api("/projects/#{project.id}/repository/branches/new_design/unprotect", user)
  241 + response.status.should == 200
  242 + end
202 end 243 end
203 244
204 describe "GET /projects/:id/members" do 245 describe "GET /projects/:id/members" do
@@ -217,6 +258,11 @@ describe Gitlab::API do @@ -217,6 +258,11 @@ describe Gitlab::API do
217 json_response.count.should == 1 258 json_response.count.should == 1
218 json_response.first['email'].should == user.email 259 json_response.first['email'].should == user.email
219 end 260 end
  261 +
  262 + it "should return a 404 error if id not found" do
  263 + get api("/projects/9999/members", user)
  264 + response.status.should == 404
  265 + end
220 end 266 end
221 267
222 describe "GET /projects/:id/members/:user_id" do 268 describe "GET /projects/:id/members/:user_id" do
@@ -226,6 +272,11 @@ describe Gitlab::API do @@ -226,6 +272,11 @@ describe Gitlab::API do
226 json_response['email'].should == user.email 272 json_response['email'].should == user.email
227 json_response['access_level'].should == UsersProject::MASTER 273 json_response['access_level'].should == UsersProject::MASTER
228 end 274 end
  275 +
  276 + it "should return a 404 error if user id not found" do
  277 + get api("/projects/#{project.id}/members/1234", user)
  278 + response.status.should == 404
  279 + end
229 end 280 end
230 281
231 describe "POST /projects/:id/members" do 282 describe "POST /projects/:id/members" do
@@ -239,6 +290,34 @@ describe Gitlab::API do @@ -239,6 +290,34 @@ describe Gitlab::API do
239 json_response['email'].should == user2.email 290 json_response['email'].should == user2.email
240 json_response['access_level'].should == UsersProject::DEVELOPER 291 json_response['access_level'].should == UsersProject::DEVELOPER
241 end 292 end
  293 +
  294 + it "should return a 201 status if user is already project member" do
  295 + post api("/projects/#{project.id}/members", user), user_id: user2.id,
  296 + access_level: UsersProject::DEVELOPER
  297 + expect {
  298 + post api("/projects/#{project.id}/members", user), user_id: user2.id,
  299 + access_level: UsersProject::DEVELOPER
  300 + }.not_to change { UsersProject.count }.by(1)
  301 +
  302 + response.status.should == 201
  303 + json_response['email'].should == user2.email
  304 + json_response['access_level'].should == UsersProject::DEVELOPER
  305 + end
  306 +
  307 + it "should return a 400 error when user id is not given" do
  308 + post api("/projects/#{project.id}/members", user), access_level: UsersProject::MASTER
  309 + response.status.should == 400
  310 + end
  311 +
  312 + it "should return a 400 error when access level is not given" do
  313 + post api("/projects/#{project.id}/members", user), user_id: user2.id
  314 + response.status.should == 400
  315 + end
  316 +
  317 + it "should return a 422 error when access level is not known" do
  318 + post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234
  319 + response.status.should == 422
  320 + end
242 end 321 end
243 322
244 describe "PUT /projects/:id/members/:user_id" do 323 describe "PUT /projects/:id/members/:user_id" do
@@ -248,6 +327,21 @@ describe Gitlab::API do @@ -248,6 +327,21 @@ describe Gitlab::API do
248 json_response['email'].should == user3.email 327 json_response['email'].should == user3.email
249 json_response['access_level'].should == UsersProject::MASTER 328 json_response['access_level'].should == UsersProject::MASTER
250 end 329 end
  330 +
  331 + it "should return a 404 error if user_id is not found" do
  332 + put api("/projects/#{project.id}/members/1234", user), access_level: UsersProject::MASTER
  333 + response.status.should == 404
  334 + end
  335 +
  336 + it "should return a 400 error when access level is not given" do
  337 + put api("/projects/#{project.id}/members/#{user3.id}", user)
  338 + response.status.should == 400
  339 + end
  340 +
  341 + it "should return a 422 error when access level is not known" do
  342 + put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123
  343 + response.status.should == 422
  344 + end
251 end 345 end
252 346
253 describe "DELETE /projects/:id/members/:user_id" do 347 describe "DELETE /projects/:id/members/:user_id" do
@@ -256,6 +350,30 @@ describe Gitlab::API do @@ -256,6 +350,30 @@ describe Gitlab::API do
256 delete api("/projects/#{project.id}/members/#{user3.id}", user) 350 delete api("/projects/#{project.id}/members/#{user3.id}", user)
257 }.to change { UsersProject.count }.by(-1) 351 }.to change { UsersProject.count }.by(-1)
258 end 352 end
  353 +
  354 + it "should return 200 if team member is not part of a project" do
  355 + delete api("/projects/#{project.id}/members/#{user3.id}", user)
  356 + expect {
  357 + delete api("/projects/#{project.id}/members/#{user3.id}", user)
  358 + }.to_not change { UsersProject.count }.by(1)
  359 + end
  360 +
  361 + it "should return 200 if team member already removed" do
  362 + delete api("/projects/#{project.id}/members/#{user3.id}", user)
  363 + delete api("/projects/#{project.id}/members/#{user3.id}", user)
  364 + response.status.should == 200
  365 + end
  366 + end
  367 +
  368 + describe "DELETE /projects/:id/members/:user_id" do
  369 + it "should return 200 OK when the user was not member" do
  370 + expect {
  371 + delete api("/projects/#{project.id}/members/1000000", user)
  372 + }.to change { UsersProject.count }.by(0)
  373 + response.status.should == 200
  374 + json_response['message'].should == "Access revoked"
  375 + json_response['id'].should == 1000000
  376 + end
259 end 377 end
260 378
261 describe "GET /projects/:id/hooks" do 379 describe "GET /projects/:id/hooks" do
@@ -298,6 +416,11 @@ describe Gitlab::API do @@ -298,6 +416,11 @@ describe Gitlab::API do
298 response.status.should == 403 416 response.status.should == 403
299 end 417 end
300 end 418 end
  419 +
  420 + it "should return a 404 error if hook id is not available" do
  421 + get api("/projects/#{project.id}/hooks/1234", user)
  422 + response.status.should == 404
  423 + end
301 end 424 end
302 425
303 describe "POST /projects/:id/hooks" do 426 describe "POST /projects/:id/hooks" do
@@ -306,6 +429,17 @@ describe Gitlab::API do @@ -306,6 +429,17 @@ describe Gitlab::API do
306 post api("/projects/#{project.id}/hooks", user), 429 post api("/projects/#{project.id}/hooks", user),
307 url: "http://example.com" 430 url: "http://example.com"
308 }.to change {project.hooks.count}.by(1) 431 }.to change {project.hooks.count}.by(1)
  432 + response.status.should == 201
  433 + end
  434 +
  435 + it "should return a 400 error if url not given" do
  436 + post api("/projects/#{project.id}/hooks", user)
  437 + response.status.should == 400
  438 + end
  439 +
  440 + it "should return a 422 error if url not valid" do
  441 + post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com"
  442 + response.status.should == 422
309 end 443 end
310 end 444 end
311 445
@@ -316,13 +450,44 @@ describe Gitlab::API do @@ -316,13 +450,44 @@ describe Gitlab::API do
316 response.status.should == 200 450 response.status.should == 200
317 json_response['url'].should == 'http://example.org' 451 json_response['url'].should == 'http://example.org'
318 end 452 end
  453 +
  454 + it "should return 404 error if hook id not found" do
  455 + put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
  456 + response.status.should == 404
  457 + end
  458 +
  459 + it "should return 400 error if url is not given" do
  460 + put api("/projects/#{project.id}/hooks/#{hook.id}", user)
  461 + response.status.should == 400
  462 + end
  463 +
  464 + it "should return a 422 error if url is not valid" do
  465 + put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
  466 + response.status.should == 422
  467 + end
319 end 468 end
320 469
321 - describe "DELETE /projects/:id/hooks/:hook_id" do 470 + describe "DELETE /projects/:id/hooks" do
322 it "should delete hook from project" do 471 it "should delete hook from project" do
323 expect { 472 expect {
324 - delete api("/projects/#{project.id}/hooks/#{hook.id}", user) 473 + delete api("/projects/#{project.id}/hooks", user), hook_id: hook.id
325 }.to change {project.hooks.count}.by(-1) 474 }.to change {project.hooks.count}.by(-1)
  475 + response.status.should == 200
  476 + end
  477 +
  478 + it "should return success when deleting hook" do
  479 + delete api("/projects/#{project.id}/hooks", user), hook_id: hook.id
  480 + response.status.should == 200
  481 + end
  482 +
  483 + it "should return success when deleting non existent hook" do
  484 + delete api("/projects/#{project.id}/hooks", user), hook_id: 42
  485 + response.status.should == 200
  486 + end
  487 +
  488 + it "should return a 400 error if hook id not given" do
  489 + delete api("/projects/#{project.id}/hooks", user)
  490 + response.status.should == 400
326 end 491 end
327 end 492 end
328 493
@@ -371,6 +536,11 @@ describe Gitlab::API do @@ -371,6 +536,11 @@ describe Gitlab::API do
371 response.status.should == 200 536 response.status.should == 200
372 json_response['title'].should == snippet.title 537 json_response['title'].should == snippet.title
373 end 538 end
  539 +
  540 + it "should return a 404 error if snippet id not found" do
  541 + get api("/projects/#{project.id}/snippets/1234", user)
  542 + response.status.should == 404
  543 + end
374 end 544 end
375 545
376 describe "POST /projects/:id/snippets" do 546 describe "POST /projects/:id/snippets" do
@@ -380,6 +550,24 @@ describe Gitlab::API do @@ -380,6 +550,24 @@ describe Gitlab::API do
380 response.status.should == 201 550 response.status.should == 201
381 json_response['title'].should == 'api test' 551 json_response['title'].should == 'api test'
382 end 552 end
  553 +
  554 + it "should return a 400 error if title is not given" do
  555 + post api("/projects/#{project.id}/snippets", user),
  556 + file_name: 'sample.rb', code: 'test'
  557 + response.status.should == 400
  558 + end
  559 +
  560 + it "should return a 400 error if file_name not given" do
  561 + post api("/projects/#{project.id}/snippets", user),
  562 + title: 'api test', code: 'test'
  563 + response.status.should == 400
  564 + end
  565 +
  566 + it "should return a 400 error if code not given" do
  567 + post api("/projects/#{project.id}/snippets", user),
  568 + title: 'api test', file_name: 'sample.rb'
  569 + response.status.should == 400
  570 + end
383 end 571 end
384 572
385 describe "PUT /projects/:id/snippets/:shippet_id" do 573 describe "PUT /projects/:id/snippets/:shippet_id" do
@@ -390,6 +578,13 @@ describe Gitlab::API do @@ -390,6 +578,13 @@ describe Gitlab::API do
390 json_response['title'].should == 'example' 578 json_response['title'].should == 'example'
391 snippet.reload.content.should == 'updated code' 579 snippet.reload.content.should == 'updated code'
392 end 580 end
  581 +
  582 + it "should update an existing project snippet with new title" do
  583 + put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
  584 + title: 'other api test'
  585 + response.status.should == 200
  586 + json_response['title'].should == 'other api test'
  587 + end
393 end 588 end
394 589
395 describe "DELETE /projects/:id/snippets/:snippet_id" do 590 describe "DELETE /projects/:id/snippets/:snippet_id" do
@@ -397,6 +592,12 @@ describe Gitlab::API do @@ -397,6 +592,12 @@ describe Gitlab::API do
397 expect { 592 expect {
398 delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) 593 delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
399 }.to change { Snippet.count }.by(-1) 594 }.to change { Snippet.count }.by(-1)
  595 + response.status.should == 200
  596 + end
  597 +
  598 + it "should return success when deleting unknown snippet id" do
  599 + delete api("/projects/#{project.id}/snippets/1234", user)
  600 + response.status.should == 200
400 end 601 end
401 end 602 end
402 603
@@ -405,9 +606,14 @@ describe Gitlab::API do @@ -405,9 +606,14 @@ describe Gitlab::API do
405 get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) 606 get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user)
406 response.status.should == 200 607 response.status.should == 200
407 end 608 end
  609 +
  610 + it "should return a 404 error if raw project snippet not found" do
  611 + get api("/projects/#{project.id}/snippets/5555/raw", user)
  612 + response.status.should == 404
  613 + end
408 end 614 end
409 615
410 - describe "GET /projects/:id/:sha/blob" do 616 + describe "GET /projects/:id/repository/commits/:sha/blob" do
411 it "should get the raw file contents" do 617 it "should get the raw file contents" do
412 get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user) 618 get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user)
413 response.status.should == 200 619 response.status.should == 200
@@ -422,6 +628,11 @@ describe Gitlab::API do @@ -422,6 +628,11 @@ describe Gitlab::API do
422 get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.invalid", user) 628 get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.invalid", user)
423 response.status.should == 404 629 response.status.should == 404
424 end 630 end
  631 +
  632 + it "should return a 400 error if filepath is missing" do
  633 + get api("/projects/#{project.id}/repository/commits/master/blob", user)
  634 + response.status.should == 400
  635 + end
425 end 636 end
426 637
427 describe "GET /projects/:id/keys" do 638 describe "GET /projects/:id/keys" do
spec/requests/api/session_spec.rb
@@ -35,5 +35,15 @@ describe Gitlab::API do @@ -35,5 +35,15 @@ describe Gitlab::API do
35 json_response['private_token'].should be_nil 35 json_response['private_token'].should be_nil
36 end 36 end
37 end 37 end
  38 +
  39 + context "when empty name" do
  40 + it "should return authentication error" do
  41 + post api("/session"), password: user.password
  42 + response.status.should == 401
  43 +
  44 + json_response['email'].should be_nil
  45 + json_response['private_token'].should be_nil
  46 + end
  47 + end
38 end 48 end
39 end 49 end
spec/requests/api/users_spec.rb
@@ -31,15 +31,20 @@ describe Gitlab::API do @@ -31,15 +31,20 @@ describe Gitlab::API do
31 response.status.should == 200 31 response.status.should == 200
32 json_response['email'].should == user.email 32 json_response['email'].should == user.email
33 end 33 end
34 - end  
35 34
36 - describe "POST /users" do  
37 - before{ admin } 35 + it "should return a 401 if unauthenticated" do
  36 + get api("/users/9998")
  37 + response.status.should == 401
  38 + end
38 39
39 - it "should not create invalid user" do  
40 - post api("/users", admin), { email: "invalid email" } 40 + it "should return a 404 error if user id not found" do
  41 + get api("/users/9999", user)
41 response.status.should == 404 42 response.status.should == 404
42 end 43 end
  44 + end
  45 +
  46 + describe "POST /users" do
  47 + before{ admin }
43 48
44 it "should create user" do 49 it "should create user" do
45 expect { 50 expect {
@@ -47,10 +52,48 @@ describe Gitlab::API do @@ -47,10 +52,48 @@ describe Gitlab::API do
47 }.to change { User.count }.by(1) 52 }.to change { User.count }.by(1)
48 end 53 end
49 54
  55 + it "should return 201 Created on success" do
  56 + post api("/users", admin), attributes_for(:user, projects_limit: 3)
  57 + response.status.should == 201
  58 + end
  59 +
  60 + it "should not create user with invalid email" do
  61 + post api("/users", admin), { email: "invalid email", password: 'password' }
  62 + response.status.should == 400
  63 + end
  64 +
  65 + it "should return 400 error if password not given" do
  66 + post api("/users", admin), { email: 'test@example.com' }
  67 + response.status.should == 400
  68 + end
  69 +
  70 + it "should return 400 error if email not given" do
  71 + post api("/users", admin), { password: 'pass1234' }
  72 + response.status.should == 400
  73 + end
  74 +
50 it "shouldn't available for non admin users" do 75 it "shouldn't available for non admin users" do
51 post api("/users", user), attributes_for(:user) 76 post api("/users", user), attributes_for(:user)
52 response.status.should == 403 77 response.status.should == 403
53 end 78 end
  79 +
  80 + context "with existing user" do
  81 + before { post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test' } }
  82 +
  83 + it "should not create user with same email" do
  84 + expect {
  85 + post api("/users", admin), { email: 'test@example.com', password: 'password' }
  86 + }.to change { User.count }.by(0)
  87 + end
  88 +
  89 + it "should return 409 conflict error if user with email exists" do
  90 + post api("/users", admin), { email: 'test@example.com', password: 'password' }
  91 + end
  92 +
  93 + it "should return 409 conflict error if same username exists" do
  94 + post api("/users", admin), { email: 'foo@example.com', password: 'pass', username: 'test' }
  95 + end
  96 + end
54 end 97 end
55 98
56 describe "GET /users/sign_up" do 99 describe "GET /users/sign_up" do
@@ -81,7 +124,7 @@ describe Gitlab::API do @@ -81,7 +124,7 @@ describe Gitlab::API do
81 describe "PUT /users/:id" do 124 describe "PUT /users/:id" do
82 before { admin } 125 before { admin }
83 126
84 - it "should update user" do 127 + it "should update user with new bio" do
85 put api("/users/#{user.id}", admin), {bio: 'new test bio'} 128 put api("/users/#{user.id}", admin), {bio: 'new test bio'}
86 response.status.should == 200 129 response.status.should == 200
87 json_response['bio'].should == 'new test bio' 130 json_response['bio'].should == 'new test bio'
@@ -103,6 +146,25 @@ describe Gitlab::API do @@ -103,6 +146,25 @@ describe Gitlab::API do
103 put api("/users/999999", admin), {bio: 'update should fail'} 146 put api("/users/999999", admin), {bio: 'update should fail'}
104 response.status.should == 404 147 response.status.should == 404
105 end 148 end
  149 +
  150 + context "with existing user" do
  151 + before {
  152 + post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' }
  153 + post api("/users", admin), { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' }
  154 + @user_id = User.all.last.id
  155 + }
  156 +
  157 +# it "should return 409 conflict error if email address exists" do
  158 +# put api("/users/#{@user_id}", admin), { email: 'test@example.com' }
  159 +# response.status.should == 409
  160 +# end
  161 +#
  162 +# it "should return 409 conflict error if username taken" do
  163 +# @user_id = User.all.last.id
  164 +# put api("/users/#{@user_id}", admin), { username: 'test' }
  165 +# response.status.should == 409
  166 +# end
  167 + end
106 end 168 end
107 169
108 describe "POST /users/:id/keys" do 170 describe "POST /users/:id/keys" do
@@ -131,6 +193,11 @@ describe Gitlab::API do @@ -131,6 +193,11 @@ describe Gitlab::API do
131 json_response['email'].should == user.email 193 json_response['email'].should == user.email
132 end 194 end
133 195
  196 + it "should not delete for unauthenticated user" do
  197 + delete api("/users/#{user.id}")
  198 + response.status.should == 401
  199 + end
  200 +
134 it "shouldn't available for non admin users" do 201 it "shouldn't available for non admin users" do
135 delete api("/users/#{user.id}", user) 202 delete api("/users/#{user.id}", user)
136 response.status.should == 403 203 response.status.should == 403
@@ -148,6 +215,11 @@ describe Gitlab::API do @@ -148,6 +215,11 @@ describe Gitlab::API do
148 response.status.should == 200 215 response.status.should == 200
149 json_response['email'].should == user.email 216 json_response['email'].should == user.email
150 end 217 end
  218 +
  219 + it "should return 401 error if user is unauthenticated" do
  220 + get api("/user")
  221 + response.status.should == 401
  222 + end
151 end 223 end
152 224
153 describe "GET /user/keys" do 225 describe "GET /user/keys" do
@@ -183,19 +255,38 @@ describe Gitlab::API do @@ -183,19 +255,38 @@ describe Gitlab::API do
183 get api("/user/keys/42", user) 255 get api("/user/keys/42", user)
184 response.status.should == 404 256 response.status.should == 404
185 end 257 end
186 - end  
187 258
188 - describe "POST /user/keys" do  
189 - it "should not create invalid ssh key" do  
190 - post api("/user/keys", user), { title: "invalid key" } 259 + it "should return 404 error if admin accesses user's ssh key" do
  260 + user.keys << key
  261 + user.save
  262 + admin
  263 + get api("/user/keys/#{key.id}", admin)
191 response.status.should == 404 264 response.status.should == 404
192 end 265 end
  266 + end
193 267
  268 + describe "POST /user/keys" do
194 it "should create ssh key" do 269 it "should create ssh key" do
195 key_attrs = attributes_for :key 270 key_attrs = attributes_for :key
196 expect { 271 expect {
197 post api("/user/keys", user), key_attrs 272 post api("/user/keys", user), key_attrs
198 }.to change{ user.keys.count }.by(1) 273 }.to change{ user.keys.count }.by(1)
  274 + response.status.should == 201
  275 + end
  276 +
  277 + it "should return a 401 error if unauthorized" do
  278 + post api("/user/keys"), title: 'some title', key: 'some key'
  279 + response.status.should == 401
  280 + end
  281 +
  282 + it "should not create ssh key without key" do
  283 + post api("/user/keys", user), title: 'title'
  284 + response.status.should == 400
  285 + end
  286 +
  287 + it "should not create ssh key without title" do
  288 + post api("/user/keys", user), key: "somekey"
  289 + response.status.should == 400
199 end 290 end
200 end 291 end
201 292
@@ -206,11 +297,19 @@ describe Gitlab::API do @@ -206,11 +297,19 @@ describe Gitlab::API do
206 expect { 297 expect {
207 delete api("/user/keys/#{key.id}", user) 298 delete api("/user/keys/#{key.id}", user)
208 }.to change{user.keys.count}.by(-1) 299 }.to change{user.keys.count}.by(-1)
  300 + response.status.should == 200
209 end 301 end
210 302
211 - it "should return 404 Not Found within invalid ID" do 303 + it "should return sucess if key ID not found" do
212 delete api("/user/keys/42", user) 304 delete api("/user/keys/42", user)
213 - response.status.should == 404 305 + response.status.should == 200
  306 + end
  307 +
  308 + it "should return 401 error if unauthorized" do
  309 + user.keys << key
  310 + user.save
  311 + delete api("/user/keys/#{key.id}")
  312 + response.status.should == 401
214 end 313 end
215 end 314 end
216 end 315 end