diff --git a/.env.example b/.env.example index f54e52754..b22d6e169 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,7 @@ POSTGRES_USER=changeme POSTGRES_PASSWORD=changeme HYDRA_ADMIN_URL=http://host.docker.internal:9002 -HYDRA_SECRET= +HYDRA_ADMIN_API_KEY=test-key SMEE_TUNNEL=https://smee.io/MLq0n9kvAes2vydX diff --git a/.rubocop.yml b/.rubocop.yml index ae85abdef..0f1b4ec4f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -25,3 +25,5 @@ RSpec/DescribeClass: - "spec/graphql/queries/**" - "spec/graphql/mutations/**" +RSpec/MultipleMemoizedHelpers: + Max: 8 diff --git a/app/controllers/api/default_projects_controller.rb b/app/controllers/api/default_projects_controller.rb index 687c7a9ba..988e3dfa2 100644 --- a/app/controllers/api/default_projects_controller.rb +++ b/app/controllers/api/default_projects_controller.rb @@ -2,7 +2,7 @@ module Api class DefaultProjectsController < ApiController - before_action :require_oauth_user, only: %i[create] + before_action :authorize_user, only: %i[create] def show data = if params[:type] == 'html' diff --git a/app/controllers/api/projects/images_controller.rb b/app/controllers/api/projects/images_controller.rb index e9c849caf..cf2cd472d 100644 --- a/app/controllers/api/projects/images_controller.rb +++ b/app/controllers/api/projects/images_controller.rb @@ -3,7 +3,7 @@ module Api module Projects class ImagesController < ApiController - before_action :require_oauth_user + before_action :authorize_user def create @project = Project.find_by!(identifier: params[:project_id]) diff --git a/app/controllers/api/projects/remixes_controller.rb b/app/controllers/api/projects/remixes_controller.rb index 853f6be8b..a6ad6a9a2 100644 --- a/app/controllers/api/projects/remixes_controller.rb +++ b/app/controllers/api/projects/remixes_controller.rb @@ -3,11 +3,11 @@ module Api module Projects class RemixesController < ApiController - before_action :require_oauth_user + before_action :authorize_user def create result = Project::CreateRemix.call(params: remix_params, - user_id: oauth_user_id, + user_id: current_user, original_project: project) if result.success? diff --git a/app/controllers/api/projects_controller.rb b/app/controllers/api/projects_controller.rb index 08f1758da..1744910d1 100644 --- a/app/controllers/api/projects_controller.rb +++ b/app/controllers/api/projects_controller.rb @@ -2,7 +2,7 @@ module Api class ProjectsController < ApiController - before_action :require_oauth_user, only: %i[create update index destroy] + before_action :authorize_user, only: %i[create update index destroy] before_action :load_project, only: %i[show update destroy] before_action :load_projects, only: %i[index] after_action :pagination_link_header, only: [:index] diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 13ab4543a..dd7b4bf36 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ApiController < ActionController::API - include OauthUser + include Identifiable unless Rails.application.config.consider_all_requests_local rescue_from ActiveRecord::RecordNotFound, with: -> { notfound } @@ -10,13 +10,8 @@ class ApiController < ActionController::API private - def require_oauth_user - head :unauthorized unless oauth_user_id - end - - def current_user - # current_user is required by CanCanCan - oauth_user_id + def authorize_user + head :unauthorized unless current_user end def notfound diff --git a/app/controllers/concerns/authentication_concern.rb b/app/controllers/concerns/authentication_concern.rb deleted file mode 100644 index f20549cb4..000000000 --- a/app/controllers/concerns/authentication_concern.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require 'hydra_admin_api' - -module AuthenticationConcern - extend ActiveSupport::Concern - - def current_user_id - return @current_user_id if @current_user_id - - token = request.headers['Authorization'] - return nil unless token - - @current_user_id = HydraAdminApi.fetch_oauth_user_id(token:) - end -end diff --git a/app/controllers/concerns/identifiable.rb b/app/controllers/concerns/identifiable.rb new file mode 100644 index 000000000..c6711cd6c --- /dev/null +++ b/app/controllers/concerns/identifiable.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'hydra_admin_api' + +module Identifiable + extend ActiveSupport::Concern + + def identify_user + token = request.headers['Authorization'] + return nil unless token + + HydraAdminApi.fetch_oauth_user_id(token:) + end + + def current_user_id + @current_user_id ||= identify_user + end + + # current_user is required by CanCanCan + alias current_user current_user_id +end diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index dad3b9090..84cc9c841 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -1,11 +1,6 @@ # frozen_string_literal: true -class GraphqlController < ApplicationController - include AuthenticationConcern - include ActiveStorage::SetCurrent if Rails.env.development? - - skip_before_action :verify_authenticity_token - +class GraphqlController < ApiController # If accessing from outside this domain, nullify the session # This allows for outside API access while preventing CSRF attacks, # but you'll have to authenticate your user separately @@ -14,10 +9,6 @@ class GraphqlController < ApplicationController def execute result = EditorApiSchema.execute(query, variables:, context:, operation_name:) render json: result - rescue StandardError => e - raise e unless Rails.env.development? - - handle_error_in_development(e) end private @@ -38,29 +29,15 @@ def context def variables variables_param = params[:variables] - case params[:variables] + return {} if variables_param.blank? + + case variables_param when String - if variables_param.present? - JSON.parse(variables_param) || {} - else - {} - end - when Hash - variables_param + JSON.parse(variables_param) || {} when ActionController::Parameters variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables. - when nil - {} else raise ArgumentError, "Unexpected parameter: #{variables_param}" end end - - def handle_error_in_development(error) - logger.error error.message - logger.error error.backtrace.join("\n") - - render json: { errors: [{ message: error.message, backtrace: error.backtrace }], data: {} }, - status: :internal_server_error - end end diff --git a/app/helpers/oauth_user.rb b/app/helpers/oauth_user.rb deleted file mode 100644 index 66dd011bc..000000000 --- a/app/helpers/oauth_user.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module OauthUser - def oauth_user_id - @oauth_user_id ||= fetch_oauth_user_id - end - - private - - def fetch_oauth_user_id - return nil if request.headers['Authorization'].blank? - - return AUTH_USER_ID if BYPASS_AUTH - - json = hydra_request - json['sub'] - end - - def hydra_request - con = Faraday.new ENV.fetch('HYDRA_ADMIN_URL') - res = con.post do |req| - req.url '/oauth2/introspect' - req.headers['Content-Type'] = 'application/x-www-form-urlencoded' - req.headers['apiKey'] = ENV.fetch('HYDRA_SECRET') - req.body = { token: request.headers['Authorization'] } - end - JSON.parse(res.body) - end -end diff --git a/spec/lib/hydra_admin_api_spec.rb b/spec/lib/hydra_admin_api_spec.rb index 52436a6f1..50cadaacc 100644 --- a/spec/lib/hydra_admin_api_spec.rb +++ b/spec/lib/hydra_admin_api_spec.rb @@ -3,7 +3,6 @@ require 'rails_helper' require 'hydra_admin_api' -# rubocop:disable RSpec/MultipleMemoizedHelpers RSpec.describe HydraAdminApi do let(:hydra_admin_url) { 'https://hydra.com/admin' } let(:hydra_admin_api_key) { 'secret' } @@ -55,4 +54,3 @@ end end end -# rubocop:enable RSpec/MultipleMemoizedHelpers diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 8884db96d..44b3c4b94 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -83,7 +83,7 @@ config.include GraphqlQueryHelpers, type: :graphql_query config.include PhraseIdentifierMock - config.include OauthUserMock + config.include HydraAdminApiMock, type: :request end Shoulda::Matchers.configure do |config| diff --git a/spec/requests/graphql_spec.rb b/spec/requests/graphql_spec.rb index 867c1ecfb..5084b2bf6 100644 --- a/spec/requests/graphql_spec.rb +++ b/spec/requests/graphql_spec.rb @@ -3,38 +3,136 @@ require 'rails_helper' RSpec.describe 'POST /graphql' do - subject { response } + subject(:request) { post(graphql_path, as: :json, params:, headers:) } - let(:params) { {} } - let(:headers) { {} } - let(:json_response) { JSON.parse(response.body) } + let(:headers) { nil } + let(:params) { nil } - before { post graphql_path, params:, headers: } + before do + allow(EditorApiSchema).to receive(:execute).and_return({}) + end - it { is_expected.to be_ok } + shared_examples 'no variables are set' do + it 'sets an empty hash for the variables' do + request + expect(EditorApiSchema).to have_received(:execute).with(anything, hash_including(variables: {})) + end + end - it 'returns errors' do - expect(json_response['errors']).to be_a Array + shared_examples 'correctly sets the variables' do + it 'passes them correctly' do + request + expect(EditorApiSchema).to have_received(:execute).with(anything, hash_including(variables:)) + end end - context 'with a query' do - let(:params) { { query: } } - let(:query) { '' } + shared_examples 'an unidentified request' do + it 'sets the current_user_id as nil in the context' do + request + expect(EditorApiSchema).to have_received(:execute).with(anything, hash_including(context: hash_including(current_user_id: nil))) + end + end + + it 'returns OK' do + request + expect(response).to be_ok + end + + it_behaves_like 'an unidentified request' + + it_behaves_like 'no variables are set' - it { is_expected.to be_ok } + it 'returns a JSON response' do + request + expect(response.content_type).to start_with 'application/json;' + end + + context 'when an operationName is given' do + let(:params) { { operationName: operation_name } } + let(:operation_name) { 'testOperation' } - it 'returns errors' do - expect(json_response['errors']).to be_a Array + it 'sets the operationName correctly' do + request + expect(EditorApiSchema).to have_received(:execute).with(anything, hash_including(operation_name:)) end + end - context 'with a valid query' do - let(:query) { '{ node("xyz") }' } + context 'when an Authorization header is supplied' do + let(:headers) { { Authorization: token } } + let(:token) { '' } - it { is_expected.to be_ok } + it_behaves_like 'an unidentified request' - it 'returns data' do - expect(json_response.dig('data', 'node')).to be_nil + context 'with a token' do + let(:token) { 'valid-token' } + + context 'when the token is invalid' do + before do + stub_fetch_oauth_user_id(nil) + end + + it_behaves_like 'an unidentified request' end + + context 'when the token is valid' do + let(:current_user_id) { SecureRandom.uuid } + + before do + stub_fetch_oauth_user_id(current_user_id) + end + + it 'sets the current_user_id in the context' do + request + expect(EditorApiSchema).to have_received(:execute).with(anything, hash_including(context: hash_including(current_user_id:))) + end + end + end + end + + context 'when variables are given' do + let(:params) { { variables: } } + let(:variables) { { 'key' => 'value' } } + + it_behaves_like 'correctly sets the variables' + + context 'when they are a JSON string' do + subject(:request) { post(graphql_path, as: :url_encoded_form, params:) } + + let(:params) { { variables: variables.to_json } } + + it_behaves_like 'correctly sets the variables' + end + + context 'when the params are encoded as url_encoded_form' do + subject(:request) { post(graphql_path, as: :url_encoded_form, params:) } + + it_behaves_like 'correctly sets the variables' + end + + context 'when variables are set to null' do + let(:variables) { 'null' } + + it_behaves_like 'no variables are set' + end + + context 'when variables are an empty string' do + let(:variables) { '' } + + it_behaves_like 'no variables are set' + end + end + + context 'when a query is given' do + let(:query) { '{ query { hello { id } }' } + let(:params) { { query: } } + + before do + allow(EditorApiSchema).to receive(:execute).and_return({}) + end + + it 'passes them correctly' do + request + expect(EditorApiSchema).to have_received(:execute).with(query, anything) end end end diff --git a/spec/requests/projects/create_spec.rb b/spec/requests/projects/create_spec.rb index d3acd224b..43ba6bd89 100644 --- a/spec/requests/projects/create_spec.rb +++ b/spec/requests/projects/create_spec.rb @@ -6,10 +6,12 @@ let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' } let(:project) { create(:project, user_id:) } - describe 'create' do - context 'when auth is correct' do + context 'when auth is correct' do + let(:headers) { { Authorization: 'dummy-token' } } + + context 'when creating project is successful' do before do - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(user_id) response = OperationResponse.new response[:project] = project @@ -17,14 +19,15 @@ end it 'returns success' do - post '/api/projects' + post '/api/projects', headers: headers + expect(response).to have_http_status(:ok) end end context 'when creating project fails' do before do - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(user_id) response = OperationResponse.new response[:error] = 'Error creating project' @@ -32,16 +35,18 @@ end it 'returns error' do - post '/api/projects' + post '/api/projects', headers: headers + expect(response).to have_http_status(:internal_server_error) end end + end - context 'when no auth user' do - it 'returns unauthorized' do - post '/api/projects' - expect(response).to have_http_status(:unauthorized) - end + context 'when no token is given' do + it 'returns unauthorized' do + post '/api/projects' + + expect(response).to have_http_status(:unauthorized) end end end diff --git a/spec/requests/projects/destroy_spec.rb b/spec/requests/projects/destroy_spec.rb index 5bda9344f..958b7db73 100644 --- a/spec/requests/projects/destroy_spec.rb +++ b/spec/requests/projects/destroy_spec.rb @@ -7,20 +7,22 @@ context 'when user is logged in' do let!(:project) { create(:project, user_id:) } + let(:headers) { { Authorization: 'dummy-token' } } before do - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(user_id) end context 'when deleting a project the user owns' do it 'returns success' do - delete "/api/projects/#{project.identifier}" + delete "/api/projects/#{project.identifier}", headers: headers + expect(response).to have_http_status(:ok) end it "deletes user's project" do expect do - delete "/api/projects/#{project.identifier}" + delete "/api/projects/#{project.identifier}", headers: end.to change(Project, :count).by(-1) end end @@ -29,15 +31,17 @@ let(:non_owned_project) { create(:project) } it 'returns forbidden' do - delete "/api/projects/#{non_owned_project.identifier}" + delete "/api/projects/#{non_owned_project.identifier}", headers: headers + expect(response).to have_http_status(:forbidden) end end end - context 'when no user' do + context 'when no token is given' do it 'returns unauthorized' do delete '/api/projects/project-identifier' + expect(response).to have_http_status(:unauthorized) end end diff --git a/spec/requests/projects/images_spec.rb b/spec/requests/projects/images_spec.rb index 0369f0e1f..795c0e587 100644 --- a/spec/requests/projects/images_spec.rb +++ b/spec/requests/projects/images_spec.rb @@ -20,47 +20,51 @@ describe 'create' do context 'when auth is correct' do + let(:headers) { { Authorization: 'dummy-token' } } + before do - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(project.user_id) end it 'attaches file to project' do - expect { post "/api/projects/#{project.identifier}/images", params: }.to change { project.images.count }.by(1) + expect { post "/api/projects/#{project.identifier}/images", params:, headers: }.to change { project.images.count }.by(1) end it 'returns file list' do - post "/api/projects/#{project.identifier}/images", params: params + post "/api/projects/#{project.identifier}/images", params: params, headers: headers expect(response.body).to eq(expected_json) end it 'returns success response' do - post "/api/projects/#{project.identifier}/images", params: params + post "/api/projects/#{project.identifier}/images", params: params, headers: headers expect(response).to have_http_status(:ok) end it 'returns 404 response if invalid project' do - post '/api/projects/no-such-project/images' + post '/api/projects/no-such-project/images', headers: headers expect(response).to have_http_status(:not_found) end end context 'when authed user is not creator' do + let(:headers) { { Authorization: 'dummy-token' } } + before do - mock_oauth_user + stub_fetch_oauth_user_id(SecureRandom.uuid) end it 'returns forbidden response' do - post "/api/projects/#{project.identifier}/images", params: params + post "/api/projects/#{project.identifier}/images", params: params, headers: headers expect(response).to have_http_status(:forbidden) end end - context 'when auth is invalid' do + context 'when auth token is missing' do it 'returns unauthorized' do - post "/api/projects/#{project.identifier}/images" + post "/api/projects/#{project.identifier}/images", headers: headers expect(response).to have_http_status(:unauthorized) end diff --git a/spec/requests/projects/index_spec.rb b/spec/requests/projects/index_spec.rb index 4d050fedf..9c2051da4 100644 --- a/spec/requests/projects/index_spec.rb +++ b/spec/requests/projects/index_spec.rb @@ -5,6 +5,7 @@ RSpec.describe 'Project index requests' do include PaginationLinksMock + let(:headers) { { Authorization: 'dummy-token' } } let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' } let(:project_keys) { %w[identifier project_type name user_id updated_at] } @@ -16,28 +17,28 @@ before do # create non user projects create_list(:project, 2) - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(user_id) end it 'returns success response' do - get '/api/projects' + get '/api/projects', headers: headers expect(response).to have_http_status(:ok) end it 'returns correct number of projects' do - get '/api/projects' + get '/api/projects', headers: headers returned = JSON.parse(response.body) expect(returned.length).to eq(2) end it 'returns users projects' do - get '/api/projects' + get '/api/projects', headers: headers returned = JSON.parse(response.body) expect(returned.all? { |proj| proj['user_id'] == user_id }).to be(true) end it 'returns all keys in response' do - get '/api/projects' + get '/api/projects', headers: headers returned = JSON.parse(response.body) returned.each { |project| expect(project.keys).to eq(project_keys) } end @@ -46,17 +47,17 @@ context 'when the projects index has pagination' do before do create_list(:project, 10, user_id:) - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(user_id) end it 'returns the default number of projects on the first page' do - get '/api/projects' + get '/api/projects', headers: headers returned = JSON.parse(response.body) expect(returned.length).to eq(8) end it 'returns the next set of projects on the next page' do - get '/api/projects?page=2' + get '/api/projects?page=2', headers: headers returned = JSON.parse(response.body) expect(returned.length).to eq(4) end @@ -66,7 +67,7 @@ next_link = page_links(2, 'next') expected_link_header = [last_link, next_link].join(', ') - get '/api/projects' + get '/api/projects', headers: headers expect(response.headers['Link']).to eq expected_link_header end @@ -75,12 +76,12 @@ prev_link = page_links(1, 'prev') expected_link_header = [first_link, prev_link].join(', ') - get '/api/projects?page=2' + get '/api/projects?page=2', headers: headers expect(response.headers['Link']).to eq expected_link_header end end - context 'when no user' do + context 'when no token is given' do it 'returns unauthorized' do get '/api/projects' expect(response).to have_http_status(:unauthorized) diff --git a/spec/requests/projects/remix_spec.rb b/spec/requests/projects/remix_spec.rb index 3776e0175..fd3b8a290 100644 --- a/spec/requests/projects/remix_spec.rb +++ b/spec/requests/projects/remix_spec.rb @@ -13,57 +13,57 @@ } end - describe 'create' do + before do + mock_phrase_generation + end + + context 'when auth is correct' do + let(:headers) { { Authorization: 'dummy-token' } } + before do - mock_phrase_generation + stub_fetch_oauth_user_id(user_id) end - context 'when auth is correct' do - before do - mock_oauth_user(user_id) - end - - it 'returns success response' do - post "/api/projects/#{original_project.identifier}/remix", params: { project: project_params } + it 'returns success response' do + post "/api/projects/#{original_project.identifier}/remix", params: { project: project_params }, headers: headers - expect(response).to have_http_status(:ok) - end + expect(response).to have_http_status(:ok) + end - it 'returns 404 response if invalid project' do - project_params[:identifier] = 'no-such-project' - post '/api/projects/no-such-project/remix', params: { project: project_params } + it 'returns 404 response if invalid project' do + project_params[:identifier] = 'no-such-project' + post '/api/projects/no-such-project/remix', params: { project: project_params }, headers: headers - expect(response).to have_http_status(:not_found) - end + expect(response).to have_http_status(:not_found) end context 'when project can not be saved' do before do - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(user_id) error_response = OperationResponse.new error_response[:error] = 'Something went wrong' allow(Project::CreateRemix).to receive(:call).and_return(error_response) end it 'returns 400' do - post "/api/projects/#{original_project.identifier}/remix", params: { project: project_params } + post "/api/projects/#{original_project.identifier}/remix", params: { project: project_params }, headers: headers expect(response).to have_http_status(:bad_request) end it 'returns error message' do - post "/api/projects/#{original_project.identifier}/remix", params: { project: project_params } + post "/api/projects/#{original_project.identifier}/remix", params: { project: project_params }, headers: headers expect(response.body).to eq({ error: 'Something went wrong' }.to_json) end end + end - context 'when auth is invalid' do - it 'returns unauthorized' do - post "/api/projects/#{original_project.identifier}/remix" + context 'when auth is invalid' do + it 'returns unauthorized' do + post "/api/projects/#{original_project.identifier}/remix" - expect(response).to have_http_status(:unauthorized) - end + expect(response).to have_http_status(:unauthorized) end end end diff --git a/spec/requests/projects/show_spec.rb b/spec/requests/projects/show_spec.rb index d7d525ca5..dc65dc6b2 100644 --- a/spec/requests/projects/show_spec.rb +++ b/spec/requests/projects/show_spec.rb @@ -14,26 +14,29 @@ image_list: [] }.to_json end + let(:headers) { {} } context 'when user is logged in' do + let(:headers) { { Authorization: 'dummy-token' } } + before do - mock_oauth_user(project.user_id) + stub_fetch_oauth_user_id(project.user_id) end context 'when loading own project' do it 'returns success response' do - get "/api/projects/#{project.identifier}" + get "/api/projects/#{project.identifier}", headers: headers expect(response).to have_http_status(:ok) end it 'returns json' do - get "/api/projects/#{project.identifier}" + get "/api/projects/#{project.identifier}", headers: headers expect(response.content_type).to eq('application/json; charset=utf-8') end it 'returns the project json' do - get "/api/projects/#{project.identifier}" + get "/api/projects/#{project.identifier}", headers: headers expect(response.body).to eq(project_json) end end @@ -52,13 +55,13 @@ end it 'returns forbidden response' do - get "/api/projects/#{another_project.identifier}" + get "/api/projects/#{another_project.identifier}", headers: headers expect(response).to have_http_status(:forbidden) end it 'does not return the project json' do - get "/api/projects/#{another_project.identifier}" + get "/api/projects/#{another_project.identifier}", headers: headers expect(response.body).not_to include(another_project_json) end end @@ -79,36 +82,36 @@ end it 'returns success response' do - get "/api/projects/#{starter_project.identifier}" + get "/api/projects/#{starter_project.identifier}", headers: headers expect(response).to have_http_status(:ok) end it 'returns json' do - get "/api/projects/#{starter_project.identifier}" + get "/api/projects/#{starter_project.identifier}", headers: headers expect(response.content_type).to eq('application/json; charset=utf-8') end it 'returns the project json' do - get "/api/projects/#{starter_project.identifier}" + get "/api/projects/#{starter_project.identifier}", headers: headers expect(response.body).to eq(starter_project_json) end it 'returns 404 response if invalid project' do - get '/api/projects/no-such-project' + get '/api/projects/no-such-project', headers: headers expect(response).to have_http_status(:not_found) end end context 'when loading an owned project' do it 'returns forbidden response' do - get "/api/projects/#{project.identifier}" + get "/api/projects/#{project.identifier}", headers: headers expect(response).to have_http_status(:forbidden) end it 'does not return the project json' do - get "/api/projects/#{project.identifier}" + get "/api/projects/#{project.identifier}", headers: headers expect(response.body).not_to include(project_json) end end diff --git a/spec/requests/projects/update_spec.rb b/spec/requests/projects/update_spec.rb index 835ab070e..7b0b14966 100644 --- a/spec/requests/projects/update_spec.rb +++ b/spec/requests/projects/update_spec.rb @@ -3,11 +3,12 @@ require 'rails_helper' RSpec.describe 'Project update requests' do + let(:headers) { { Authorization: 'dummy-token' } } let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' } let(:project) { create(:project, user_id:) } context 'when authed user is project creator' do - let(:project) { create(:project, :with_default_component, user_id:) } + let(:project) { create(:project, :with_default_component) } let!(:component) { create(:component, project:) } let(:default_component_params) do project.components.first.attributes.symbolize_keys.slice( @@ -27,16 +28,16 @@ end before do - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(project.user_id) end it 'returns success response' do - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(response).to have_http_status(:ok) end it 'returns updated project json' do - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(response.body).to include('updated component content') end @@ -44,7 +45,7 @@ mock_response = instance_double(OperationResponse) allow(mock_response).to receive(:success?).and_return(true) allow(Project::Update).to receive(:call).and_return(mock_response) - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(Project::Update).to have_received(:call) end @@ -52,17 +53,17 @@ let(:params) { { project: { name: 'updated project name' } } } it 'returns success response' do - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(response).to have_http_status(:ok) end it 'returns json with updated project properties' do - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(response.body).to include('updated project name') end it 'returns json with previous project components' do - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(response.body).to include(project.components.first.attributes[:content].to_s) end end @@ -71,7 +72,7 @@ let(:params) { { project: { components: [] } } } it 'returns error response' do - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(response).to have_http_status(:bad_request) end end @@ -82,18 +83,24 @@ let(:params) { { project: { components: [] } } } before do - mock_oauth_user(user_id) + stub_fetch_oauth_user_id(SecureRandom.uuid) end it 'returns forbidden response' do - put "/api/projects/#{project.identifier}", params: params + put "/api/projects/#{project.identifier}", params: params, headers: headers expect(response).to have_http_status(:forbidden) end end - context 'when auth is invalid' do + context 'when auth token is invalid' do + let(:project) { create(:project) } + + before do + allow(HydraAdminApi).to receive(:fetch_oauth_user_id).and_return(nil) + end + it 'returns unauthorized' do - put "/api/projects/#{project.identifier}" + put "/api/projects/#{project.identifier}", headers: headers expect(response).to have_http_status(:unauthorized) end diff --git a/spec/support/hydra_admin_api_mock.rb b/spec/support/hydra_admin_api_mock.rb new file mode 100644 index 000000000..6b17f519c --- /dev/null +++ b/spec/support/hydra_admin_api_mock.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'hydra_admin_api' + +module HydraAdminApiMock + def stub_fetch_oauth_user_id(user_id) + allow(HydraAdminApi).to receive(:fetch_oauth_user_id).and_return(user_id) + end +end diff --git a/spec/support/oauth_user_mock.rb b/spec/support/oauth_user_mock.rb deleted file mode 100644 index 59234bc94..000000000 --- a/spec/support/oauth_user_mock.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module OauthUserMock - def mock_oauth_user(user_id = nil) - user_id ||= SecureRandom.uuid - - # rubocop:disable RSpec/AnyInstance - allow_any_instance_of(OauthUser).to receive(:oauth_user_id).and_return(user_id) - # rubocop:enable RSpec/AnyInstance - end -end