diff --git a/doc/api/users.md b/doc/api/users.md index 382d5fe03c1f37feef3d6749f804b9f8ed18f1ed..3578d3197749ed88046135de9d274ef04400855f 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -785,6 +785,7 @@ Set the status of the current user. ```plaintext PUT /user/status +PATCH /user/status ``` | Attribute | Type | Required | Description | @@ -793,7 +794,9 @@ PUT /user/status | `message` | string | no | Message to set as a status. It can also contain emoji codes. Cannot exceed 100 characters. | | `clear_status_after` | string | no | Automatically clean up the status after a given time interval, allowed values: `30_minutes`, `3_hours`, `8_hours`, `1_day`, `3_days`, `7_days`, `30_days` -When both parameters `emoji` and `message` are empty, the status is cleared. When the `clear_status_after` parameter is missing from the request, the previously set value for `"clear_status_after` is cleared. +Difference between `PUT` and `PATCH` + +When using `PUT` any parameters that are not passed will be set to `null` and therefore cleared. When using `PATCH` any parameters that are not passed will be ignored. Explicitly pass `null` to clear a field. ```shell curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "clear_status_after=1_day" --data "emoji=coffee" \ diff --git a/lib/api/users.rb b/lib/api/users.rb index d2d45c942918c3448a36b09fd222880ed8de219d..7b4c9104cd8194476e7e5efc718ff50dee236bd5 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1020,6 +1020,25 @@ def target_user end end + helpers do + def set_user_status(include_missing_params:) + forbidden! unless can?(current_user, :update_user_status, current_user) + + if ::Users::SetStatusService.new(current_user, declared_params(include_missing: include_missing_params)).execute + present current_user.status, with: Entities::UserStatus + else + render_validation_error!(current_user.status) + end + end + + params :set_user_status_params do + optional :emoji, type: String, desc: "The emoji to set on the status" + optional :message, type: String, desc: "The status message to set" + optional :availability, type: String, desc: "The availability of user to set" + optional :clear_status_after, type: String, desc: "Automatically clear emoji, message and availability fields after a certain time", values: UserStatus::CLEAR_STATUS_QUICK_OPTIONS.keys + end + end + desc "Get the currently authenticated user's SSH keys" do success Entities::SSHKey end @@ -1299,21 +1318,30 @@ def target_user desc 'Set the status of the current user' do success Entities::UserStatus + detail 'Any parameters that are not passed will be nullified.' end params do - optional :emoji, type: String, desc: "The emoji to set on the status" - optional :message, type: String, desc: "The status message to set" - optional :availability, type: String, desc: "The availability of user to set" - optional :clear_status_after, type: String, desc: "Automatically clear emoji, message and availability fields after a certain time", values: UserStatus::CLEAR_STATUS_QUICK_OPTIONS.keys + use :set_user_status_params end put "status", feature_category: :users do - forbidden! unless can?(current_user, :update_user_status, current_user) + set_user_status(include_missing_params: true) + end - if ::Users::SetStatusService.new(current_user, declared_params).execute - present current_user.status, with: Entities::UserStatus - else - render_validation_error!(current_user.status) + desc 'Set the status of the current user' do + success Entities::UserStatus + detail 'Any parameters that are not passed will be ignored.' + end + params do + use :set_user_status_params + end + patch "status", feature_category: :users do + if declared_params(include_missing: false).empty? + status :ok + + break end + + set_user_status(include_missing_params: false) end desc 'get the status of the current user' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index bfb71d95f5e9b06878f8ff247f930241961d121e..0808b8b3a194f75970573b179945c5395df21221 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -4045,60 +4045,164 @@ def update_password(user, admin, password = User.random_password) end end - describe 'GET /user/status' do - let(:path) { '/user/status' } + describe '/user/status' do + let(:user_status) { create(:user_status, clear_status_at: 8.hours.from_now) } + let(:user_with_status) { user_status.user } + let(:params) { {} } + let(:request_user) { user } - it_behaves_like 'rendering user status' - end + shared_examples '/user/status successful response' do + context 'when request is successful' do + let(:params) { { emoji: 'smirk', message: 'hello world' } } - describe 'PUT /user/status' do - it 'saves the status' do - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world' } + it 'saves the status' do + set_user_status - expect(response).to have_gitlab_http_status(:success) - expect(json_response['emoji']).to eq('smirk') + expect(response).to have_gitlab_http_status(:success) + expect(json_response['emoji']).to eq('smirk') + expect(json_response['message']).to eq('hello world') + end + end end - it 'renders errors when the status was invalid' do - put api('/user/status', user), params: { emoji: 'does not exist', message: 'hello world' } + shared_examples '/user/status unsuccessful response' do + context 'when request is unsuccessful' do + let(:params) { { emoji: 'does not exist', message: 'hello world' } } - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['emoji']).to be_present + it 'renders errors' do + set_user_status + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']['emoji']).to be_present + end + end end - it 'deletes the status when passing empty values' do - put api('/user/status', user) + shared_examples '/user/status passing nil for params' do + context 'when passing nil for params' do + let(:params) { { emoji: nil, message: nil, clear_status_after: nil } } + let(:request_user) { user_with_status } - expect(response).to have_gitlab_http_status(:success) - expect(user.reload.status).to be_nil + it 'deletes the status' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status).to be_nil + end + end end - context 'when clear_status_after is given' do - it 'sets the clear_status_at column' do - freeze_time do + shared_examples '/user/status clear_status_after field' do + context 'when clear_status_after is valid', :freeze_time do + let(:params) { { emoji: 'smirk', message: 'hello world', clear_status_after: '3_hours' } } + + it 'sets the clear_status_at column' do expected_clear_status_at = 3.hours.from_now - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world', clear_status_after: '3_hours' } + set_user_status expect(response).to have_gitlab_http_status(:success) - expect(user.status.reload.clear_status_at).to be_within(1.minute).of(expected_clear_status_at) - expect(Time.parse(json_response["clear_status_at"])).to be_within(1.minute).of(expected_clear_status_at) + expect(user.status.clear_status_at).to be_like_time(expected_clear_status_at) + expect(Time.parse(json_response["clear_status_at"])).to be_like_time(expected_clear_status_at) end end - it 'unsets the clear_status_at column' do - user.create_status!(clear_status_at: 5.hours.ago) + context 'when clear_status_after is nil' do + let(:params) { { emoji: 'smirk', message: 'hello world', clear_status_after: nil } } + let(:request_user) { user_with_status } - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world', clear_status_after: nil } + it 'unsets the clear_status_at column' do + set_user_status - expect(response).to have_gitlab_http_status(:success) - expect(user.status.reload.clear_status_at).to be_nil + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status.clear_status_at).to be_nil + end end - it 'raises error when unknown status value is given' do - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world', clear_status_after: 'wrong' } + context 'when clear_status_after is invalid' do + let(:params) { { emoji: 'smirk', message: 'hello world', clear_status_after: 'invalid' } } - expect(response).to have_gitlab_http_status(:bad_request) + it 'raises error when unknown status value is given' do + set_user_status + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + + describe 'GET' do + let(:path) { '/user/status' } + + it_behaves_like 'rendering user status' + end + + describe 'PUT' do + subject(:set_user_status) { put api('/user/status', request_user), params: params } + + include_examples '/user/status successful response' + + include_examples '/user/status unsuccessful response' + + include_examples '/user/status passing nil for params' + + include_examples '/user/status clear_status_after field' + + context 'when passing empty params' do + let(:request_user) { user_with_status } + + it 'deletes the status' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status).to be_nil + end + end + + context 'when clear_status_after is not given' do + let(:params) { { emoji: 'smirk', message: 'hello world' } } + let(:request_user) { user_with_status } + + it 'unsets clear_status_at column' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status.clear_status_at).to be_nil + end + end + end + + describe 'PATCH' do + subject(:set_user_status) { patch api('/user/status', request_user), params: params } + + include_examples '/user/status successful response' + + include_examples '/user/status unsuccessful response' + + include_examples '/user/status passing nil for params' + + include_examples '/user/status clear_status_after field' + + context 'when passing empty params' do + let(:request_user) { user_with_status } + + it 'does not update the status' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status).to eq(user_status) + end + end + + context 'when clear_status_after is not given' do + let(:params) { { emoji: 'smirk', message: 'hello world' } } + let(:request_user) { user_with_status } + + it 'does not unset clear_status_at column' do + set_user_status + + expect(response).to have_gitlab_http_status(:success) + expect(user_with_status.status.clear_status_at).not_to be_nil + end end end end