diff --git a/config/routes/user.rb b/config/routes/user.rb index c453d4e15..3e5b72985 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -25,8 +25,8 @@ Zammad::Application.routes.draw do match api_path + '/users/:id', to: 'users#show', via: :get match api_path + '/users/history/:id', to: 'users#history', via: :get match api_path + '/users', to: 'users#create', via: :post - match api_path + '/users/:id', to: 'users#update', via: :put - match api_path + '/users/:id', to: 'users#destroy', via: :delete + match api_path + '/users/:id', to: 'users#update', via: :put, as: 'api_v1_update_user' + match api_path + '/users/:id', to: 'users#destroy', via: :delete, as: 'api_v1_delete_user' match api_path + '/users/image/:hash', to: 'users#image', via: :get match api_path + '/users/email_verify', to: 'users#email_verify', via: :post diff --git a/spec/controllers/users_controller_spec.rb b/spec/requests/user_spec.rb similarity index 93% rename from spec/controllers/users_controller_spec.rb rename to spec/requests/user_spec.rb index ca943e478..87c00c748 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/requests/user_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe UsersController, type: :controller do +RSpec.describe 'User endpoint', type: :request do let(:role_with_admin_user_permissions) do create(:role).tap do |role| @@ -16,7 +16,7 @@ RSpec.describe UsersController, type: :controller do end let(:admin_without_admin_user_permissions) { create(:user, roles: [role_without_admin_user_permissions]) } - describe 'POST #create' do + describe 'User creation' do let(:attributes) { attributes_params_for(:user) } @@ -25,7 +25,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(requester) expect do - post :create, params: attributes + post api_v1_users_path, params: attributes end.to not_change { User.count } @@ -51,7 +51,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(admin_with_admin_user_permissions) expect do - post :create, params: payload + post api_v1_users_path, params: payload end.to change { User.count }.by(1) @@ -64,7 +64,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(admin_without_admin_user_permissions) expect do - post :create, params: payload + post api_v1_users_path, params: payload end.to not_change { User.count } @@ -77,7 +77,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(requester) expect do - post :create, params: payload + post api_v1_users_path, params: payload end.to change { User.count }.by(1) @@ -128,7 +128,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(admin_with_admin_user_permissions) expect do - post :create, params: payload + post api_v1_users_path, params: payload end.to change { User.count }.by(1) @@ -141,7 +141,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(admin_without_admin_user_permissions) expect do - post :create, params: payload + post api_v1_users_path, params: payload end.to not_change { User.count } @@ -154,7 +154,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(requester) expect do - post :create, params: payload + post api_v1_users_path, params: payload end.to change { User.count }.by(1) @@ -187,13 +187,13 @@ RSpec.describe UsersController, type: :controller do end end - describe 'PUT #update' do + describe 'User update' do def authorized_update_request(requester:, requested:) authenticated_as(requester) expect do - put :update, params: cleaned_params_for(requested).merge(firstname: 'Changed') + put api_v1_update_user_path(requested), params: cleaned_params_for(requested).merge(firstname: 'Changed') end.to change { requested.reload.firstname } @@ -205,7 +205,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(requester) expect do - put :update, params: cleaned_params_for(requested).merge(firstname: 'Changed') + put api_v1_update_user_path(requested), params: cleaned_params_for(requested).merge(firstname: 'Changed') end.to not_change { requested.reload.attributes } @@ -375,7 +375,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(admin_with_admin_user_permissions) expect do - put :update, params: payload + put api_v1_update_user_path(requested), params: payload end.to change { value_of_attribute } @@ -386,7 +386,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(admin_without_admin_user_permissions) expect do - put :update, params: payload + put api_v1_update_user_path(requested), params: payload end.to not_change { value_of_attribute } @@ -402,7 +402,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(requester) expect do - put :update, params: payload + put api_v1_update_user_path(requested), params: payload end.to change { value_of_attribute } @@ -418,7 +418,7 @@ RSpec.describe UsersController, type: :controller do authenticated_as(requester) expect do - put :update, params: payload + put api_v1_update_user_path(requested), params: payload end.to not_change { value_of_attribute } @@ -510,12 +510,12 @@ RSpec.describe UsersController, type: :controller do end end - describe 'DELETE #destroy' do + describe 'User deletion' do def authorized_destroy_request(requester:, requested:) authenticated_as(requester) - delete :destroy, params: { id: requested.id } + delete api_v1_delete_user_path(requested) expect(response).to have_http_status(:success) expect(requested).not_to exist_in_database @@ -524,7 +524,7 @@ RSpec.describe UsersController, type: :controller do def unauthorized_destroy_request(requester:, requested:) authenticated_as(requester) - delete :destroy, params: { id: requested.id } + delete api_v1_delete_user_path(requested) expect(response).to have_http_status(:unauthorized) expect(requested).to exist_in_database diff --git a/spec/support/controller.rb b/spec/support/controller.rb deleted file mode 100644 index 28dfebebc..000000000 --- a/spec/support/controller.rb +++ /dev/null @@ -1,89 +0,0 @@ -module ZammadSpecSupportController - - # Authenticates all requests of the current example as the given user. - # - # @example - # authenticated_as(some_admin_user) - # - # @return nil - def authenticated_as(user) - session[:user_id] = user.id - end - - # Provides a Hash of attributes for the given FactoryBot - # factory parameters which can be used as the params payload. - # Note that the attributes are "cleaned" so no created_by_id etc. - # is present. - # - # @see FactoryBot#attributes_for - # - # @example - # attributes_params_for(:admin_user, email: 'custom@example.com') - # # => {firstname: 'Nicole', email: 'custom@example.com', ...} - # - # @return [Hash{Symbol => }] request cleaned attributes - def attributes_params_for(*args) - filter_unused_params(attributes_for(*args)) - end - - # Provides a Hash of attributes for the given Model instance which can - # be used as the params payload. - # Note that the attributes are "cleaned" so no created_by_id etc. - # is present. - # - # @param [Hash] instance An ActiveRecord instance - # - # @example - # cleaned_params_for(some_admin_user) - # # => {firstname: 'Nicole', email: 'admin@example.com', ...} - # - # @return [Hash{Symbol => }] request cleaned attributes - def cleaned_params_for(instance) - filter_unused_params(instance.attributes) - end - - # This is a self explaining internal method. - # - # @see ApplicationModel#filter_unused_params - def filter_unused_params(unfiltered) - # let's get private - ApplicationModel.send(:filter_unused_params, unfiltered) - end - - def self.included(base) - - # Execute in RSpec class context - base.class_exec do - - # This method disables the CSRF token validation for all controller - # examples. It's possible to re-enable the check by adding the - # meta tag `verify_csrf_token` to the needing example: - # - # @example - # it 'does stuff with verified CSRF token', verify_csrf_token: true do - # - before(:each) do |example| - if !example.metadata[:verify_csrf_token] - allow(controller).to receive(:verify_csrf_token).and_return(true) - end - end - - # This method disables the user device check for all controller - # examples. It's possible to re-enable the check by adding the - # meta tag `perform_user_device_check` to the needing example: - # - # @example - # it 'does stuff with user device check', perform_user_device_check: true do - # - before(:each) do |example| - if !example.metadata[:perform_user_device_check] - session[:user_device_updated_at] = Time.zone.now - end - end - end - end -end - -RSpec.configure do |config| - config.include ZammadSpecSupportController, type: :controller -end diff --git a/spec/support/request.rb b/spec/support/request.rb new file mode 100644 index 000000000..981a30434 --- /dev/null +++ b/spec/support/request.rb @@ -0,0 +1,108 @@ +module ZammadSpecSupportRequest + + # This ruby meta programming action creates the methods to perform: + # GET, POST, PATCH, PUT, DELETE and HEAD + # HTTP "requests". + # They overwrite the ones of `ActionDispatch::Integration::RequestHelpers` + # to add the headers set by #add_headers before + %i[get post patch put delete head].each do |method_id| + + define_method(method_id) do |path, **args| + headers = Hash(headers).merge(Hash(@headers)) + super(path, headers: headers, **args) + end + end + + # Adds one or more HTTP headers to all requests of the current example. + # + # @param [Hash{String => String}] headers Hash of HTTP headers + # + # @example + # add_headers('Eg Some X-Header' => 'Some value') + + # @example + # add_headers( + # 'Header 1' => 'Some value', + # 'Header 2' => 'Some value', + # ... + # ) + # + # @return [Hash] The current headers Hash + def add_headers(headers) + @headers = Hash(@headers).merge(headers) + end + + # Parses the response.body as JSON. + # + # @example + # json_response + + # @example + # json_response.is_a?(Array) + # + # @return [Array, Hash, ...] Parsed JSON structure as Ruby object + def json_response + JSON.parse(response.body) + end + + # Authenticates all requests of the current example as the given user. + # + # @example + # authenticated_as(some_admin_user) + # + # @return nil + def authenticated_as(user) + # mock authentication otherwise login won't + # if user has no password (which is expensive to create) + if user.password.nil? + allow(User).to receive(:authenticate).with(user.login, '').and_return(user) + end + + credentials = ActionController::HttpAuthentication::Basic.encode_credentials(user.login, user.password) + add_headers('Authorization' => credentials) + end + + # Provides a Hash of attributes for the given FactoryBot + # factory parameters which can be used as the params payload. + # Note that the attributes are "cleaned" so no created_by_id etc. + # is present. + # + # @see FactoryBot#attributes_for + # + # @example + # attributes_params_for(:admin_user, email: 'custom@example.com') + # # => {firstname: 'Nicole', email: 'custom@example.com', ...} + # + # @return [Hash{Symbol => }] request cleaned attributes + def attributes_params_for(*args) + filter_unused_params(attributes_for(*args)) + end + + # Provides a Hash of attributes for the given Model instance which can + # be used as the params payload. + # Note that the attributes are "cleaned" so no created_by_id etc. + # is present. + # + # @param [Hash] instance An ActiveRecord instance + # + # @example + # cleaned_params_for(some_admin_user) + # # => {firstname: 'Nicole', email: 'admin@example.com', ...} + # + # @return [Hash{Symbol => }] request cleaned attributes + def cleaned_params_for(instance) + filter_unused_params(instance.attributes) + end + + # This is a self explaining internal method. + # + # @see ApplicationModel#filter_unused_params + def filter_unused_params(unfiltered) + # let's get private + ApplicationModel.send(:filter_unused_params, unfiltered) + end +end + +RSpec.configure do |config| + config.include ZammadSpecSupportRequest, type: :request +end