Commit 7a774d1a authored by Douwe Maan's avatar Douwe Maan

Merge branch '28251-mr-and-issue-iids-for-api-v4' into 'master'

API routes referencing a specific issue should use the issue `iid`

Closes #28251

See merge request !9530
parents bec5c370 6384bf7a
...@@ -14,8 +14,8 @@ requests, snippets, and notes/comments. Issues, merge requests, snippets, and no ...@@ -14,8 +14,8 @@ requests, snippets, and notes/comments. Issues, merge requests, snippets, and no
Gets a list of all award emoji Gets a list of all award emoji
``` ```
GET /projects/:id/issues/:issue_id/award_emoji GET /projects/:id/issues/:issue_iid/award_emoji
GET /projects/:id/merge_requests/:merge_request_id/award_emoji GET /projects/:id/merge_requests/:merge_request_iid/award_emoji
GET /projects/:id/snippets/:snippet_id/award_emoji GET /projects/:id/snippets/:snippet_id/award_emoji
``` ```
...@@ -24,7 +24,7 @@ Parameters: ...@@ -24,7 +24,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `awardable_id` | integer | yes | The ID of an awardable | | `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji
...@@ -74,8 +74,8 @@ Example Response: ...@@ -74,8 +74,8 @@ Example Response:
Gets a single award emoji from an issue, snippet, or merge request. Gets a single award emoji from an issue, snippet, or merge request.
``` ```
GET /projects/:id/issues/:issue_id/award_emoji/:award_id GET /projects/:id/issues/:issue_iid/award_emoji/:award_id
GET /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id GET /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
``` ```
...@@ -84,7 +84,7 @@ Parameters: ...@@ -84,7 +84,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `awardable_id` | integer | yes | The ID of an awardable | | `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
| `award_id` | integer | yes | The ID of the award emoji | | `award_id` | integer | yes | The ID of the award emoji |
```bash ```bash
...@@ -117,8 +117,8 @@ Example Response: ...@@ -117,8 +117,8 @@ Example Response:
This end point creates an award emoji on the specified resource This end point creates an award emoji on the specified resource
``` ```
POST /projects/:id/issues/:issue_id/award_emoji POST /projects/:id/issues/:issue_iid/award_emoji
POST /projects/:id/merge_requests/:merge_request_id/award_emoji POST /projects/:id/merge_requests/:merge_request_iid/award_emoji
POST /projects/:id/snippets/:snippet_id/award_emoji POST /projects/:id/snippets/:snippet_id/award_emoji
``` ```
...@@ -127,7 +127,7 @@ Parameters: ...@@ -127,7 +127,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `awardable_id` | integer | yes | The ID of an awardable | | `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
| `name` | string | yes | The name of the emoji, without colons | | `name` | string | yes | The name of the emoji, without colons |
```bash ```bash
...@@ -161,8 +161,8 @@ Sometimes its just not meant to be, and you'll have to remove your award. Only a ...@@ -161,8 +161,8 @@ Sometimes its just not meant to be, and you'll have to remove your award. Only a
admins or the author of the award. admins or the author of the award.
``` ```
DELETE /projects/:id/issues/:issue_id/award_emoji/:award_id DELETE /projects/:id/issues/:issue_iid/award_emoji/:award_id
DELETE /projects/:id/merge_requests/:merge_request_id/award_emoji/:award_id DELETE /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
``` ```
...@@ -171,7 +171,7 @@ Parameters: ...@@ -171,7 +171,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of an issue | | `issue_iid` | integer | yes | The internal ID of an issue |
| `award_id` | integer | yes | The ID of a award_emoji | | `award_id` | integer | yes | The ID of a award_emoji |
```bash ```bash
...@@ -188,7 +188,7 @@ easily adapted for notes on a Merge Request. ...@@ -188,7 +188,7 @@ easily adapted for notes on a Merge Request.
### List a note's award emoji ### List a note's award emoji
``` ```
GET /projects/:id/issues/:issue_id/notes/:note_id/award_emoji GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
``` ```
Parameters: Parameters:
...@@ -196,7 +196,7 @@ Parameters: ...@@ -196,7 +196,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of an issue | | `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of an note | | `note_id` | integer | yes | The ID of an note |
...@@ -230,7 +230,7 @@ Example Response: ...@@ -230,7 +230,7 @@ Example Response:
### Get single note's award emoji ### Get single note's award emoji
``` ```
GET /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
``` ```
Parameters: Parameters:
...@@ -238,7 +238,7 @@ Parameters: ...@@ -238,7 +238,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of an issue | | `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of a note | | `note_id` | integer | yes | The ID of a note |
| `award_id` | integer | yes | The ID of the award emoji | | `award_id` | integer | yes | The ID of the award emoji |
...@@ -270,7 +270,7 @@ Example Response: ...@@ -270,7 +270,7 @@ Example Response:
### Award a new emoji on a note ### Award a new emoji on a note
``` ```
POST /projects/:id/issues/:issue_id/notes/:note_id/award_emoji POST /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
``` ```
Parameters: Parameters:
...@@ -278,7 +278,7 @@ Parameters: ...@@ -278,7 +278,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of an issue | | `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of a note | | `note_id` | integer | yes | The ID of a note |
| `name` | string | yes | The name of the emoji, without colons | | `name` | string | yes | The name of the emoji, without colons |
...@@ -313,7 +313,7 @@ Sometimes its just not meant to be, and you'll have to remove your award. Only a ...@@ -313,7 +313,7 @@ Sometimes its just not meant to be, and you'll have to remove your award. Only a
admins or the author of the award. admins or the author of the award.
``` ```
DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id DELETE /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
``` ```
Parameters: Parameters:
...@@ -321,7 +321,7 @@ Parameters: ...@@ -321,7 +321,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of an issue | | `issue_iid` | integer | yes | The internal ID of an issue |
| `note_id` | integer | yes | The ID of a note | | `note_id` | integer | yes | The ID of a note |
| `award_id` | integer | yes | The ID of a award_emoji | | `award_id` | integer | yes | The ID of a award_emoji |
......
...@@ -261,13 +261,13 @@ Example response: ...@@ -261,13 +261,13 @@ Example response:
Get a single project issue. Get a single project issue.
``` ```
GET /projects/:id/issues/:issue_id GET /projects/:id/issues/:issue_iid
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id`| integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/41 curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/41
...@@ -385,13 +385,13 @@ Updates an existing project issue. This call is also used to mark an issue as ...@@ -385,13 +385,13 @@ Updates an existing project issue. This call is also used to mark an issue as
closed. closed.
``` ```
PUT /projects/:id/issues/:issue_id PUT /projects/:id/issues/:issue_iid
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
| `title` | string | no | The title of an issue | | `title` | string | no | The title of an issue |
| `description` | string | no | The description of an issue | | `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Updates an issue to be confidential | | `confidential` | boolean | no | Updates an issue to be confidential |
...@@ -444,13 +444,13 @@ Example response: ...@@ -444,13 +444,13 @@ Example response:
Only for admins and project owners. Soft deletes the issue in question. Only for admins and project owners. Soft deletes the issue in question.
``` ```
DELETE /projects/:id/issues/:issue_id DELETE /projects/:id/issues/:issue_iid
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85 curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85
...@@ -466,13 +466,13 @@ If a given label and/or milestone with the same name also exists in the target ...@@ -466,13 +466,13 @@ If a given label and/or milestone with the same name also exists in the target
project, it will then be assigned to the issue that is being moved. project, it will then be assigned to the issue that is being moved.
``` ```
POST /projects/:id/issues/:issue_id/move POST /projects/:id/issues/:issue_iid/move
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
| `to_project_id` | integer | yes | The ID of the new project | | `to_project_id` | integer | yes | The ID of the new project |
```bash ```bash
...@@ -522,13 +522,13 @@ If the user is already subscribed to the issue, the status code `304` ...@@ -522,13 +522,13 @@ If the user is already subscribed to the issue, the status code `304`
is returned. is returned.
``` ```
POST /projects/:id/issues/:issue_id/subscribe POST /projects/:id/issues/:issue_iid/subscribe
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/subscribe curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/subscribe
...@@ -577,13 +577,13 @@ from it. If the user is not subscribed to the issue, the ...@@ -577,13 +577,13 @@ from it. If the user is not subscribed to the issue, the
status code `304` is returned. status code `304` is returned.
``` ```
POST /projects/:id/issues/:issue_id/unsubscribe POST /projects/:id/issues/:issue_iid/unsubscribe
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe
...@@ -596,13 +596,13 @@ there already exists a todo for the user on that issue, status code `304` is ...@@ -596,13 +596,13 @@ there already exists a todo for the user on that issue, status code `304` is
returned. returned.
``` ```
POST /projects/:id/issues/:issue_id/todo POST /projects/:id/issues/:issue_iid/todo
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/todo curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/todo
...@@ -687,13 +687,13 @@ Example response: ...@@ -687,13 +687,13 @@ Example response:
Sets an estimated time of work for this issue. Sets an estimated time of work for this issue.
``` ```
POST /projects/:id/issues/:issue_id/time_estimate POST /projects/:id/issues/:issue_iid/time_estimate
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
| `duration` | string | yes | The duration in human format. e.g: 3h30m | | `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash ```bash
...@@ -716,13 +716,13 @@ Example response: ...@@ -716,13 +716,13 @@ Example response:
Resets the estimated time for this issue to 0 seconds. Resets the estimated time for this issue to 0 seconds.
``` ```
POST /projects/:id/issues/:issue_id/reset_time_estimate POST /projects/:id/issues/:issue_iid/reset_time_estimate
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_time_estimate curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_time_estimate
...@@ -744,13 +744,13 @@ Example response: ...@@ -744,13 +744,13 @@ Example response:
Adds spent time for this issue Adds spent time for this issue
``` ```
POST /projects/:id/issues/:issue_id/add_spent_time POST /projects/:id/issues/:issue_iid/add_spent_time
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
| `duration` | string | yes | The duration in human format. e.g: 3h30m | | `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash ```bash
...@@ -773,13 +773,13 @@ Example response: ...@@ -773,13 +773,13 @@ Example response:
Resets the total spent time for this issue to 0 seconds. Resets the total spent time for this issue to 0 seconds.
``` ```
POST /projects/:id/issues/:issue_id/reset_spent_time POST /projects/:id/issues/:issue_iid/reset_spent_time
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_spent_time curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_spent_time
...@@ -799,13 +799,13 @@ Example response: ...@@ -799,13 +799,13 @@ Example response:
## Get time tracking stats ## Get time tracking stats
``` ```
GET /projects/:id/issues/:issue_id/time_stats GET /projects/:id/issues/:issue_iid/time_stats
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash ```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_stats curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_stats
......
...@@ -82,13 +82,13 @@ Parameters: ...@@ -82,13 +82,13 @@ Parameters:
Shows information about a single merge request. Shows information about a single merge request.
``` ```
GET /projects/:id/merge_requests/:merge_request_id GET /projects/:id/merge_requests/:merge_request_iid
``` ```
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of MR - `merge_request_iid` (required) - The internal ID of the merge request
```json ```json
{ {
...@@ -150,13 +150,13 @@ Parameters: ...@@ -150,13 +150,13 @@ Parameters:
Get a list of merge request commits. Get a list of merge request commits.
``` ```
GET /projects/:id/merge_requests/:merge_request_id/commits GET /projects/:id/merge_requests/:merge_request_iid/commits
``` ```
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of MR - `merge_request_iid` (required) - The internal ID of the merge request
```json ```json
...@@ -187,13 +187,13 @@ Parameters: ...@@ -187,13 +187,13 @@ Parameters:
Shows information about the merge request including its files and changes. Shows information about the merge request including its files and changes.
``` ```
GET /projects/:id/merge_requests/:merge_request_id/changes GET /projects/:id/merge_requests/:merge_request_iid/changes
``` ```
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of MR - `merge_request_iid` (required) - The internal ID of the merge request
```json ```json
{ {
...@@ -342,13 +342,13 @@ POST /projects/:id/merge_requests ...@@ -342,13 +342,13 @@ POST /projects/:id/merge_requests
Updates an existing merge request. You can change the target branch, title, or even close the MR. Updates an existing merge request. You can change the target branch, title, or even close the MR.
``` ```
PUT /projects/:id/merge_requests/:merge_request_id PUT /projects/:id/merge_requests/:merge_request_iid
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | string | yes | The ID of a project | | `id` | string | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a merge request | | `merge_request_iid` | integer | yes | The ID of a merge request |
| `target_branch` | string | no | The target branch | | `target_branch` | string | no | The target branch |
| `title` | string | no | Title of MR | | `title` | string | no | Title of MR |
| `assignee_id` | integer | no | Assignee user ID | | `assignee_id` | integer | no | Assignee user ID |
...@@ -419,13 +419,13 @@ Must include at least one non-required attribute from above. ...@@ -419,13 +419,13 @@ Must include at least one non-required attribute from above.
Only for admins and project owners. Soft deletes the merge request in question. Only for admins and project owners. Soft deletes the merge request in question.
``` ```
DELETE /projects/:id/merge_requests/:merge_request_id DELETE /projects/:id/merge_requests/:merge_request_iid
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a project's merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash ```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/merge_requests/85 curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/merge_requests/85
...@@ -445,13 +445,13 @@ If the `sha` parameter is passed and does not match the HEAD of the source - you ...@@ -445,13 +445,13 @@ If the `sha` parameter is passed and does not match the HEAD of the source - you
If you don't have permissions to accept this merge request - you'll get a `401` If you don't have permissions to accept this merge request - you'll get a `401`
``` ```
PUT /projects/:id/merge_requests/:merge_request_id/merge PUT /projects/:id/merge_requests/:merge_request_iid/merge
``` ```
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `merge_request_id` (required) - ID of MR - `merge_request_iid` (required) - Internal ID of MR
- `merge_commit_message` (optional) - Custom merge commit message - `merge_commit_message` (optional) - Custom merge commit message
- `should_remove_source_branch` (optional) - if `true` removes the source branch - `should_remove_source_branch` (optional) - if `true` removes the source branch
- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds - `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds
...@@ -520,12 +520,12 @@ If the merge request is already merged or closed - you get `405` and error messa ...@@ -520,12 +520,12 @@ If the merge request is already merged or closed - you get `405` and error messa
In case the merge request is not set to be merged when the pipeline succeeds, you'll also get a `406` error. In case the merge request is not set to be merged when the pipeline succeeds, you'll also get a `406` error.
``` ```
PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds
``` ```
Parameters: Parameters:
- `id` (required) - The ID of a project - `id` (required) - The ID of a project
- `merge_request_id` (required) - ID of MR - `merge_request_iid` (required) - Internal ID of MR
```json ```json
{ {
...@@ -591,13 +591,13 @@ Comments are done via the [notes](notes.md) resource. ...@@ -591,13 +591,13 @@ Comments are done via the [notes](notes.md) resource.
Get all the issues that would be closed by merging the provided merge request. Get all the issues that would be closed by merging the provided merge request.
``` ```
GET /projects/:id/merge_requests/:merge_request_id/closes_issues GET /projects/:id/merge_requests/:merge_request_iid/closes_issues
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of the merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/closes_issues curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/closes_issues
...@@ -666,13 +666,13 @@ Subscribes the authenticated user to a merge request to receive notification. If ...@@ -666,13 +666,13 @@ Subscribes the authenticated user to a merge request to receive notification. If
status code `304` is returned. status code `304` is returned.
``` ```
POST /projects/:id/merge_requests/:merge_request_id/subscribe POST /projects/:id/merge_requests/:merge_request_iid/subscribe
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of the merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/subscribe curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/subscribe
...@@ -740,13 +740,13 @@ notifications from that merge request. If the user is ...@@ -740,13 +740,13 @@ notifications from that merge request. If the user is
not subscribed to the merge request, the status code `304` is returned. not subscribed to the merge request, the status code `304` is returned.
``` ```
POST /projects/:id/merge_requests/:merge_request_id/unsubscribe POST /projects/:id/merge_requests/:merge_request_iid/unsubscribe
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of the merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/unsubscribe curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/unsubscribe
...@@ -814,13 +814,13 @@ If there already exists a todo for the user on that merge request, ...@@ -814,13 +814,13 @@ If there already exists a todo for the user on that merge request,
status code `304` is returned. status code `304` is returned.
``` ```
POST /projects/:id/merge_requests/:merge_request_id/todo POST /projects/:id/merge_requests/:merge_request_iid/todo
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of the merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/27/todo curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/27/todo
...@@ -914,13 +914,13 @@ Example response: ...@@ -914,13 +914,13 @@ Example response:
Get a list of merge request diff versions. Get a list of merge request diff versions.
``` ```
GET /projects/:id/merge_requests/:merge_request_id/versions GET /projects/:id/merge_requests/:merge_request_iid/versions
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- | | --------- | ------- | -------- | --------------------- |
| `id` | String | yes | The ID of the project | | `id` | String | yes | The ID of the project |
| `merge_request_id` | integer | yes | The ID of the merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions
...@@ -955,13 +955,13 @@ Example response: ...@@ -955,13 +955,13 @@ Example response:
Get a single merge request diff version. Get a single merge request diff version.
``` ```
GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- | | --------- | ------- | -------- | --------------------- |
| `id` | String | yes | The ID of the project | | `id` | String | yes | The ID of the project |
| `merge_request_id` | integer | yes | The ID of the merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
| `version_id` | integer | yes | The ID of the merge request diff version | | `version_id` | integer | yes | The ID of the merge request diff version |
```bash ```bash
...@@ -1022,13 +1022,13 @@ Example response: ...@@ -1022,13 +1022,13 @@ Example response:
Sets an estimated time of work for this merge request. Sets an estimated time of work for this merge request.
``` ```
POST /projects/:id/merge_requests/:merge_request_id/time_estimate POST /projects/:id/merge_requests/:merge_request_iid/time_estimate
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a project's merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
| `duration` | string | yes | The duration in human format. e.g: 3h30m | | `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash ```bash
...@@ -1051,13 +1051,13 @@ Example response: ...@@ -1051,13 +1051,13 @@ Example response:
Resets the estimated time for this merge request to 0 seconds. Resets the estimated time for this merge request to 0 seconds.
``` ```
POST /projects/:id/merge_requests/:merge_request_id/reset_time_estimate POST /projects/:id/merge_requests/:merge_request_iid/reset_time_estimate
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a project's merge_request | | `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_time_estimate curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_time_estimate
...@@ -1079,13 +1079,13 @@ Example response: ...@@ -1079,13 +1079,13 @@ Example response:
Adds spent time for this merge request Adds spent time for this merge request
``` ```
POST /projects/:id/merge_requests/:merge_request_id/add_spent_time POST /projects/:id/merge_requests/:merge_request_iid/add_spent_time
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a project's merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
| `duration` | string | yes | The duration in human format. e.g: 3h30m | | `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash ```bash
...@@ -1108,13 +1108,13 @@ Example response: ...@@ -1108,13 +1108,13 @@ Example response:
Resets the total spent time for this merge request to 0 seconds. Resets the total spent time for this merge request to 0 seconds.
``` ```
POST /projects/:id/merge_requests/:merge_request_id/reset_spent_time POST /projects/:id/merge_requests/:merge_request_iid/reset_spent_time
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a project's merge_request | | `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_spent_time curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_spent_time
...@@ -1134,13 +1134,13 @@ Example response: ...@@ -1134,13 +1134,13 @@ Example response:
## Get time tracking stats ## Get time tracking stats
``` ```
GET /projects/:id/merge_requests/:merge_request_id/time_stats GET /projects/:id/merge_requests/:merge_request_iid/time_stats
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of a project's merge request | | `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash ```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_stats curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_stats
......
...@@ -69,4 +69,5 @@ changes are in V4: ...@@ -69,4 +69,5 @@ changes are in V4:
- `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline` - `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline`
- Require description when creating a new trigger `POST /projects/:id/triggers` - Require description when creating a new trigger `POST /projects/:id/triggers`
- Simplify project payload exposed on Environment endpoints [!9675](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9675) - Simplify project payload exposed on Environment endpoints [!9675](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9675)
- API uses merge request `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the merge requests, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530)
- API uses issue `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the issues, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9530)
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
version %w(v3 v4), using: :path version %w(v3 v4), using: :path
version 'v3', using: :path do version 'v3', using: :path do
helpers ::API::V3::Helpers
mount ::API::V3::AwardEmoji mount ::API::V3::AwardEmoji
mount ::API::V3::Boards mount ::API::V3::Boards
mount ::API::V3::Branches mount ::API::V3::Branches
......
...@@ -3,12 +3,16 @@ module API ...@@ -3,12 +3,16 @@ module API
include PaginationParams include PaginationParams
before { authenticate! } before { authenticate! }
AWARDABLES = %w[issue merge_request snippet].freeze AWARDABLES = [
{ type: 'issue', find_by: :iid },
{ type: 'merge_request', find_by: :iid },
{ type: 'snippet', find_by: :id }
].freeze
resource :projects do resource :projects do
AWARDABLES.each do |awardable_type| AWARDABLES.each do |awardable_params|
awardable_string = awardable_type.pluralize awardable_string = awardable_params[:type].pluralize
awardable_id_string = "#{awardable_type}_id" awardable_id_string = "#{awardable_params[:type]}_#{awardable_params[:find_by]}"
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
...@@ -104,10 +108,10 @@ module API ...@@ -104,10 +108,10 @@ module API
note_id = params.delete(:note_id) note_id = params.delete(:note_id)
awardable.notes.find(note_id) awardable.notes.find(note_id)
elsif params.include?(:issue_id) elsif params.include?(:issue_iid)
user_project.issues.find(params[:issue_id]) user_project.issues.find_by!(iid: params[:issue_iid])
elsif params.include?(:merge_request_id) elsif params.include?(:merge_request_iid)
user_project.merge_requests.find(params[:merge_request_id]) user_project.merge_requests.find_by!(iid: params[:merge_request_iid])
else else
user_project.snippets.find(params[:snippet_id]) user_project.snippets.find(params[:snippet_id])
end end
......
...@@ -82,16 +82,16 @@ module API ...@@ -82,16 +82,16 @@ module API
label || not_found!('Label') label || not_found!('Label')
end end
def find_project_issue(id) def find_project_issue(iid)
IssuesFinder.new(current_user, project_id: user_project.id).find(id) IssuesFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid)
end end
def find_project_merge_request(id) def find_project_merge_request(iid)
MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id) MergeRequestsFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid)
end end
def find_merge_request_with_access(id, access_level = :read_merge_request) def find_merge_request_with_access(iid, access_level = :read_merge_request)
merge_request = user_project.merge_requests.find(id) merge_request = user_project.merge_requests.find_by!(iid: iid)
authorize! access_level, merge_request authorize! access_level, merge_request
merge_request merge_request
end end
......
...@@ -102,10 +102,10 @@ module API ...@@ -102,10 +102,10 @@ module API
success Entities::Issue success Entities::Issue
end end
params do params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue' requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end end
get ":id/issues/:issue_id" do get ":id/issues/:issue_iid" do
issue = find_project_issue(params[:issue_id]) issue = find_project_issue(params[:issue_iid])
present issue, with: Entities::Issue, current_user: current_user, project: user_project present issue, with: Entities::Issue, current_user: current_user, project: user_project
end end
...@@ -152,7 +152,7 @@ module API ...@@ -152,7 +152,7 @@ module API
success Entities::Issue success Entities::Issue
end end
params do params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue' requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
optional :title, type: String, desc: 'The title of an issue' optional :title, type: String, desc: 'The title of an issue'
optional :updated_at, type: DateTime, optional :updated_at, type: DateTime,
desc: 'Date time when the issue was updated. Available only for admins and project owners.' desc: 'Date time when the issue was updated. Available only for admins and project owners.'
...@@ -161,8 +161,8 @@ module API ...@@ -161,8 +161,8 @@ module API
at_least_one_of :title, :description, :assignee_id, :milestone_id, at_least_one_of :title, :description, :assignee_id, :milestone_id,
:labels, :created_at, :due_date, :confidential, :state_event :labels, :created_at, :due_date, :confidential, :state_event
end end
put ':id/issues/:issue_id' do put ':id/issues/:issue_iid' do
issue = user_project.issues.find(params.delete(:issue_id)) issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue authorize! :update_issue, issue
# Setting created_at time only allowed for admins and project owners # Setting created_at time only allowed for admins and project owners
...@@ -189,11 +189,11 @@ module API ...@@ -189,11 +189,11 @@ module API
success Entities::Issue success Entities::Issue
end end
params do params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue' requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
requires :to_project_id, type: Integer, desc: 'The ID of the new project' requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end end
post ':id/issues/:issue_id/move' do post ':id/issues/:issue_iid/move' do
issue = user_project.issues.find_by(id: params[:issue_id]) issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue not_found!('Issue') unless issue
new_project = Project.find_by(id: params[:to_project_id]) new_project = Project.find_by(id: params[:to_project_id])
...@@ -209,10 +209,10 @@ module API ...@@ -209,10 +209,10 @@ module API
desc 'Delete a project issue' desc 'Delete a project issue'
params do params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue' requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end end
delete ":id/issues/:issue_id" do delete ":id/issues/:issue_iid" do
issue = user_project.issues.find_by(id: params[:issue_id]) issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue not_found!('Issue') unless issue
authorize!(:destroy_issue, issue) authorize!(:destroy_issue, issue)
......
...@@ -13,11 +13,11 @@ module API ...@@ -13,11 +13,11 @@ module API
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
use :pagination use :pagination
end end
get ":id/merge_requests/:merge_request_id/versions" do get ":id/merge_requests/:merge_request_iid/versions" do
merge_request = find_merge_request_with_access(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
end end
...@@ -29,12 +29,12 @@ module API ...@@ -29,12 +29,12 @@ module API
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version' requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end end
get ":id/merge_requests/:merge_request_id/versions/:version_id" do get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
merge_request = find_merge_request_with_access(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
end end
......
...@@ -101,23 +101,23 @@ module API ...@@ -101,23 +101,23 @@ module API
desc 'Delete a merge request' desc 'Delete a merge request'
params do params do
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end end
delete ":id/merge_requests/:merge_request_id" do delete ":id/merge_requests/:merge_request_iid" do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request) authorize!(:destroy_merge_request, merge_request)
merge_request.destroy merge_request.destroy
end end
params do params do
requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end end
desc 'Get a single merge request' do desc 'Get a single merge request' do
success Entities::MergeRequest success Entities::MergeRequest
end end
get ':id/merge_requests/:merge_request_id' do get ':id/merge_requests/:merge_request_iid' do
merge_request = find_merge_request_with_access(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end end
...@@ -125,8 +125,8 @@ module API ...@@ -125,8 +125,8 @@ module API
desc 'Get the commits of a merge request' do desc 'Get the commits of a merge request' do
success Entities::RepoCommit success Entities::RepoCommit
end end
get ':id/merge_requests/:merge_request_id/commits' do get ':id/merge_requests/:merge_request_iid/commits' do
merge_request = find_merge_request_with_access(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
commits = ::Kaminari.paginate_array(merge_request.commits) commits = ::Kaminari.paginate_array(merge_request.commits)
present paginate(commits), with: Entities::RepoCommit present paginate(commits), with: Entities::RepoCommit
...@@ -135,8 +135,8 @@ module API ...@@ -135,8 +135,8 @@ module API
desc 'Show the merge request changes' do desc 'Show the merge request changes' do
success Entities::MergeRequestChanges success Entities::MergeRequestChanges
end end
get ':id/merge_requests/:merge_request_id/changes' do get ':id/merge_requests/:merge_request_iid/changes' do
merge_request = find_merge_request_with_access(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequestChanges, current_user: current_user present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end end
...@@ -154,8 +154,8 @@ module API ...@@ -154,8 +154,8 @@ module API
:milestone_id, :labels, :state_event, :milestone_id, :labels, :state_event,
:remove_source_branch :remove_source_branch
end end
put ':id/merge_requests/:merge_request_id' do put ':id/merge_requests/:merge_request_iid' do
merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request) merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
...@@ -180,8 +180,8 @@ module API ...@@ -180,8 +180,8 @@ module API
desc: 'When true, this merge request will be merged when the pipeline succeeds' desc: 'When true, this merge request will be merged when the pipeline succeeds'
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end end
put ':id/merge_requests/:merge_request_id/merge' do put ':id/merge_requests/:merge_request_iid/merge' do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_project_merge_request(params[:merge_request_iid])
# Merge request can not be merged # Merge request can not be merged
# because user dont have permissions to push into target branch # because user dont have permissions to push into target branch
...@@ -216,8 +216,8 @@ module API ...@@ -216,8 +216,8 @@ module API
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
success Entities::MergeRequest success Entities::MergeRequest
end end
post ':id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds' do post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
...@@ -232,8 +232,8 @@ module API ...@@ -232,8 +232,8 @@ module API
params do params do
use :pagination use :pagination
end end
get ':id/merge_requests/:merge_request_id/comments' do get ':id/merge_requests/:merge_request_iid/comments' do
merge_request = find_merge_request_with_access(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.notes.fresh), with: Entities::MRNote present paginate(merge_request.notes.fresh), with: Entities::MRNote
end end
...@@ -243,8 +243,8 @@ module API ...@@ -243,8 +243,8 @@ module API
params do params do
requires :note, type: String, desc: 'The text of the comment' requires :note, type: String, desc: 'The text of the comment'
end end
post ':id/merge_requests/:merge_request_id/comments' do post ':id/merge_requests/:merge_request_iid/comments' do
merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note) merge_request = find_merge_request_with_access(params[:merge_request_iid], :create_note)
opts = { opts = {
note: params[:note], note: params[:note],
...@@ -267,8 +267,8 @@ module API ...@@ -267,8 +267,8 @@ module API
params do params do
use :pagination use :pagination
end end
get ':id/merge_requests/:merge_request_id/closes_issues' do get ':id/merge_requests/:merge_request_iid/closes_issues' do
merge_request = find_merge_request_with_access(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: issue_entity(user_project), current_user: current_user present paginate(issues), with: issue_entity(user_project), current_user: current_user
end end
......
...@@ -5,11 +5,11 @@ module API ...@@ -5,11 +5,11 @@ module API
included do included do
helpers do helpers do
def issuable_name def issuable_name
declared_params.has_key?(:issue_id) ? 'issue' : 'merge_request' declared_params.has_key?(:issue_iid) ? 'issue' : 'merge_request'
end end
def issuable_key def issuable_key
"#{issuable_name}_id".to_sym "#{issuable_name}_iid".to_sym
end end
def update_issuable_key def update_issuable_key
...@@ -50,7 +50,7 @@ module API ...@@ -50,7 +50,7 @@ module API
issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request' issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request'
issuable_collection_name = issuable_name.pluralize issuable_collection_name = issuable_name.pluralize
issuable_key = "#{issuable_name}_id".to_sym issuable_key = "#{issuable_name}_iid".to_sym
desc "Set a time estimate for a project #{issuable_name}" desc "Set a time estimate for a project #{issuable_name}"
params do params do
......
...@@ -5,8 +5,8 @@ module API ...@@ -5,8 +5,8 @@ module API
before { authenticate! } before { authenticate! }
ISSUABLE_TYPES = { ISSUABLE_TYPES = {
'merge_requests' => ->(id) { find_merge_request_with_access(id) }, 'merge_requests' => ->(iid) { find_merge_request_with_access(iid) },
'issues' => ->(id) { find_project_issue(id) } 'issues' => ->(iid) { find_project_issue(iid) }
}.freeze }.freeze
params do params do
...@@ -14,13 +14,13 @@ module API ...@@ -14,13 +14,13 @@ module API
end end
resource :projects do resource :projects do
ISSUABLE_TYPES.each do |type, finder| ISSUABLE_TYPES.each do |type, finder|
type_id_str = "#{type.singularize}_id".to_sym type_id_str = "#{type.singularize}_iid".to_sym
desc 'Create a todo on an issuable' do desc 'Create a todo on an issuable' do
success Entities::Todo success Entities::Todo
end end
params do params do
requires type_id_str, type: Integer, desc: 'The ID of an issuable' requires type_id_str, type: Integer, desc: 'The IID of an issuable'
end end
post ":id/#{type}/:#{type_id_str}/todo" do post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder) issuable = instance_exec(params[type_id_str], &finder)
......
...@@ -16,11 +16,64 @@ module API ...@@ -16,11 +16,64 @@ module API
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet" requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end end
[":id/#{awardable_string}/:#{awardable_id_string}/award_emoji", [
":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"].each do |endpoint| ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
].each do |endpoint|
desc 'Get a list of project +awardable+ award emoji' do
detail 'This feature was introduced in 8.9'
success Entities::AwardEmoji
end
params do
use :pagination
end
get endpoint do
if can_read_awardable?
awards = awardable.award_emoji
present paginate(awards), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
end
desc 'Get a specific award emoji' do
detail 'This feature was introduced in 8.9'
success Entities::AwardEmoji
end
params do
requires :award_id, type: Integer, desc: 'The ID of the award'
end
get "#{endpoint}/:award_id" do
if can_read_awardable?
present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji
else
not_found!("Award Emoji")
end
end
desc 'Award a new Emoji' do
detail 'This feature was introduced in 8.9'
success Entities::AwardEmoji
end
params do
requires :name, type: String, desc: 'The name of a award_emoji (without colons)'
end
post endpoint do
not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
award = awardable.create_award_emoji(params[:name], current_user)
if award.persisted?
present award, with: Entities::AwardEmoji
else
not_found!("Award Emoji #{award.errors.messages}")
end
end
desc 'Delete a +awardables+ award emoji' do desc 'Delete a +awardables+ award emoji' do
detail 'This feature was introduced in 8.9' detail 'This feature was introduced in 8.9'
success ::API::Entities::AwardEmoji success Entities::AwardEmoji
end end
params do params do
requires :award_id, type: Integer, desc: 'The ID of an award emoji' requires :award_id, type: Integer, desc: 'The ID of an award emoji'
...@@ -30,13 +83,22 @@ module API ...@@ -30,13 +83,22 @@ module API
unauthorized! unless award.user == current_user || current_user.admin? unauthorized! unless award.user == current_user || current_user.admin?
present award.destroy, with: ::API::Entities::AwardEmoji award.destroy
present award, with: Entities::AwardEmoji
end end
end end
end end
end end
helpers do helpers do
def can_read_awardable?
can?(current_user, read_ability(awardable), awardable)
end
def can_award_awardable?
awardable.user_can_award?(current_user, params[:name])
end
def awardable def awardable
@awardable ||= @awardable ||=
begin begin
...@@ -53,6 +115,15 @@ module API ...@@ -53,6 +115,15 @@ module API
end end
end end
end end
def read_ability(awardable)
case awardable
when Note
read_ability(awardable.noteable)
else
:"read_#{awardable.class.to_s.underscore}"
end
end
end end
end end
end end
......
module API
module V3
module Helpers
def find_project_issue(id)
IssuesFinder.new(current_user, project_id: user_project.id).find(id)
end
def find_project_merge_request(id)
MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
end
def find_merge_request_with_access(id, access_level = :read_merge_request)
merge_request = user_project.merge_requests.find(id)
authorize! access_level, merge_request
merge_request
end
end
end
end
module API
module V3
module TimeTrackingEndpoints
extend ActiveSupport::Concern
included do
helpers do
def issuable_name
declared_params.has_key?(:issue_id) ? 'issue' : 'merge_request'
end
def issuable_key
"#{issuable_name}_id".to_sym
end
def update_issuable_key
"update_#{issuable_name}".to_sym
end
def read_issuable_key
"read_#{issuable_name}".to_sym
end
def load_issuable
@issuable ||= begin
case issuable_name
when 'issue'
find_project_issue(params.delete(issuable_key))
when 'merge_request'
find_project_merge_request(params.delete(issuable_key))
end
end
end
def update_issuable(attrs)
custom_params = declared_params(include_missing: false)
custom_params.merge!(attrs)
issuable = update_service.new(user_project, current_user, custom_params).execute(load_issuable)
if issuable.valid?
present issuable, with: ::API::Entities::IssuableTimeStats
else
render_validation_error!(issuable)
end
end
def update_service
issuable_name == 'issue' ? ::Issues::UpdateService : ::MergeRequests::UpdateService
end
end
issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request'
issuable_collection_name = issuable_name.pluralize
issuable_key = "#{issuable_name}_id".to_sym
desc "Set a time estimate for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
requires :duration, type: String, desc: 'The duration to be parsed'
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/time_estimate" do
authorize! update_issuable_key, load_issuable
status :ok
update_issuable(time_estimate: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)))
end
desc "Reset the time estimate for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_time_estimate" do
authorize! update_issuable_key, load_issuable
status :ok
update_issuable(time_estimate: 0)
end
desc "Add spent time for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
requires :duration, type: String, desc: 'The duration to be parsed'
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/add_spent_time" do
authorize! update_issuable_key, load_issuable
update_issuable(spend_time: {
duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)),
user: current_user
})
end
desc "Reset spent time for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
end
post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_spent_time" do
authorize! update_issuable_key, load_issuable
status :ok
update_issuable(spend_time: { duration: :reset, user: current_user })
end
desc "Show time stats for a project #{issuable_name}"
params do
requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
end
get ":id/#{issuable_collection_name}/:#{issuable_key}/time_stats" do
authorize! read_issuable_key, load_issuable
present load_issuable, with: ::API::Entities::IssuableTimeStats
end
end
end
end
end
...@@ -15,7 +15,7 @@ describe API::AwardEmoji, api: true do ...@@ -15,7 +15,7 @@ describe API::AwardEmoji, api: true do
describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do
context 'on an issue' do context 'on an issue' do
it "returns an array of award_emoji" do it "returns an array of award_emoji" do
get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
...@@ -31,7 +31,7 @@ describe API::AwardEmoji, api: true do ...@@ -31,7 +31,7 @@ describe API::AwardEmoji, api: true do
context 'on a merge request' do context 'on a merge request' do
it "returns an array of award_emoji" do it "returns an array of award_emoji" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -57,7 +57,7 @@ describe API::AwardEmoji, api: true do ...@@ -57,7 +57,7 @@ describe API::AwardEmoji, api: true do
it 'returns a status code 404' do it 'returns a status code 404' do
user1 = create(:user) user1 = create(:user)
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji", user1)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -68,7 +68,7 @@ describe API::AwardEmoji, api: true do ...@@ -68,7 +68,7 @@ describe API::AwardEmoji, api: true do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') } let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an array of award emoji' do it 'returns an array of award emoji' do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user) get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
...@@ -79,7 +79,7 @@ describe API::AwardEmoji, api: true do ...@@ -79,7 +79,7 @@ describe API::AwardEmoji, api: true do
describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do
context 'on an issue' do context 'on an issue' do
it "returns the award emoji" do it "returns the award emoji" do
get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/#{award_emoji.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(award_emoji.name) expect(json_response['name']).to eq(award_emoji.name)
...@@ -88,7 +88,7 @@ describe API::AwardEmoji, api: true do ...@@ -88,7 +88,7 @@ describe API::AwardEmoji, api: true do
end end
it "returns a 404 error if the award is not found" do it "returns a 404 error if the award is not found" do
get api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/12345", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -96,7 +96,7 @@ describe API::AwardEmoji, api: true do ...@@ -96,7 +96,7 @@ describe API::AwardEmoji, api: true do
context 'on a merge request' do context 'on a merge request' do
it 'returns the award emoji' do it 'returns the award emoji' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(downvote.name) expect(json_response['name']).to eq(downvote.name)
...@@ -123,7 +123,7 @@ describe API::AwardEmoji, api: true do ...@@ -123,7 +123,7 @@ describe API::AwardEmoji, api: true do
it 'returns a status code 404' do it 'returns a status code 404' do
user1 = create(:user) user1 = create(:user)
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user1)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -134,7 +134,7 @@ describe API::AwardEmoji, api: true do ...@@ -134,7 +134,7 @@ describe API::AwardEmoji, api: true do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') } let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an award emoji' do it 'returns an award emoji' do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).not_to be_an Array expect(json_response).not_to be_an Array
...@@ -147,7 +147,7 @@ describe API::AwardEmoji, api: true do ...@@ -147,7 +147,7 @@ describe API::AwardEmoji, api: true do
context "on an issue" do context "on an issue" do
it "creates a new award emoji" do it "creates a new award emoji" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish' post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'blowfish'
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['name']).to eq('blowfish') expect(json_response['name']).to eq('blowfish')
...@@ -155,13 +155,13 @@ describe API::AwardEmoji, api: true do ...@@ -155,13 +155,13 @@ describe API::AwardEmoji, api: true do
end end
it "returns a 400 bad request error if the name is not given" do it "returns a 400 bad request error if the name is not given" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user)
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
it "returns a 401 unauthorized error if the user is not authenticated" do it "returns a 401 unauthorized error if the user is not authenticated" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup' post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji"), name: 'thumbsup'
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
...@@ -173,15 +173,15 @@ describe API::AwardEmoji, api: true do ...@@ -173,15 +173,15 @@ describe API::AwardEmoji, api: true do
end end
it "normalizes +1 as thumbsup award" do it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1' post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: '+1'
expect(issue.award_emoji.last.name).to eq("thumbsup") expect(issue.award_emoji.last.name).to eq("thumbsup")
end end
context 'when the emoji already has been awarded' do context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do it 'returns a 404 status code' do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup'
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken") expect(json_response["message"]).to match("has already been taken")
...@@ -207,7 +207,7 @@ describe API::AwardEmoji, api: true do ...@@ -207,7 +207,7 @@ describe API::AwardEmoji, api: true do
it 'creates a new award emoji' do it 'creates a new award emoji' do
expect do expect do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
end.to change { note.award_emoji.count }.from(0).to(1) end.to change { note.award_emoji.count }.from(0).to(1)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
...@@ -215,21 +215,21 @@ describe API::AwardEmoji, api: true do ...@@ -215,21 +215,21 @@ describe API::AwardEmoji, api: true do
end end
it "it returns 404 error when user authored note" do it "it returns 404 error when user authored note" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup' post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "normalizes +1 as thumbsup award" do it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1' post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: '+1'
expect(note.award_emoji.last.name).to eq("thumbsup") expect(note.award_emoji.last.name).to eq("thumbsup")
end end
context 'when the emoji already has been awarded' do context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do it 'returns a 404 status code' do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken") expect(json_response["message"]).to match("has already been taken")
...@@ -241,14 +241,14 @@ describe API::AwardEmoji, api: true do ...@@ -241,14 +241,14 @@ describe API::AwardEmoji, api: true do
context 'when the awardable is an Issue' do context 'when the awardable is an Issue' do
it 'deletes the award' do it 'deletes the award' do
expect do expect do
delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) delete api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/#{award_emoji.id}", user)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { issue.award_emoji.count }.from(1).to(0) end.to change { issue.award_emoji.count }.from(1).to(0)
end end
it 'returns a 404 error when the award emoji can not be found' do it 'returns a 404 error when the award emoji can not be found' do
delete api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) delete api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/12345", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -257,14 +257,14 @@ describe API::AwardEmoji, api: true do ...@@ -257,14 +257,14 @@ describe API::AwardEmoji, api: true do
context 'when the awardable is a Merge Request' do context 'when the awardable is a Merge Request' do
it 'deletes the award' do it 'deletes the award' do
expect do expect do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { merge_request.award_emoji.count }.from(1).to(0) end.to change { merge_request.award_emoji.count }.from(1).to(0)
end end
it 'returns a 404 error when note id not found' do it 'returns a 404 error when note id not found' do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes/12345", user) delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes/12345", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -289,7 +289,7 @@ describe API::AwardEmoji, api: true do ...@@ -289,7 +289,7 @@ describe API::AwardEmoji, api: true do
it 'deletes the award' do it 'deletes the award' do
expect do expect do
delete api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) delete api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end.to change { note.award_emoji.count }.from(1).to(0) end.to change { note.award_emoji.count }.from(1).to(0)
......
...@@ -757,9 +757,9 @@ describe API::Issues, api: true do ...@@ -757,9 +757,9 @@ describe API::Issues, api: true do
end end
end end
describe "GET /projects/:id/issues/:issue_id" do describe "GET /projects/:id/issues/:issue_iid" do
it 'exposes known attributes' do it 'exposes known attributes' do
get api("/projects/#{project.id}/issues/#{issue.id}", user) get api("/projects/#{project.id}/issues/#{issue.iid}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['id']).to eq(issue.id) expect(json_response['id']).to eq(issue.id)
...@@ -777,8 +777,8 @@ describe API::Issues, api: true do ...@@ -777,8 +777,8 @@ describe API::Issues, api: true do
expect(json_response['confidential']).to be_falsy expect(json_response['confidential']).to be_falsy
end end
it "returns a project issue by id" do it "returns a project issue by internal id" do
get api("/projects/#{project.id}/issues/#{issue.id}", user) get api("/projects/#{project.id}/issues/#{issue.iid}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq(issue.title) expect(json_response['title']).to eq(issue.title)
...@@ -790,40 +790,52 @@ describe API::Issues, api: true do ...@@ -790,40 +790,52 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "returns 404 if the issue ID is used" do
get api("/projects/#{project.id}/issues/#{issue.id}", user)
expect(response).to have_http_status(404)
end
context 'confidential issues' do context 'confidential issues' do
it "returns 404 for non project members" do it "returns 404 for non project members" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "returns 404 for project members with guest role" do it "returns 404 for project members with guest role" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "returns confidential issue for project members" do it "returns confidential issue for project members" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid) expect(json_response['iid']).to eq(confidential_issue.iid)
end end
it "returns confidential issue for author" do it "returns confidential issue for author" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid) expect(json_response['iid']).to eq(confidential_issue.iid)
end end
it "returns confidential issue for assignee" do it "returns confidential issue for assignee" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid) expect(json_response['iid']).to eq(confidential_issue.iid)
end end
it "returns confidential issue for admin" do it "returns confidential issue for admin" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid) expect(json_response['iid']).to eq(confidential_issue.iid)
...@@ -1004,23 +1016,29 @@ describe API::Issues, api: true do ...@@ -1004,23 +1016,29 @@ describe API::Issues, api: true do
end end
end end
describe "PUT /projects/:id/issues/:issue_id to update only title" do describe "PUT /projects/:id/issues/:issue_iid to update only title" do
it "updates a project issue" do it "updates a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title') expect(json_response['title']).to eq('updated title')
end end
it "returns 404 error if issue id not found" do it "returns 404 error if issue iid not found" do
put api("/projects/#{project.id}/issues/44444", user), put api("/projects/#{project.id}/issues/44444", user),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'allows special label names' do it "returns 404 error if issue id is used instead of the iid" do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title'
expect(response).to have_http_status(404)
end
it 'allows special label names' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title', title: 'updated title',
labels: 'label, label?, label&foo, ?, &' labels: 'label, label?, label&foo, ?, &'
...@@ -1034,40 +1052,40 @@ describe API::Issues, api: true do ...@@ -1034,40 +1052,40 @@ describe API::Issues, api: true do
context 'confidential issues' do context 'confidential issues' do
it "returns 403 for non project members" do it "returns 403 for non project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member), put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
it "returns 403 for project members with guest role" do it "returns 403 for project members with guest role" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest), put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
it "updates a confidential issue for project members" do it "updates a confidential issue for project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title') expect(json_response['title']).to eq('updated title')
end end
it "updates a confidential issue for author" do it "updates a confidential issue for author" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author), put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title') expect(json_response['title']).to eq('updated title')
end end
it "updates a confidential issue for admin" do it "updates a confidential issue for admin" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin), put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title') expect(json_response['title']).to eq('updated title')
end end
it 'sets an issue to confidential' do it 'sets an issue to confidential' do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
confidential: true confidential: true
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
...@@ -1075,7 +1093,7 @@ describe API::Issues, api: true do ...@@ -1075,7 +1093,7 @@ describe API::Issues, api: true do
end end
it 'makes a confidential issue public' do it 'makes a confidential issue public' do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
confidential: false confidential: false
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
...@@ -1083,7 +1101,7 @@ describe API::Issues, api: true do ...@@ -1083,7 +1101,7 @@ describe API::Issues, api: true do
end end
it 'does not update a confidential issue with wrong confidential flag' do it 'does not update a confidential issue with wrong confidential flag' do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
confidential: 'foo' confidential: 'foo'
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
...@@ -1092,7 +1110,7 @@ describe API::Issues, api: true do ...@@ -1092,7 +1110,7 @@ describe API::Issues, api: true do
end end
end end
describe 'PUT /projects/:id/issues/:issue_id with spam filtering' do describe 'PUT /projects/:id/issues/:issue_iid with spam filtering' do
let(:params) do let(:params) do
{ {
title: 'updated title', title: 'updated title',
...@@ -1105,7 +1123,7 @@ describe API::Issues, api: true do ...@@ -1105,7 +1123,7 @@ describe API::Issues, api: true do
allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true) allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true) allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true)
put api("/projects/#{project.id}/issues/#{issue.id}", user), params put api("/projects/#{project.id}/issues/#{issue.iid}", user), params
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" }) expect(json_response['message']).to eq({ "error" => "Spam detected" })
...@@ -1119,12 +1137,12 @@ describe API::Issues, api: true do ...@@ -1119,12 +1137,12 @@ describe API::Issues, api: true do
end end
end end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do describe 'PUT /projects/:id/issues/:issue_iid to update labels' do
let!(:label) { create(:label, title: 'dummy', project: project) } let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) } let!(:label_link) { create(:label_link, label: label, target: issue) }
it 'does not update labels if not present' do it 'does not update labels if not present' do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title' title: 'updated title'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['labels']).to eq([label.title]) expect(json_response['labels']).to eq([label.title])
...@@ -1135,7 +1153,7 @@ describe API::Issues, api: true do ...@@ -1135,7 +1153,7 @@ describe API::Issues, api: true do
label.toggle_subscription(user2, project) label.toggle_subscription(user2, project)
perform_enqueued_jobs do perform_enqueued_jobs do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'updated title', labels: label.title title: 'updated title', labels: label.title
end end
...@@ -1143,14 +1161,14 @@ describe API::Issues, api: true do ...@@ -1143,14 +1161,14 @@ describe API::Issues, api: true do
end end
it 'removes all labels' do it 'removes all labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: ''
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['labels']).to eq([]) expect(json_response['labels']).to eq([])
end end
it 'updates labels' do it 'updates labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'foo,bar' labels: 'foo,bar'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['labels']).to include 'foo' expect(json_response['labels']).to include 'foo'
...@@ -1158,7 +1176,7 @@ describe API::Issues, api: true do ...@@ -1158,7 +1176,7 @@ describe API::Issues, api: true do
end end
it 'allows special label names' do it 'allows special label names' do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&'
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['labels']).to include 'label:foo' expect(json_response['labels']).to include 'label:foo'
...@@ -1172,7 +1190,7 @@ describe API::Issues, api: true do ...@@ -1172,7 +1190,7 @@ describe API::Issues, api: true do
end end
it 'returns 400 if title is too long' do it 'returns 400 if title is too long' do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
title: 'g' * 256 title: 'g' * 256
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(json_response['message']['title']).to eq([ expect(json_response['message']['title']).to eq([
...@@ -1181,9 +1199,9 @@ describe API::Issues, api: true do ...@@ -1181,9 +1199,9 @@ describe API::Issues, api: true do
end end
end end
describe "PUT /projects/:id/issues/:issue_id to update state and label" do describe "PUT /projects/:id/issues/:issue_iid to update state and label" do
it "updates a project issue" do it "updates a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'label2', state_event: "close" labels: 'label2', state_event: "close"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
...@@ -1192,7 +1210,7 @@ describe API::Issues, api: true do ...@@ -1192,7 +1210,7 @@ describe API::Issues, api: true do
end end
it 'reopens a project isssue' do it 'reopens a project isssue' do
put api("/projects/#{project.id}/issues/#{closed_issue.id}", user), state_event: 'reopen' put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), state_event: 'reopen'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['state']).to eq 'reopened' expect(json_response['state']).to eq 'reopened'
...@@ -1201,7 +1219,7 @@ describe API::Issues, api: true do ...@@ -1201,7 +1219,7 @@ describe API::Issues, api: true do
context 'when an admin or owner makes the request' do context 'when an admin or owner makes the request' do
it 'accepts the update date to be set' do it 'accepts the update date to be set' do
update_time = 2.weeks.ago update_time = 2.weeks.ago
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.iid}", user),
labels: 'label3', state_event: 'close', updated_at: update_time labels: 'label3', state_event: 'close', updated_at: update_time
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
...@@ -1211,25 +1229,25 @@ describe API::Issues, api: true do ...@@ -1211,25 +1229,25 @@ describe API::Issues, api: true do
end end
end end
describe 'PUT /projects/:id/issues/:issue_id to update due date' do describe 'PUT /projects/:id/issues/:issue_iid to update due date' do
it 'creates a new project issue' do it 'creates a new project issue' do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d') due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
put api("/projects/#{project.id}/issues/#{issue.id}", user), due_date: due_date put api("/projects/#{project.id}/issues/#{issue.iid}", user), due_date: due_date
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['due_date']).to eq(due_date) expect(json_response['due_date']).to eq(due_date)
end end
end end
describe "DELETE /projects/:id/issues/:issue_id" do describe "DELETE /projects/:id/issues/:issue_iid" do
it "rejects a non member from deleting an issue" do it "rejects a non member from deleting an issue" do
delete api("/projects/#{project.id}/issues/#{issue.id}", non_member) delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
it "rejects a developer from deleting an issue" do it "rejects a developer from deleting an issue" do
delete api("/projects/#{project.id}/issues/#{issue.id}", author) delete api("/projects/#{project.id}/issues/#{issue.iid}", author)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
...@@ -1238,7 +1256,7 @@ describe API::Issues, api: true do ...@@ -1238,7 +1256,7 @@ describe API::Issues, api: true do
let(:project) { create(:empty_project, namespace: owner.namespace) } let(:project) { create(:empty_project, namespace: owner.namespace) }
it "deletes the issue if an admin requests it" do it "deletes the issue if an admin requests it" do
delete api("/projects/#{project.id}/issues/#{issue.id}", owner) delete api("/projects/#{project.id}/issues/#{issue.iid}", owner)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end end
...@@ -1251,14 +1269,20 @@ describe API::Issues, api: true do ...@@ -1251,14 +1269,20 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
it 'returns 404 when using the issue ID instead of IID' do
delete api("/projects/#{project.id}/issues/#{issue.id}", user)
expect(response).to have_http_status(404)
end
end end
describe '/projects/:id/issues/:issue_id/move' do describe '/projects/:id/issues/:issue_iid/move' do
let!(:target_project) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } let!(:target_project) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace ) }
let!(:target_project2) { create(:empty_project, creator_id: non_member.id, namespace: non_member.namespace ) } let!(:target_project2) { create(:empty_project, creator_id: non_member.id, namespace: non_member.namespace ) }
it 'moves an issue' do it 'moves an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: target_project.id to_project_id: target_project.id
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
...@@ -1267,7 +1291,7 @@ describe API::Issues, api: true do ...@@ -1267,7 +1291,7 @@ describe API::Issues, api: true do
context 'when source and target projects are the same' do context 'when source and target projects are the same' do
it 'returns 400 when trying to move an issue' do it 'returns 400 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: project.id to_project_id: project.id
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
...@@ -1277,7 +1301,7 @@ describe API::Issues, api: true do ...@@ -1277,7 +1301,7 @@ describe API::Issues, api: true do
context 'when the user does not have the permission to move issues' do context 'when the user does not have the permission to move issues' do
it 'returns 400 when trying to move an issue' do it 'returns 400 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: target_project2.id to_project_id: target_project2.id
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
...@@ -1286,13 +1310,23 @@ describe API::Issues, api: true do ...@@ -1286,13 +1310,23 @@ describe API::Issues, api: true do
end end
it 'moves the issue to another namespace if I am admin' do it 'moves the issue to another namespace if I am admin' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", admin), post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin),
to_project_id: target_project2.id to_project_id: target_project2.id
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['project_id']).to eq(target_project2.id) expect(json_response['project_id']).to eq(target_project2.id)
end end
context 'when using the issue ID instead of iid' do
it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
to_project_id: target_project.id
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Issue Not Found')
end
end
context 'when issue does not exist' do context 'when issue does not exist' do
it 'returns 404 when trying to move an issue' do it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/123/move", user), post api("/projects/#{project.id}/issues/123/move", user),
...@@ -1305,7 +1339,7 @@ describe API::Issues, api: true do ...@@ -1305,7 +1339,7 @@ describe API::Issues, api: true do
context 'when source project does not exist' do context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do it 'returns 404 when trying to move an issue' do
post api("/projects/123/issues/#{issue.id}/move", user), post api("/projects/123/issues/#{issue.iid}/move", user),
to_project_id: target_project.id to_project_id: target_project.id
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
...@@ -1315,7 +1349,7 @@ describe API::Issues, api: true do ...@@ -1315,7 +1349,7 @@ describe API::Issues, api: true do
context 'when target project does not exist' do context 'when target project does not exist' do
it 'returns 404 when trying to move an issue' do it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
to_project_id: 123 to_project_id: 123
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
...@@ -1323,16 +1357,16 @@ describe API::Issues, api: true do ...@@ -1323,16 +1357,16 @@ describe API::Issues, api: true do
end end
end end
describe 'POST :id/issues/:issue_id/subscribe' do describe 'POST :id/issues/:issue_iid/subscribe' do
it 'subscribes to an issue' do it 'subscribes to an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user2) post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(true) expect(json_response['subscribed']).to eq(true)
end end
it 'returns 304 if already subscribed' do it 'returns 304 if already subscribed' do
post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user)
expect(response).to have_http_status(304) expect(response).to have_http_status(304)
end end
...@@ -1343,8 +1377,14 @@ describe API::Issues, api: true do ...@@ -1343,8 +1377,14 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'returns 404 if the issue ID is used instead of the iid' do
post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)
expect(response).to have_http_status(404)
end
it 'returns 404 if the issue is confidential' do it 'returns 404 if the issue is confidential' do
post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscribe", non_member) post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
...@@ -1352,14 +1392,14 @@ describe API::Issues, api: true do ...@@ -1352,14 +1392,14 @@ describe API::Issues, api: true do
describe 'POST :id/issues/:issue_id/unsubscribe' do describe 'POST :id/issues/:issue_id/unsubscribe' do
it 'unsubscribes from an issue' do it 'unsubscribes from an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(false) expect(json_response['subscribed']).to eq(false)
end end
it 'returns 304 if not subscribed' do it 'returns 304 if not subscribed' do
post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user2) post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user2)
expect(response).to have_http_status(304) expect(response).to have_http_status(304)
end end
...@@ -1370,8 +1410,14 @@ describe API::Issues, api: true do ...@@ -1370,8 +1410,14 @@ describe API::Issues, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'returns 404 if using the issue ID instead of iid' do
post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)
expect(response).to have_http_status(404)
end
it 'returns 404 if the issue is confidential' do it 'returns 404 if the issue is confidential' do
post api("/projects/#{project.id}/issues/#{confidential_issue.id}/unsubscribe", non_member) post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
......
...@@ -13,9 +13,9 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do ...@@ -13,9 +13,9 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
project.team << [user, :master] project.team << [user, :master]
end end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do describe 'GET /projects/:id/merge_requests/:merge_request_iid/versions' do
it 'returns 200 for a valid merge request' do it 'returns 200 for a valid merge request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions", user)
merge_request_diff = merge_request.merge_request_diffs.first merge_request_diff = merge_request.merge_request_diffs.first
expect(response.status).to eq 200 expect(response.status).to eq 200
...@@ -26,16 +26,22 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do ...@@ -26,16 +26,22 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request id is used instead of the iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
expect(response).to have_http_status(404)
end
it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/versions", user) get api("/projects/#{project.id}/merge_requests/999/versions", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do describe 'GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id' do
let(:merge_request_diff) { merge_request.merge_request_diffs.first }
it 'returns a 200 for a valid merge request' do it 'returns a 200 for a valid merge request' do
merge_request_diff = merge_request.merge_request_diffs.first get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/#{merge_request_diff.id}", user)
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(json_response['id']).to eq(merge_request_diff.id) expect(json_response['id']).to eq(merge_request_diff.id)
...@@ -43,8 +49,18 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do ...@@ -43,8 +49,18 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size)
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request id is used instead of the iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
expect(response).to have_http_status(404)
end
it 'returns a 404 when merge_request version_id is not found' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/999", user)
expect(response).to have_http_status(404)
end
it 'returns a 404 when merge_request_iid is not found' do
get api("/projects/#{project.id}/merge_requests/12345/versions/#{merge_request_diff.id}", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
......
...@@ -153,9 +153,9 @@ describe API::MergeRequests, api: true do ...@@ -153,9 +153,9 @@ describe API::MergeRequests, api: true do
end end
end end
describe "GET /projects/:id/merge_requests/:merge_request_id" do describe "GET /projects/:id/merge_requests/:merge_request_iid" do
it 'exposes known attributes' do it 'exposes known attributes' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['id']).to eq(merge_request.id) expect(json_response['id']).to eq(merge_request.id)
...@@ -184,7 +184,7 @@ describe API::MergeRequests, api: true do ...@@ -184,7 +184,7 @@ describe API::MergeRequests, api: true do
end end
it "returns merge_request" do it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq(merge_request.title) expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['iid']).to eq(merge_request.iid)
...@@ -194,25 +194,31 @@ describe API::MergeRequests, api: true do ...@@ -194,25 +194,31 @@ describe API::MergeRequests, api: true do
expect(json_response['force_close_merge_request']).to be_falsy expect(json_response['force_close_merge_request']).to be_falsy
end end
it "returns a 404 error if merge_request_id not found" do it "returns a 404 error if merge_request_iid not found" do
get api("/projects/#{project.id}/merge_requests/999", user) get api("/projects/#{project.id}/merge_requests/999", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "returns a 404 error if merge_request `id` is used instead of iid" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response).to have_http_status(404)
end
context 'Work in Progress' do context 'Work in Progress' do
let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) } let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) }
it "returns merge_request" do it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user) get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['work_in_progress']).to eq(true) expect(json_response['work_in_progress']).to eq(true)
end end
end end
end end
describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do describe 'GET /projects/:id/merge_requests/:merge_request_iid/commits' do
it 'returns a 200 when merge request is valid' do it 'returns a 200 when merge request is valid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
commit = merge_request.commits.first commit = merge_request.commits.first
expect(response.status).to eq 200 expect(response.status).to eq 200
...@@ -223,24 +229,36 @@ describe API::MergeRequests, api: true do ...@@ -223,24 +229,36 @@ describe API::MergeRequests, api: true do
expect(json_response.first['title']).to eq(commit.title) expect(json_response.first['title']).to eq(commit.title)
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/commits", user) get api("/projects/#{project.id}/merge_requests/999/commits", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'returns a 404 when merge_request id is used instead of iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)
expect(response).to have_http_status(404)
end
end end
describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do describe 'GET /projects/:id/merge_requests/:merge_request_iid/changes' do
it 'returns the change information of the merge_request' do it 'returns the change information of the merge_request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes", user)
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size) expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/changes", user) get api("/projects/#{project.id}/merge_requests/999/changes", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'returns a 404 when merge_request id is used instead of iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
expect(response).to have_http_status(404)
end
end end
describe "POST /projects/:id/merge_requests" do describe "POST /projects/:id/merge_requests" do
...@@ -400,7 +418,7 @@ describe API::MergeRequests, api: true do ...@@ -400,7 +418,7 @@ describe API::MergeRequests, api: true do
end end
end end
describe "DELETE /projects/:id/merge_requests/:merge_request_id" do describe "DELETE /projects/:id/merge_requests/:merge_request_iid" do
context "when the user is developer" do context "when the user is developer" do
let(:developer) { create(:user) } let(:developer) { create(:user) }
...@@ -409,25 +427,37 @@ describe API::MergeRequests, api: true do ...@@ -409,25 +427,37 @@ describe API::MergeRequests, api: true do
end end
it "denies the deletion of the merge request" do it "denies the deletion of the merge request" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer) delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", developer)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
end end
context "when the user is project owner" do context "when the user is project owner" do
it "destroys the merge request owners can destroy" do it "destroys the merge request owners can destroy" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_http_status(204) expect(response).to have_http_status(204)
end end
it "returns 404 for an invalid merge request IID" do
delete api("/projects/#{project.id}/merge_requests/12345", user)
expect(response).to have_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response).to have_http_status(404)
end
end end
end end
describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge" do
let(:pipeline) { create(:ci_pipeline_without_jobs) } let(:pipeline) { create(:ci_pipeline_without_jobs) }
it "returns merge_request in case of success" do it "returns merge_request in case of success" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
...@@ -436,7 +466,7 @@ describe API::MergeRequests, api: true do ...@@ -436,7 +466,7 @@ describe API::MergeRequests, api: true do
allow_any_instance_of(MergeRequest). allow_any_instance_of(MergeRequest).
to receive(:can_be_merged?).and_return(false) to receive(:can_be_merged?).and_return(false)
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(406) expect(response).to have_http_status(406)
expect(json_response['message']).to eq('Branch cannot be merged') expect(json_response['message']).to eq('Branch cannot be merged')
...@@ -444,14 +474,14 @@ describe API::MergeRequests, api: true do ...@@ -444,14 +474,14 @@ describe API::MergeRequests, api: true do
it "returns 405 if merge_request is not open" do it "returns 405 if merge_request is not open" do
merge_request.close merge_request.close
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(405) expect(response).to have_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed') expect(json_response['message']).to eq('405 Method Not Allowed')
end end
it "returns 405 if merge_request is a work in progress" do it "returns 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}") merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(405) expect(response).to have_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed') expect(json_response['message']).to eq('405 Method Not Allowed')
end end
...@@ -459,7 +489,7 @@ describe API::MergeRequests, api: true do ...@@ -459,7 +489,7 @@ describe API::MergeRequests, api: true do
it 'returns 405 if the build failed for a merge request that requires success' do it 'returns 405 if the build failed for a merge request that requires success' do
allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false) allow_any_instance_of(MergeRequest).to receive(:mergeable_ci_state?).and_return(false)
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
expect(response).to have_http_status(405) expect(response).to have_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed') expect(json_response['message']).to eq('405 Method Not Allowed')
...@@ -468,20 +498,20 @@ describe API::MergeRequests, api: true do ...@@ -468,20 +498,20 @@ describe API::MergeRequests, api: true do
it "returns 401 if user has no permissions to merge" do it "returns 401 if user has no permissions to merge" do
user2 = create(:user) user2 = create(:user)
project.team << [user2, :reporter] project.team << [user2, :reporter]
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2) put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2)
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
expect(json_response['message']).to eq('401 Unauthorized') expect(json_response['message']).to eq('401 Unauthorized')
end end
it "returns 409 if the SHA parameter doesn't match" do it "returns 409 if the SHA parameter doesn't match" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha.reverse put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha.reverse
expect(response).to have_http_status(409) expect(response).to have_http_status(409)
expect(json_response['message']).to start_with('SHA does not match HEAD of source branch') expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
end end
it "succeeds if the SHA parameter matches" do it "succeeds if the SHA parameter matches" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
...@@ -490,18 +520,30 @@ describe API::MergeRequests, api: true do ...@@ -490,18 +520,30 @@ describe API::MergeRequests, api: true do
allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
allow(pipeline).to receive(:active?).and_return(true) allow(pipeline).to receive(:active?).and_return(true)
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_pipeline_succeeds: true put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq('Test') expect(json_response['title']).to eq('Test')
expect(json_response['merge_when_pipeline_succeeds']).to eq(true) expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
end end
it "returns 404 for an invalid merge request IID" do
put api("/projects/#{project.id}/merge_requests/12345/merge", user)
expect(response).to have_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response).to have_http_status(404)
end
end end
describe "PUT /projects/:id/merge_requests/:merge_request_id" do describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
context "to close a MR" do context "to close a MR" do
it "returns merge_request" do it "returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['state']).to eq('closed') expect(json_response['state']).to eq('closed')
...@@ -509,38 +551,38 @@ describe API::MergeRequests, api: true do ...@@ -509,38 +551,38 @@ describe API::MergeRequests, api: true do
end end
it "updates title and returns merge_request" do it "updates title and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq('New title') expect(json_response['title']).to eq('New title')
end end
it "updates description and returns merge_request" do it "updates description and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description" put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['description']).to eq('New description') expect(json_response['description']).to eq('New description')
end end
it "updates milestone_id and returns merge_request" do it "updates milestone_id and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['milestone']['id']).to eq(milestone.id) expect(json_response['milestone']['id']).to eq(milestone.id)
end end
it "returns merge_request with renamed target_branch" do it "returns merge_request with renamed target_branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['target_branch']).to eq('wiki') expect(json_response['target_branch']).to eq('wiki')
end end
it "returns merge_request that removes the source branch" do it "returns merge_request that removes the source branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), remove_source_branch: true put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['force_remove_source_branch']).to be_truthy expect(json_response['force_remove_source_branch']).to be_truthy
end end
it 'allows special label names' do it 'allows special label names' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
title: 'new issue', title: 'new issue',
labels: 'label, label?, label&foo, ?, &' labels: 'label, label?, label&foo, ?, &'
...@@ -553,7 +595,7 @@ describe API::MergeRequests, api: true do ...@@ -553,7 +595,7 @@ describe API::MergeRequests, api: true do
end end
it 'does not update state when title is empty' do it 'does not update state when title is empty' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil
merge_request.reload merge_request.reload
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
...@@ -561,19 +603,31 @@ describe API::MergeRequests, api: true do ...@@ -561,19 +603,31 @@ describe API::MergeRequests, api: true do
end end
it 'does not update state when target_branch is empty' do it 'does not update state when target_branch is empty' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil
merge_request.reload merge_request.reload
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(merge_request.state).to eq('opened') expect(merge_request.state).to eq('opened')
end end
it "returns 404 for an invalid merge request IID" do
put api("/projects/#{project.id}/merge_requests/12345", user), state_event: "close"
expect(response).to have_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
expect(response).to have_http_status(404)
end
end end
describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do describe "POST /projects/:id/merge_requests/:merge_request_iid/comments" do
it "returns comment" do it "returns comment" do
original_count = merge_request.notes.size original_count = merge_request.notes.size
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/comments", user), note: "My comment"
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['note']).to eq('My comment') expect(json_response['note']).to eq('My comment')
...@@ -583,23 +637,29 @@ describe API::MergeRequests, api: true do ...@@ -583,23 +637,29 @@ describe API::MergeRequests, api: true do
end end
it "returns 400 if note is missing" do it "returns 400 if note is missing" do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/comments", user)
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
end end
it "returns 404 if note is attached to non existent merge request" do it "returns 404 if merge request iid is invalid" do
post api("/projects/#{project.id}/merge_requests/404/comments", user), post api("/projects/#{project.id}/merge_requests/404/comments", user),
note: 'My comment' note: 'My comment'
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "returns 404 if merge request id is used instead of iid" do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user),
note: 'My comment'
expect(response).to have_http_status(404)
end
end end
describe "GET :id/merge_requests/:merge_request_id/comments" do describe "GET :id/merge_requests/:merge_request_iid/comments" do
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
it "returns merge_request comments ordered by created_at" do it "returns merge_request comments ordered by created_at" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/comments", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -610,20 +670,25 @@ describe API::MergeRequests, api: true do ...@@ -610,20 +670,25 @@ describe API::MergeRequests, api: true do
expect(json_response.last['note']).to eq("another comment on a MR") expect(json_response.last['note']).to eq("another comment on a MR")
end end
it "returns a 404 error if merge_request_id not found" do it "returns a 404 error if merge_request_iid is invalid" do
get api("/projects/#{project.id}/merge_requests/999/comments", user) get api("/projects/#{project.id}/merge_requests/999/comments", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it "returns a 404 error if merge_request id is used instead of iid" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response).to have_http_status(404)
end
end end
describe 'GET :id/merge_requests/:merge_request_id/closes_issues' do describe 'GET :id/merge_requests/:merge_request_iid/closes_issues' do
it 'returns the issue that will be closed on merge' do it 'returns the issue that will be closed on merge' do
issue = create(:issue, project: project) issue = create(:issue, project: project)
mr = merge_request.tap do |mr| mr = merge_request.tap do |mr|
mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}") mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
end end
get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user) get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -633,7 +698,7 @@ describe API::MergeRequests, api: true do ...@@ -633,7 +698,7 @@ describe API::MergeRequests, api: true do
end end
it 'returns an empty array when there are no issues to be closed' do it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -647,7 +712,7 @@ describe API::MergeRequests, api: true do ...@@ -647,7 +712,7 @@ describe API::MergeRequests, api: true do
merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project) merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project)
merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}") merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}")
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user) get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -663,22 +728,34 @@ describe API::MergeRequests, api: true do ...@@ -663,22 +728,34 @@ describe API::MergeRequests, api: true do
guest = create(:user) guest = create(:user)
project.team << [guest, :guest] project.team << [guest, :guest]
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest) get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", guest)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
it "returns 404 for an invalid merge request IID" do
get api("/projects/#{project.id}/merge_requests/12345/closes_issues", user)
expect(response).to have_http_status(404)
end end
describe 'POST :id/merge_requests/:merge_request_id/subscribe' do it "returns 404 if the merge request id is used instead of iid" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
expect(response).to have_http_status(404)
end
end
describe 'POST :id/merge_requests/:merge_request_iid/subscribe' do
it 'subscribes to a merge request' do it 'subscribes to a merge request' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", admin) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", admin)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(true) expect(json_response['subscribed']).to eq(true)
end end
it 'returns 304 if already subscribed' do it 'returns 304 if already subscribed' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", user)
expect(response).to have_http_status(304) expect(response).to have_http_status(304)
end end
...@@ -689,26 +766,32 @@ describe API::MergeRequests, api: true do ...@@ -689,26 +766,32 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'returns 404 if the merge request id is used instead of iid' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user)
expect(response).to have_http_status(404)
end
it 'returns 403 if user has no access to read code' do it 'returns 403 if user has no access to read code' do
guest = create(:user) guest = create(:user)
project.team << [guest, :guest] project.team << [guest, :guest]
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", guest) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", guest)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
end end
describe 'POST :id/merge_requests/:merge_request_id/unsubscribe' do describe 'POST :id/merge_requests/:merge_request_iid/unsubscribe' do
it 'unsubscribes from a merge request' do it 'unsubscribes from a merge request' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", user)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['subscribed']).to eq(false) expect(json_response['subscribed']).to eq(false)
end end
it 'returns 304 if not subscribed' do it 'returns 304 if not subscribed' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", admin) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", admin)
expect(response).to have_http_status(304) expect(response).to have_http_status(304)
end end
...@@ -719,11 +802,17 @@ describe API::MergeRequests, api: true do ...@@ -719,11 +802,17 @@ describe API::MergeRequests, api: true do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
it 'returns 404 if the merge request id is used instead of iid' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user)
expect(response).to have_http_status(404)
end
it 'returns 403 if user has no access to read code' do it 'returns 403 if user has no access to read code' do
guest = create(:user) guest = create(:user)
project.team << [guest, :guest] project.team << [guest, :guest]
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", guest) post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", guest)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
......
...@@ -163,7 +163,7 @@ describe API::Todos, api: true do ...@@ -163,7 +163,7 @@ describe API::Todos, api: true do
shared_examples 'an issuable' do |issuable_type| shared_examples 'an issuable' do |issuable_type|
it 'creates a todo on an issuable' do it 'creates a todo on an issuable' do
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe) post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(json_response['project']).to be_a Hash expect(json_response['project']).to be_a Hash
...@@ -180,7 +180,7 @@ describe API::Todos, api: true do ...@@ -180,7 +180,7 @@ describe API::Todos, api: true do
it 'returns 304 there already exist a todo on that issuable' do it 'returns 304 there already exist a todo on that issuable' do
create(:todo, project: project_1, author: author_1, user: john_doe, target: issuable) create(:todo, project: project_1, author: author_1, user: john_doe, target: issuable)
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", john_doe) post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", john_doe)
expect(response.status).to eq(304) expect(response.status).to eq(304)
end end
...@@ -195,7 +195,7 @@ describe API::Todos, api: true do ...@@ -195,7 +195,7 @@ describe API::Todos, api: true do
guest = create(:user) guest = create(:user)
project_1.team << [guest, :guest] project_1.team << [guest, :guest]
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.id}/todo", guest) post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", guest)
if issuable_type == 'merge_requests' if issuable_type == 'merge_requests'
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
......
...@@ -13,6 +13,231 @@ describe API::V3::AwardEmoji, api: true do ...@@ -13,6 +13,231 @@ describe API::V3::AwardEmoji, api: true do
before { project.team << [user, :master] } before { project.team << [user, :master] }
describe "GET /projects/:id/awardable/:awardable_id/award_emoji" do
context 'on an issue' do
it "returns an array of award_emoji" do
get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(award_emoji.name)
end
it "returns a 404 error when issue id not found" do
get v3_api("/projects/#{project.id}/issues/12345/award_emoji", user)
expect(response).to have_http_status(404)
end
end
context 'on a merge request' do
it "returns an array of award_emoji" do
get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(downvote.name)
end
end
context 'on a snippet' do
let(:snippet) { create(:project_snippet, :public, project: project) }
let!(:award) { create(:award_emoji, awardable: snippet) }
it 'returns the awarded emoji' do
get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(award.name)
end
end
context 'when the user has no access' do
it 'returns a status code 404' do
user1 = create(:user)
get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1)
expect(response).to have_http_status(404)
end
end
end
describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji' do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an array of award emoji' do
get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(rocket.name)
end
end
describe "GET /projects/:id/awardable/:awardable_id/award_emoji/:award_id" do
context 'on an issue' do
it "returns the award emoji" do
get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(award_emoji.name)
expect(json_response['awardable_id']).to eq(issue.id)
expect(json_response['awardable_type']).to eq("Issue")
end
it "returns a 404 error if the award is not found" do
get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user)
expect(response).to have_http_status(404)
end
end
context 'on a merge request' do
it 'returns the award emoji' do
get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(downvote.name)
expect(json_response['awardable_id']).to eq(merge_request.id)
expect(json_response['awardable_type']).to eq("MergeRequest")
end
end
context 'on a snippet' do
let(:snippet) { create(:project_snippet, :public, project: project) }
let!(:award) { create(:award_emoji, awardable: snippet) }
it 'returns the awarded emoji' do
get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(award.name)
expect(json_response['awardable_id']).to eq(snippet.id)
expect(json_response['awardable_type']).to eq("Snippet")
end
end
context 'when the user has no access' do
it 'returns a status code 404' do
user1 = create(:user)
get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1)
expect(response).to have_http_status(404)
end
end
end
describe 'GET /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji/:award_id' do
let!(:rocket) { create(:award_emoji, awardable: note, name: 'rocket') }
it 'returns an award emoji' do
get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user)
expect(response).to have_http_status(200)
expect(json_response).not_to be_an Array
expect(json_response['name']).to eq(rocket.name)
end
end
describe "POST /projects/:id/awardable/:awardable_id/award_emoji" do
let(:issue2) { create(:issue, project: project, author: user) }
context "on an issue" do
it "creates a new award emoji" do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish'
expect(response).to have_http_status(201)
expect(json_response['name']).to eq('blowfish')
expect(json_response['user']['username']).to eq(user.username)
end
it "returns a 400 bad request error if the name is not given" do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user)
expect(response).to have_http_status(400)
end
it "returns a 401 unauthorized error if the user is not authenticated" do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup'
expect(response).to have_http_status(401)
end
it "returns a 404 error if the user authored issue" do
post v3_api("/projects/#{project.id}/issues/#{issue2.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404)
end
it "normalizes +1 as thumbsup award" do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1'
expect(issue.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
end
end
end
context 'on a snippet' do
it 'creates a new award emoji' do
snippet = create(:project_snippet, :public, project: project)
post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish'
expect(response).to have_http_status(201)
expect(json_response['name']).to eq('blowfish')
expect(json_response['user']['username']).to eq(user.username)
end
end
end
describe "POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji" do
let(:note2) { create(:note, project: project, noteable: issue, author: user) }
it 'creates a new award emoji' do
expect do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
end.to change { note.award_emoji.count }.from(0).to(1)
expect(response).to have_http_status(201)
expect(json_response['user']['username']).to eq(user.username)
end
it "it returns 404 error when user authored note" do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404)
end
it "normalizes +1 as thumbsup award" do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1'
expect(note.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
end
end
end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
context 'when the awardable is an Issue' do context 'when the awardable is an Issue' do
it 'deletes the award' do it 'deletes the award' do
......
...@@ -1288,6 +1288,6 @@ describe API::V3::Issues, api: true do ...@@ -1288,6 +1288,6 @@ describe API::V3::Issues, api: true do
describe 'time tracking endpoints' do describe 'time tracking endpoints' do
let(:issuable) { issue } let(:issuable) { issue }
include_examples 'time tracking endpoints', 'issue' include_examples 'V3 time tracking endpoints', 'issue'
end end
end end
require "spec_helper" require "spec_helper"
describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do describe API::V3::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
include ApiHelpers include ApiHelpers
let!(:user) { create(:user) } let!(:user) { create(:user) }
...@@ -15,7 +15,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do ...@@ -15,7 +15,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
it 'returns 200 for a valid merge request' do it 'returns 200 for a valid merge request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
merge_request_diff = merge_request.merge_request_diffs.first merge_request_diff = merge_request.merge_request_diffs.first
expect(response.status).to eq 200 expect(response.status).to eq 200
...@@ -25,7 +25,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do ...@@ -25,7 +25,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request_id not found' do
get api("/projects/#{project.id}/merge_requests/999/versions", user) get v3_api("/projects/#{project.id}/merge_requests/999/versions", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
...@@ -33,7 +33,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do ...@@ -33,7 +33,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
it 'returns a 200 for a valid merge request' do it 'returns a 200 for a valid merge request' do
merge_request_diff = merge_request.merge_request_diffs.first merge_request_diff = merge_request.merge_request_diffs.first
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(json_response['id']).to eq(merge_request_diff.id) expect(json_response['id']).to eq(merge_request_diff.id)
...@@ -42,7 +42,8 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do ...@@ -42,7 +42,8 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
end end
it 'returns a 404 when merge_request_id not found' do it 'returns a 404 when merge_request_id not found' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user) get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
......
...@@ -712,7 +712,7 @@ describe API::MergeRequests, api: true do ...@@ -712,7 +712,7 @@ describe API::MergeRequests, api: true do
describe 'Time tracking' do describe 'Time tracking' do
let(:issuable) { merge_request } let(:issuable) { merge_request }
include_examples 'time tracking endpoints', 'merge_request' include_examples 'V3 time tracking endpoints', 'merge_request'
end end
def mr_with_later_created_and_updated_at_time def mr_with_later_created_and_updated_at_time
......
...@@ -7,13 +7,13 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -7,13 +7,13 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
context 'with an unauthorized user' do context 'with an unauthorized user' do
subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", non_member), duration: '1w') } subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", non_member), duration: '1w') }
it_behaves_like 'an unauthorized API user' it_behaves_like 'an unauthorized API user'
end end
it "sets the time estimate for #{issuable_name}" do it "sets the time estimate for #{issuable_name}" do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w' post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['human_time_estimate']).to eq('1w') expect(json_response['human_time_estimate']).to eq('1w')
...@@ -21,12 +21,12 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -21,12 +21,12 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe 'updating the current estimate' do describe 'updating the current estimate' do
before do before do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w' post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w'
end end
context 'when duration has a bad format' do context 'when duration has a bad format' do
it 'does not modify the original estimate' do it 'does not modify the original estimate' do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo' post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: 'foo'
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(issuable.reload.human_time_estimate).to eq('1w') expect(issuable.reload.human_time_estimate).to eq('1w')
...@@ -35,7 +35,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -35,7 +35,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
context 'with a valid duration' do context 'with a valid duration' do
it 'updates the estimate' do it 'updates the estimate' do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h' post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '3w1h'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(issuable.reload.human_time_estimate).to eq('3w 1h') expect(issuable.reload.human_time_estimate).to eq('3w 1h')
...@@ -46,13 +46,13 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -46,13 +46,13 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_time_estimate" do describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_time_estimate" do
context 'with an unauthorized user' do context 'with an unauthorized user' do
subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", non_member)) } subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_time_estimate", non_member)) }
it_behaves_like 'an unauthorized API user' it_behaves_like 'an unauthorized API user'
end end
it "resets the time estimate for #{issuable_name}" do it "resets the time estimate for #{issuable_name}" do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user) post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_time_estimate", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['time_estimate']).to eq(0) expect(json_response['time_estimate']).to eq(0)
...@@ -62,7 +62,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -62,7 +62,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do
context 'with an unauthorized user' do context 'with an unauthorized user' do
subject do subject do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", non_member), post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", non_member),
duration: '2h' duration: '2h'
end end
...@@ -70,7 +70,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -70,7 +70,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
end end
it "add spent time for #{issuable_name}" do it "add spent time for #{issuable_name}" do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
duration: '2h' duration: '2h'
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
...@@ -81,7 +81,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -81,7 +81,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
it 'subtracts time of the total spent time' do it 'subtracts time of the total spent time' do
issuable.update_attributes!(spend_time: { duration: 7200, user: user }) issuable.update_attributes!(spend_time: { duration: 7200, user: user })
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
duration: '-1h' duration: '-1h'
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
...@@ -93,7 +93,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -93,7 +93,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
it 'does not modify the total time spent' do it 'does not modify the total time spent' do
issuable.update_attributes!(spend_time: { duration: 7200, user: user }) issuable.update_attributes!(spend_time: { duration: 7200, user: user })
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
duration: '-1w' duration: '-1w'
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
...@@ -104,13 +104,13 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -104,13 +104,13 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do
context 'with an unauthorized user' do context 'with an unauthorized user' do
subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", non_member)) } subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_spent_time", non_member)) }
it_behaves_like 'an unauthorized API user' it_behaves_like 'an unauthorized API user'
end end
it "resets spent time for #{issuable_name}" do it "resets spent time for #{issuable_name}" do
post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user) post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_spent_time", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['total_time_spent']).to eq(0) expect(json_response['total_time_spent']).to eq(0)
...@@ -122,7 +122,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| ...@@ -122,7 +122,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
issuable.update_attributes!(spend_time: { duration: 1800, user: user }, issuable.update_attributes!(spend_time: { duration: 1800, user: user },
time_estimate: 3600) time_estimate: 3600)
get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user) get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_stats", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['total_time_spent']).to eq(1800) expect(json_response['total_time_spent']).to eq(1800)
......
shared_examples 'V3 time tracking endpoints' do |issuable_name|
issuable_collection_name = issuable_name.pluralize
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
context 'with an unauthorized user' do
subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", non_member), duration: '1w') }
it_behaves_like 'an unauthorized API user'
end
it "sets the time estimate for #{issuable_name}" do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
expect(response).to have_http_status(200)
expect(json_response['human_time_estimate']).to eq('1w')
end
describe 'updating the current estimate' do
before do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w'
end
context 'when duration has a bad format' do
it 'does not modify the original estimate' do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo'
expect(response).to have_http_status(400)
expect(issuable.reload.human_time_estimate).to eq('1w')
end
end
context 'with a valid duration' do
it 'updates the estimate' do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h'
expect(response).to have_http_status(200)
expect(issuable.reload.human_time_estimate).to eq('3w 1h')
end
end
end
end
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_time_estimate" do
context 'with an unauthorized user' do
subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", non_member)) }
it_behaves_like 'an unauthorized API user'
end
it "resets the time estimate for #{issuable_name}" do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user)
expect(response).to have_http_status(200)
expect(json_response['time_estimate']).to eq(0)
end
end
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do
context 'with an unauthorized user' do
subject do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", non_member),
duration: '2h'
end
it_behaves_like 'an unauthorized API user'
end
it "add spent time for #{issuable_name}" do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
duration: '2h'
expect(response).to have_http_status(201)
expect(json_response['human_total_time_spent']).to eq('2h')
end
context 'when subtracting time' do
it 'subtracts time of the total spent time' do
issuable.update_attributes!(spend_time: { duration: 7200, user: user })
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
duration: '-1h'
expect(response).to have_http_status(201)
expect(json_response['total_time_spent']).to eq(3600)
end
end
context 'when time to subtract is greater than the total spent time' do
it 'does not modify the total time spent' do
issuable.update_attributes!(spend_time: { duration: 7200, user: user })
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user),
duration: '-1w'
expect(response).to have_http_status(400)
expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/)
end
end
end
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/reset_spent_time" do
context 'with an unauthorized user' do
subject { post(v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", non_member)) }
it_behaves_like 'an unauthorized API user'
end
it "resets spent time for #{issuable_name}" do
post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user)
expect(response).to have_http_status(200)
expect(json_response['total_time_spent']).to eq(0)
end
end
describe "GET /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_stats" do
it "returns the time stats for #{issuable_name}" do
issuable.update_attributes!(spend_time: { duration: 1800, user: user },
time_estimate: 3600)
get v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user)
expect(response).to have_http_status(200)
expect(json_response['total_time_spent']).to eq(1800)
expect(json_response['time_estimate']).to eq(3600)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment