diff --git a/Gemfile b/Gemfile index 6e65722acb..7c757f4498 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'http://rubygems.org' gem 'addressable' -gem 'activesupport' +gem 'railties' gem 'rake' gem 'eventmachine', '~> 1.0.0' gem 'fog' @@ -57,6 +57,7 @@ group :test do gem 'rspec_api_documentation', git: 'https://github.com/zipmark/rspec_api_documentation.git' gem 'rspec-collection_matchers' gem 'rspec-its' + gem 'rspec-rails' gem 'rubocop' gem 'astrolabe' gem 'timecop' diff --git a/app/controllers/base/base_controller.rb b/app/controllers/base/base_controller.rb index e0d928dffa..779a5dbc7d 100644 --- a/app/controllers/base/base_controller.rb +++ b/app/controllers/base/base_controller.rb @@ -202,18 +202,6 @@ def bad_request!(message) raise VCAP::Errors::ApiError.new_from_details('MessageParseError', message) end - def invalid_param!(message) - raise VCAP::Errors::ApiError.new_from_details('BadQueryParameter', message) - end - - def unprocessable!(message) - raise VCAP::Errors::ApiError.new_from_details('UnprocessableEntity', message) - end - - def unauthorized! - raise VCAP::Errors::ApiError.new_from_details('NotAuthorized') - end - attr_reader :config, :logger, :env, :params, :body, :request_attrs class << self diff --git a/app/controllers/base/base_controller_v3.rb b/app/controllers/base/base_controller_v3.rb new file mode 100644 index 0000000000..de42555f7a --- /dev/null +++ b/app/controllers/base/base_controller_v3.rb @@ -0,0 +1,77 @@ +require 'rails' +require 'action_controller' + +module VCAP + module CloudController + module RestController + class ApplicationCC < ::Rails::Application + config.middleware.delete 'ActionDispatch::Session::CookieStore' + config.middleware.delete 'ActionDispatch::Cookies' + config.middleware.delete 'ActionDispatch::Flash' + end + + module V3ErrorsHelper + def invalid_param!(message) + raise VCAP::Errors::ApiError.new_from_details('BadQueryParameter', message) + end + + def unprocessable!(message) + raise VCAP::Errors::ApiError.new_from_details('UnprocessableEntity', message) + end + + def unauthorized! + raise VCAP::Errors::ApiError.new_from_details('NotAuthorized') + end + end + + class BaseControllerV3 < ::ActionController::Base + include VCAP::CloudController + include VCAP::CloudController::RestController::V3ErrorsHelper + + wrap_parameters :body, format: [:json] + + before_filter :set_current_user + before_filter :check_read_permissions!, only: [:index, :show] + before_filter :check_write_permissions!, except: [:index, :show] + + def query_params + request.query_parameters.with_indifferent_access + end + + # include VCAP::Errors + # include VCAP::RestAPI + # include Messages + # include Routes + # extend Forwardable + + def check_read_permissions! + read_scope = SecurityContext.scopes.include?('cloud_controller.read') + raise VCAP::Errors::ApiError.new_from_details('NotAuthorized') if !roles.admin? && !read_scope + end + + def check_write_permissions! + write_scope = SecurityContext.scopes.include?('cloud_controller.write') + raise VCAP::Errors::ApiError.new_from_details('NotAuthorized') if !roles.admin? && !write_scope + end + + def set_current_user + auth_token = request.headers['HTTP_AUTHORIZATION'] + token_decoder = VCAP::UaaTokenDecoder.new(Config.config[:uaa]) + VCAP::CloudController::Security::SecurityContextConfigurer.new(token_decoder).configure(auth_token) + end + + def roles + VCAP::CloudController::SecurityContext.roles + end + + def current_user + VCAP::CloudController::SecurityContext.current_user + end + + def current_user_email + VCAP::CloudController::SecurityContext.current_user_email + end + end + end + end +end diff --git a/app/controllers/v3/processes_controller.rb b/app/controllers/v3/processes_controller.rb index b519e90835..2eb8626fd7 100644 --- a/app/controllers/v3/processes_controller.rb +++ b/app/controllers/v3/processes_controller.rb @@ -7,135 +7,111 @@ require 'actions/process_update' require 'messages/process_update_message' require 'messages/processes_list_message' +require 'controllers/base/base_controller_v3' -module VCAP::CloudController - class ProcessesController < RestController::BaseController - def self.dependencies - [:process_presenter, :index_stopper] - end - - get '/v3/processes', :list - def list - check_read_permissions! - - message = ProcessesListMessage.from_params(params) - invalid_param!(message.errors.full_messages) unless message.valid? - - pagination_options = PaginationOptions.from_params(params) - invalid_param!(pagination_options.errors.full_messages) unless pagination_options.valid? - invalid_param!("Unknown query param(s) '#{params.keys.join("', '")}'") if params.any? - - if roles.admin? - paginated_result = ProcessListFetcher.new.fetch_all(pagination_options) - else - space_guids = membership.space_guids_for_roles([Membership::SPACE_DEVELOPER, Membership::SPACE_MANAGER, Membership::SPACE_AUDITOR, Membership::ORG_MANAGER]) - paginated_result = ProcessListFetcher.new.fetch(pagination_options, space_guids) - end - - [HTTP::OK, @process_presenter.present_json_list(paginated_result, '/v3/processes')] - end - - get '/v3/processes/:guid', :show - def show(guid) - check_read_permissions! +class ProcessesController < VCAP::CloudController::RestController::BaseControllerV3 + def index + message = ProcessesListMessage.from_params(query_params) + invalid_param!(message.errors.full_messages) unless message.valid? - process = ProcessModel.where(guid: guid).eager(:space, :organization).all.first + pagination_options = PaginationOptions.from_params(query_params) + invalid_param!(pagination_options.errors.full_messages) unless pagination_options.valid? - not_found! if process.nil? || !can_read?(process.space.guid, process.organization.guid) - - [HTTP::OK, @process_presenter.present_json(process)] + if roles.admin? + paginated_result = ProcessListFetcher.new.fetch_all(pagination_options) + else + space_guids = membership.space_guids_for_roles([Membership::SPACE_DEVELOPER, Membership::SPACE_MANAGER, Membership::SPACE_AUDITOR, Membership::ORG_MANAGER]) + paginated_result = ProcessListFetcher.new.fetch(pagination_options, space_guids) end - patch '/v3/processes/:guid', :update - def update(guid) - check_write_permissions! - - request = parse_and_validate_json(body) - message = ProcessUpdateMessage.create_from_http_request(request) - unprocessable!(message.errors.full_messages) unless message.valid? - - process = ProcessModel.where(guid: guid).eager(:space, :organization).all.first - not_found! if process.nil? || !can_read?(process.space.guid, process.organization.guid) - unauthorized! if !can_update?(process.space.guid) - - ProcessUpdate.new(current_user, current_user_email).update(process, message) + render status: :ok, json: process_presenter.present_json_list(paginated_result, '/v3/processes') + end - [HTTP::OK, @process_presenter.present_json(process)] - rescue ProcessUpdate::InvalidProcess => e - unprocessable!(e.message) - end + def show + guid = params[:guid] + process = ProcessModel.where(guid: guid).eager(:space, :organization).all.first + not_found! if process.nil? || !can_read?(process.space.guid, process.organization.guid) + render status: :ok, json: process_presenter.present_json(process) + end - delete '/v3/processes/:guid/instances/:index', :terminate - def terminate(process_guid, process_index) - check_write_permissions! + def update + message = ProcessUpdateMessage.create_from_http_request(params[:body]) + unprocessable!(message.errors.full_messages) unless message.valid? - process = ProcessModel.where(guid: process_guid).eager(:space, :organization).all.first - not_found! if process.nil? || !can_read?(process.space.guid, process.organization.guid) - unauthorized! unless can_terminate?(process.space.guid) + process = ProcessModel.where(guid: guid).eager(:space, :organization).all.first + not_found! if process.nil? || !can_read?(process.space.guid, process.organization.guid) + unauthorized! if !can_update?(process.space.guid) - instance_not_found! unless process_index.to_i < process.instances && process_index.to_i >= 0 + ProcessUpdate.new(current_user, current_user_email).update(process, message) - index_stopper.stop_index(process, process_index.to_i) + render status: :ok, json: process_presenter.present_json(process) + rescue ProcessUpdate::InvalidProcess => e + unprocessable!(e.message) + end - [HTTP::NO_CONTENT, nil] - end + def terminate + process = ProcessModel.where(guid: process_guid).eager(:space, :organization).all.first + not_found! if process.nil? || !can_read?(process.space.guid, process.organization.guid) + unauthorized! unless can_terminate?(process.space.guid) - put '/v3/processes/:guid/scale', :scale - def scale(guid) - check_write_permissions! + index = params[:index].to_i + instance_not_found! unless index < process.instances && index >= 0 - FeatureFlag.raise_unless_enabled!('app_scaling') unless roles.admin? + index_stopper.stop_index(process, index) + + head :no_content + end - request = parse_and_validate_json(body) - message = ProcessScaleMessage.create_from_http_request(request) - unprocessable!(message.errors.full_messages) if message.invalid? + def scale + FeatureFlag.raise_unless_enabled!('app_scaling') unless roles.admin? - process, space, org = ProcessScaleFetcher.new.fetch(guid) - not_found! if process.nil? || !can_read?(space.guid, org.guid) - unauthorized! if !can_scale?(space.guid) + message = ProcessScaleMessage.create_from_http_request(params[:body]) + unprocessable!(message.errors.full_messages) if message.invalid? - ProcessScale.new(current_user, current_user_email).scale(process, message) + process, space, org = ProcessScaleFetcher.new.fetch(params[:guid]) + not_found! if process.nil? || !can_read?(space.guid, org.guid) + unauthorized! if !can_scale?(space.guid) - [HTTP::OK, @process_presenter.present_json(process)] - rescue ProcessScale::InvalidProcess => e - unprocessable!(e.message) - end + ProcessScale.new(current_user, current_user_email).scale(process, message) - protected + render status: :ok, json: process_presenter.present_json(process) + rescue ProcessScale::InvalidProcess => e + unprocessable!(e.message) + end - attr_reader :index_stopper + private - def inject_dependencies(dependencies) - @process_presenter = dependencies[:process_presenter] - @index_stopper = dependencies.fetch(:index_stopper) - end + def process_presenter + ProcessPresenter.new + end - private + def index_stopper + CloudController::DependencyLocator.instance.index_stopper + end - def membership - @membership ||= Membership.new(current_user) - end + def membership + @membership ||= Membership.new(current_user) + end - def can_read?(space_guid, org_guid) - roles.admin? || - membership.has_any_roles?([Membership::SPACE_DEVELOPER, - Membership::SPACE_MANAGER, - Membership::SPACE_AUDITOR, - Membership::ORG_MANAGER], space_guid, org_guid) - end + def can_read?(space_guid, org_guid) + roles.admin? || + membership.has_any_roles?([Membership::SPACE_DEVELOPER, + Membership::SPACE_MANAGER, + Membership::SPACE_AUDITOR, + Membership::ORG_MANAGER], space_guid, org_guid) + end - def can_update?(space_guid) - roles.admin? || membership.has_any_roles?([Membership::SPACE_DEVELOPER], space_guid) - end - alias_method :can_terminate?, :can_update? - alias_method :can_scale?, :can_update? + def can_update?(space_guid) + roles.admin? || membership.has_any_roles?([Membership::SPACE_DEVELOPER], space_guid) + end + alias_method :can_terminate?, :can_update? + alias_method :can_scale?, :can_update? - def instance_not_found! - raise VCAP::Errors::ApiError.new_from_details('ResourceNotFound', 'Instance not found') - end + def instance_not_found! + raise VCAP::Errors::ApiError.new_from_details('ResourceNotFound', 'Instance not found') + end - def not_found! - raise VCAP::Errors::ApiError.new_from_details('ResourceNotFound', 'Process not found') - end + def not_found! + raise VCAP::Errors::ApiError.new_from_details('ResourceNotFound', 'Process not found') end end diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000000..7cfed8e15b --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,35 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000000..a008b61074 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,76 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like + # NGINX, varnish or squid. + # config.action_dispatch.rack_cache = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000000..9b998860a6 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,30 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Randomize the order test cases are executed. + config.active_support.test_order = :random + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr +end diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..27b1f516eb --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,15 @@ +Rails.application.routes.draw do + resource :echo + + get '/processes', to: 'processes#index' + get '/processes/:guid', to: 'processes#show' + patch '/processes/:guid', to: 'processes#update', defaults: { format: :json } + delete '/processes/:guid/instances/:index', to: 'processes#terminate', defaults: { format: :json } + put '/processes/:guid/scale', to: 'processes#scale', defaults: { format: :json } + + get '/v3/processes', to: 'processes#index' + get '/v3/processes/:guid', to: 'processes#show' + patch '/v3/processes/:guid', to: 'processes#update', defaults: { format: :json } + delete '/v3/processes/:guid/instances/:index', to: 'processes#terminate', defaults: { format: :json } + put '/v3/processes/:guid/scale', to: 'processes#scale', defaults: { format: :json } +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 0000000000..b1717c2189 --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,14 @@ +development: + secret_key_base: super_long_secret_key_for_development + active_merchant_login: 896667 + active_merchant_password: supersecretpassword888 + +test: + secret_key_base: super_long_secret_key_for_test + active_merchant_login: 896667 + active_merchant_password: supersecretpassword888 + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + active_merchant_login: <%= ENV["AM_LOGIN"] %> + active_merchant_password: <%= ENV["AM_PASSWORD"] %> diff --git a/lib/cloud_controller.rb b/lib/cloud_controller.rb index e6585c45d9..57267b55a4 100644 --- a/lib/cloud_controller.rb +++ b/lib/cloud_controller.rb @@ -1,3 +1,10 @@ +require 'vcap/common' + +require 'rails' +require 'action_controller/railtie' + +require 'controllers/base/base_controller_v3.rb' + require 'sinatra' require 'sequel' require 'thin' @@ -8,7 +15,6 @@ require 'eventmachine/schedule_sync' -require 'vcap/common' require 'vcap/errors/details' require 'vcap/errors/api_error' require 'uaa/token_coder' @@ -40,6 +46,8 @@ module VCAP::CloudController; end require 'controllers/base/front_controller' +Rails.application.initialize! if ENV['RAILS_ENV'] == 'test' + require 'cloud_controller/config' require 'cloud_controller/db' require 'cloud_controller/runner' diff --git a/lib/cloud_controller/dependency_locator.rb b/lib/cloud_controller/dependency_locator.rb index ba779b7ca1..cdfa8713dc 100644 --- a/lib/cloud_controller/dependency_locator.rb +++ b/lib/cloud_controller/dependency_locator.rb @@ -172,10 +172,6 @@ def app_repository AppRepository.new end - def process_presenter - ProcessPresenter.new - end - def app_presenter AppPresenter.new end diff --git a/lib/cloud_controller/rack_app_builder.rb b/lib/cloud_controller/rack_app_builder.rb index 151923edf6..b76264d8eb 100644 --- a/lib/cloud_controller/rack_app_builder.rb +++ b/lib/cloud_controller/rack_app_builder.rb @@ -4,6 +4,9 @@ def build(config, request_metrics) token_decoder = VCAP::UaaTokenDecoder.new(config[:uaa]) logger = access_log(config) + + Rails.application.initialize! + Rack::Builder.new do if logger use Rack::CommonLogger, logger @@ -17,6 +20,10 @@ def build(config, request_metrics) map '/' do run FrontController.new(config, token_decoder, request_metrics) end + + map '/v3' do + run Rails.application.app + end end end diff --git a/spec/api/documentation/v3/processes_api_spec.rb b/spec/api/documentation/v3/processes_api_spec.rb index 090df9ad67..92fd4fe8fa 100644 --- a/spec/api/documentation/v3/processes_api_spec.rb +++ b/spec/api/documentation/v3/processes_api_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require 'rails_helper' require 'awesome_print' require 'rspec_api_documentation/dsl' @@ -139,6 +139,8 @@ def do_request_with_error_handling process.space.add_developer user end + header 'Content-Type', 'application/json' + body_parameter :command, 'Start command for process' let(:command) { 'X' } @@ -180,6 +182,7 @@ def do_request_with_error_handling body_parameter :instances, 'Number of instances' body_parameter :memory_in_mb, 'The memory in mb allocated per instance' body_parameter :disk_in_mb, 'The disk in mb allocated per instance' + header 'Content-Type', 'application/json' let(:instances) { 3 } let(:memory_in_mb) { 100 } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000000..dd6d120adf --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,102 @@ +ENV['RAILS_ENV'] ||= 'test' + +$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) +$LOAD_PATH.unshift(File.expand_path('../../app', __FILE__)) + +require 'rubygems' +require 'bundler' +require 'bundler/setup' + +if ENV['CODECLIMATE_REPO_TOKEN'] && ENV['COVERAGE'] + require 'codeclimate-test-reporter' + CodeClimate::TestReporter.start +end + +require 'fakefs/safe' +require 'machinist/sequel' +require 'machinist/object' +require 'timecop' + +require 'steno' +require 'cf_message_bus/mock_message_bus' + +require 'cloud_controller' +require 'allowy/rspec' + +require 'posix/spawn' + +require 'rspec_api_documentation' +require 'services' + +require 'support/bootstrap/spec_bootstrap' +require 'rspec/collection_matchers' +require 'rspec/its' +require 'rspec/rails' + +VCAP::CloudController::SpecBootstrap.init + +Dir[File.expand_path('support/**/*.rb', File.dirname(__FILE__))].each { |file| require file } + +RSpec.configure do |rspec_config| + rspec_config.expect_with(:rspec) { |config| config.syntax = :expect } + # rspec_config.include Rack::Test::Methods + rspec_config.include ModelCreation + + rspec_config.include ServiceBrokerHelpers + rspec_config.include ControllerHelpers, type: :controller_helpers, file_path: EscapedPath.join(%w(spec unit controllers)) + rspec_config.include ControllerHelpers, type: :api + rspec_config.include ControllerHelpers, file_path: EscapedPath.join(%w(spec acceptance)) + rspec_config.include ApiDsl, type: :api + rspec_config.include LegacyApiDsl, type: :legacy_api + + rspec_config.include IntegrationHelpers, type: :integration + rspec_config.include IntegrationHttp, type: :integration + rspec_config.include IntegrationSetupHelpers, type: :integration + rspec_config.include IntegrationSetup, type: :integration + + rspec_config.expose_current_running_example_as :example # Can be removed when we upgrade to rspec 3 + + Delayed::Worker.plugins << DeserializationRetry + + rspec_config.before :each do + Fog::Mock.reset + Delayed::Worker.destroy_failed_jobs = false + Sequel::Deprecation.output = StringIO.new + Sequel::Deprecation.backtrace_filter = 5 + + TestConfig.reset + + stub_v1_broker + VCAP::CloudController::SecurityContext.clear + end + + rspec_config.around :each do |example| + isolation = DatabaseIsolation.choose(example.metadata[:isolation], TestConfig.config, DbConfig.new.connection) + isolation.cleanly { example.run } + end + + rspec_config.after :each do + unless Sequel::Deprecation.output.string == '' + raise "Sequel Deprecation String found: #{Sequel::Deprecation.output.string}" + end + Sequel::Deprecation.output.close unless Sequel::Deprecation.output.closed? + end + + rspec_config.after :all do + TmpdirCleaner.clean + end + + rspec_config.after :each do + Timecop.return + end + + rspec_config.after(:each, type: :legacy_api) { add_deprecation_warning } + + RspecApiDocumentation.configure do |c| + c.format = [:html, :json] + c.api_name = 'Cloud Foundry API' + c.template_path = 'spec/api/documentation/templates' + c.curl_host = 'https://api.[your-domain.com]' + c.app = Rails.application.app + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 920d6cbef9..ee57e569f7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,7 +23,6 @@ require 'cloud_controller' require 'allowy/rspec' -# require 'pry' require 'posix/spawn' require 'rspec_api_documentation' diff --git a/spec/unit/controllers/v3/processes_controller_spec.rb b/spec/unit/controllers/v3/processes_controller_spec.rb index 39c9b53c04..635ecfc376 100644 --- a/spec/unit/controllers/v3/processes_controller_spec.rb +++ b/spec/unit/controllers/v3/processes_controller_spec.rb @@ -1,65 +1,38 @@ -require 'spec_helper' +require 'rails_helper' module VCAP::CloudController - describe ProcessesController do - let(:logger) { instance_double(Steno::Logger) } + describe ProcessesController, type: :controller do let(:process_presenter) { double(:process_presenter) } - let(:index_stopper) { double(:index_stopper) } - let(:user) { User.make } let(:space) { Space.make } - let(:app_model) { AppModel.make(space: space) } - let(:process) { AppFactory.make(app_guid: app_model.guid) } - let(:guid) { process.guid } let(:membership) { instance_double(Membership) } let(:roles) { instance_double(Roles) } - let(:req_body) { '' } let(:expected_response) { 'process_response_body' } - let(:params) { {} } - - let(:processes_controller) do - ProcessesController.new( - {}, - logger, - {}, - params, - req_body, - nil, - { - process_presenter: process_presenter, - index_stopper: index_stopper - }, - ) - end before do - allow(logger).to receive(:debug) - allow(process_presenter).to receive(:present_json).and_return(expected_response) - allow(membership).to receive(:has_any_roles?).and_return(true) - allow(processes_controller).to receive(:current_user).and_return(user) - allow(processes_controller).to receive(:membership).and_return(membership) - allow(processes_controller).to receive(:check_write_permissions!).and_return(nil) - allow(processes_controller).to receive(:check_read_permissions!).and_return(nil) allow(Roles).to receive(:new).and_return(roles) allow(roles).to receive(:admin?).and_return(false) + allow_any_instance_of(ProcessesController).to receive(:process_presenter).and_return(process_presenter) + allow_any_instance_of(ProcessesController).to receive(:membership).and_return(membership) end - describe '#list' do + describe '#index' do let(:page) { 1 } let(:per_page) { 2 } let(:list_response) { 'list_response' } before do + @request.env.merge!(headers_for(User.make)) allow(process_presenter).to receive(:present_json_list).and_return(expected_response) allow(membership).to receive(:space_guids_for_roles).and_return([space.guid]) allow_any_instance_of(ProcessListFetcher).to receive(:fetch).and_call_original end it 'returns 200 and lists the apps' do - response_code, response_body = processes_controller.list + get :index expect(process_presenter).to have_received(:present_json_list).with(instance_of(PaginatedResult), '/v3/processes') - expect(response_code).to eq(200) - expect(response_body).to eq(expected_response) + expect(response.status).to eq(200) + expect(response.body).to eq(expected_response) end context 'admin' do @@ -71,11 +44,11 @@ module VCAP::CloudController it 'returns 200 and lists the apps' do expect_any_instance_of(ProcessListFetcher).to receive(:fetch_all).with(instance_of(PaginationOptions)).and_call_original - response_code, response_body = processes_controller.list + get :index expect(process_presenter).to have_received(:present_json_list).with(instance_of(PaginatedResult), '/v3/processes') - expect(response_code).to eq(200) - expect(response_body).to eq(expected_response) + expect(response.status).to eq(200) + expect(response.body).to eq(expected_response) end end @@ -83,16 +56,19 @@ module VCAP::CloudController expect_any_instance_of(ProcessListFetcher).to receive(:fetch).with(instance_of(PaginationOptions), [space.guid]).and_call_original expect_any_instance_of(ProcessListFetcher).to_not receive(:fetch_all) - processes_controller.list + get :index expect(membership).to have_received(:space_guids_for_roles).with( - [Membership::SPACE_DEVELOPER, Membership::SPACE_MANAGER, Membership::SPACE_AUDITOR, Membership::ORG_MANAGER]) + [Membership::SPACE_DEVELOPER, Membership::SPACE_MANAGER, Membership::SPACE_AUDITOR, Membership::ORG_MANAGER]) end - it 'checks for read permissions' do - processes_controller.list - - expect(processes_controller).to have_received(:check_read_permissions!) + it 'fails without read permissions scope on the auth token' do + @request.env['HTTP_AUTHORIZATION'] = '' + expect { + get :index + }.to raise_error do |error| + expect(error.name).to eq('NotAuthorized') + end end context 'when the request parameters are invalid' do @@ -101,7 +77,7 @@ module VCAP::CloudController it 'returns an 400 Bad Request' do expect { - processes_controller.list + get :index, params }.to raise_error do |error| expect(error.name).to eq 'BadQueryParameter' expect(error.response_code).to eq 400 @@ -111,15 +87,15 @@ module VCAP::CloudController end context 'because there are invalid values in parameters' do - let(:params) { { 'per_page' => 'foo' } } + let(:params) { { 'per_page' => 10000 } } it 'returns an 400 Bad Request' do expect { - processes_controller.list + get :index, params }.to raise_error do |error| expect(error.name).to eq 'BadQueryParameter' expect(error.response_code).to eq 400 - expect(error.message).to include('Per page is not a number') + expect(error.message).to include('Per page must be between 1 and 5000') end end end @@ -127,10 +103,19 @@ module VCAP::CloudController end describe '#show' do - it 'returns 200 OK' do - response_code, response = processes_controller.show(guid) - expect(response_code).to eq(HTTP::OK) - expect(response).to eq(expected_response) + let(:process_type) { App.make } + + before do + @request.env.merge!(headers_for(User.make)) + allow(process_presenter).to receive(:present_json).and_return(expected_response) + allow(membership).to receive(:has_any_roles?).and_return(true) + end + + it 'returns 200 OK with process' do + get :show, { guid: process_type.guid } + + expect(response.status).to eq(HTTP::OK) + expect(response.body).to eq(expected_response) end context 'admin' do @@ -139,19 +124,19 @@ module VCAP::CloudController allow(membership).to receive(:has_any_roles?).and_return(false) end - it 'returns 200 OK' do - response_code, response = processes_controller.show(guid) - expect(response_code).to eq(HTTP::OK) - expect(response).to eq(expected_response) + it 'returns 200 OK with process' do + get :show, { guid: process_type.guid } + + expect(response.status).to eq(HTTP::OK) + expect(response.body).to eq(expected_response) end end context 'when the user does not have read permissions' do + before { @request.env['HTTP_AUTHORIZATION'] = '' } it 'raises an ApiError with a 403 code' do - expect(processes_controller).to receive(:check_read_permissions!). - and_raise(VCAP::Errors::ApiError.new_from_details('NotAuthorized')) expect { - processes_controller.show(guid) + get :show, { guid: process_type.guid } }.to raise_error do |error| expect(error.name).to eq 'NotAuthorized' expect(error.response_code).to eq 403 @@ -160,11 +145,9 @@ module VCAP::CloudController end context 'when the process does not exist' do - let(:guid) { 'ABC123' } - it 'raises an ApiError with a 404 code' do expect { - processes_controller.show(guid) + get :show, { guid: 'ABC123' } }.to raise_error do |error| expect(error.name).to eq 'ResourceNotFound' expect(error.message).to eq 'Process not found' @@ -180,7 +163,7 @@ module VCAP::CloudController it 'raises 404' do expect { - processes_controller.show(guid) + get :show, { guid: process_type.guid } }.to raise_error do |error| expect(error.name).to eq 'ResourceNotFound' expect(error.response_code).to eq(404) @@ -191,26 +174,33 @@ module VCAP::CloudController [Membership::SPACE_DEVELOPER, Membership::SPACE_MANAGER, Membership::SPACE_AUDITOR, - Membership::ORG_MANAGER], process.space.guid, process.space.organization.guid) + Membership::ORG_MANAGER], process_type.space.guid, process_type.space.organization.guid) end end end describe '#update' do + let(:process_type) { App.make } let(:req_body) do { - 'command' => 'new command', - }.to_json + 'command' => 'new command', + } + end + + before do + @request.env.merge!(headers_for(User.make)) + allow(process_presenter).to receive(:present_json).and_return(expected_response) + allow(membership).to receive(:has_any_roles?).and_return(true) end it 'updates the process and returns the correct things' do - expect(process.command).not_to eq('new command') + expect(process_type.command).not_to eq('new command') - status, body = processes_controller.update(process.guid) + patch :update, { guid: process_type.guid, body: req_body } - expect(process.reload.command).to eq('new command') - expect(status).to eq(HTTP::OK) - expect(body).to eq(expected_response) + expect(process_type.reload.command).to eq('new command') + expect(response.status).to eq(HTTP::OK) + expect(response.body).to eq(expected_response) end context 'admin' do @@ -220,20 +210,20 @@ module VCAP::CloudController end it 'updates the process and returns the correct things' do - expect(process.command).not_to eq('new command') + expect(process_type.command).not_to eq('new command') - status, body = processes_controller.update(process.guid) + patch :update, { guid: process_type.guid, body: req_body } - expect(process.reload.command).to eq('new command') - expect(status).to eq(HTTP::OK) - expect(body).to eq(expected_response) + expect(process_type.reload.command).to eq('new command') + expect(response.status).to eq(HTTP::OK) + expect(response.body).to eq(expected_response) end end context 'when the process does not exist' do it 'raises an ApiError with a 404 code' do expect { - processes_controller.update('made-up-guid') + patch :update, { guid: 'made-up-guid', body: req_body } }.to raise_error do |error| expect(error.name).to eq 'ResourceNotFound' expect(error.response_code).to eq 404 @@ -241,25 +231,12 @@ module VCAP::CloudController end end - context 'when the request body is invalid JSON' do - let(:req_body) { '{ invalid_json }' } - - it 'returns an 400 Bad Request' do - expect { - processes_controller.update(guid) - }.to raise_error do |error| - expect(error.name).to eq 'MessageParseError' - expect(error.response_code).to eq 400 - end - end - end - context 'when the user does not have write permissions' do + before { @request.env['HTTP_AUTHORIZATION'] = '' } + it 'raises an ApiError with a 403 code' do - expect(processes_controller).to receive(:check_write_permissions!). - and_raise(VCAP::Errors::ApiError.new_from_details('NotAuthorized')) expect { - processes_controller.update(guid) + patch :update, { guid: process_type.guid, body: req_body } }.to raise_error do |error| expect(error.name).to eq 'NotAuthorized' expect(error.response_code).to eq 403 @@ -274,7 +251,7 @@ module VCAP::CloudController it 'returns 422' do expect { - processes_controller.update(guid) + patch :update, { guid: process_type.guid, body: req_body } }.to raise_error do |error| expect(error.name).to eq 'UnprocessableEntity' expect(error.response_code).to eq(422) @@ -284,11 +261,11 @@ module VCAP::CloudController end context 'when the request provides invalid data' do - let(:req_body) { '{"command": false}' } + let(:req_body) { { command: false } } it 'returns 422' do expect { - processes_controller.update(guid) + patch :update, { guid: process_type.guid, body: req_body } }.to raise_error do |error| expect(error.name).to eq 'UnprocessableEntity' expect(error.response_code).to eq(422) @@ -304,7 +281,7 @@ module VCAP::CloudController it 'raises 404' do expect { - processes_controller.update(guid) + patch :update, { guid: process_type.guid, body: req_body } }.to raise_error do |error| expect(error.name).to eq 'ResourceNotFound' expect(error.response_code).to eq(404) @@ -314,7 +291,7 @@ module VCAP::CloudController [Membership::SPACE_DEVELOPER, Membership::SPACE_MANAGER, Membership::SPACE_AUDITOR, - Membership::ORG_MANAGER], process.space.guid, process.space.organization.guid) + Membership::ORG_MANAGER], process_type.space.guid, process_type.space.organization.guid) end end @@ -325,54 +302,59 @@ module VCAP::CloudController it 'raises an ApiError with a 403 code' do expect { - processes_controller.update(guid) + patch :update, { guid: process_type.guid, body: req_body } }.to raise_error do |error| expect(error.name).to eq 'NotAuthorized' expect(error.response_code).to eq 403 end expect(membership).to have_received(:has_any_roles?).with( - [Membership::SPACE_DEVELOPER], process.space.guid) + [Membership::SPACE_DEVELOPER], process_type.space.guid) end end end describe '#terminate' do - let(:req_body) { '{"instances": 1, "memory_in_mb": 100, "disk_in_mb": 200}' } - let(:app) { AppModel.make } - let(:space) { app.space } - let(:org) { space.organization } - let(:process) { AppFactory.make(app_guid: app.guid, space: space) } - let(:expected_response) { 'some response' } - let(:manager) { make_manager_for_space(space) } + let(:process_type) { AppFactory.make } + let(:index_stopper) { double(:index_stopper) } before do - CloudController::DependencyLocator.instance.register(:index_stopper, index_stopper) + @request.env.merge!(headers_for(User.make)) allow(index_stopper).to receive(:stop_index) - - expect(process.instances).to eq(1) + allow_any_instance_of(ProcessesController).to receive(:index_stopper).and_return(index_stopper) + allow(membership).to receive(:has_any_roles?).and_return(true) end - it 'checks for the proper roles' do - _status, _body = processes_controller.terminate(process.guid, 0) + it 'terminates the lone process' do + expect(process_type.instances).to eq(1) + + put :terminate, { guid: process_type.guid, index: 0 } + expect(response.status).to eq(204) - expect(membership).to have_received(:has_any_roles?).at_least(1).times. - with([Membership::SPACE_DEVELOPER], space.guid) + process_type.reload + expect(index_stopper).to have_received(:stop_index).with(process_type, 0) end - it 'terminates the lone process' do - expect(process.instances).to eq(1) + context 'admin' do + before do + allow(roles).to receive(:admin?).and_return(true) + allow(membership).to receive(:has_any_roles?).and_return(false) + end + + it 'terminates the lone process' do + expect(process_type.instances).to eq(1) - status, _body = processes_controller.terminate(process.guid, 0) - process.reload - expect(status).to eq(204) + put :terminate, { guid: process_type.guid, index: 0 } + expect(response.status).to eq(204) - expect(index_stopper).to have_received(:stop_index).with(process, 0) + process_type.reload + expect(index_stopper).to have_received(:stop_index).with(process_type, 0) + end end it 'returns a 404 if process does not exist' do expect { - processes_controller.terminate('bad-guid', 0) + put :terminate, { guid: 'bad-guid', index: 0 } }.to raise_error do |error| expect(error.name).to eq 'ResourceNotFound' expect(error.response_code).to eq(404) @@ -382,7 +364,7 @@ module VCAP::CloudController it 'returns a 404 if instance index out of bounds' do expect { - processes_controller.terminate(process.guid, 1) + put :terminate, { guid: process_type.guid, index: 1 } }.to raise_error do |error| expect(error.name).to eq 'ResourceNotFound' expect(error.response_code).to eq(404) @@ -391,199 +373,232 @@ module VCAP::CloudController end context 'when the user does not have write permissions' do + before { @request.env['HTTP_AUTHORIZATION'] = '' } + it 'raises an ApiError with a 403 code' do - expect(processes_controller).to receive(:check_write_permissions!). - and_raise(VCAP::Errors::ApiError.new_from_details('NotAuthorized')) expect { - processes_controller.terminate(process.guid, 0) + put :terminate, { guid: process_type.guid, index: 0 } }.to raise_error do |error| expect(error.name).to eq 'NotAuthorized' expect(error.response_code).to eq 403 end end end - end - - describe '#scale' do - let(:req_body) { '{"instances": 2, "memory_in_mb": 100, "disk_in_mb": 200}' } - let(:process) { AppFactory.make } - - it 'scales the process and returns the correct things' do - expect(process.instances).not_to eq(2) - expect(process.memory).not_to eq(100) - expect(process.disk_quota).not_to eq(200) - - status, body = processes_controller.scale(process.guid) - - process.reload - expect(process.instances).to eq(2) - expect(process.memory).to eq(100) - expect(process.disk_quota).to eq(200) - expect(status).to eq(HTTP::OK) - expect(body).to eq(expected_response) - end - context 'admin' do + context 'when the user cannot read the process' do before do - allow(roles).to receive(:admin?).and_return(true) allow(membership).to receive(:has_any_roles?).and_return(false) end - it 'scales the process and returns the correct things' do - expect(process.instances).not_to eq(2) - expect(process.memory).not_to eq(100) - expect(process.disk_quota).not_to eq(200) - - status, body = processes_controller.scale(process.guid) + it 'raises 404' do + expect { + put :terminate, { guid: process_type.guid, index: 0 } + }.to raise_error do |error| + expect(error.name).to eq 'ResourceNotFound' + expect(error.response_code).to eq(404) + end - process.reload - expect(process.instances).to eq(2) - expect(process.memory).to eq(100) - expect(process.disk_quota).to eq(200) - expect(status).to eq(HTTP::OK) - expect(body).to eq(expected_response) + expect(membership).to have_received(:has_any_roles?).with( + [Membership::SPACE_DEVELOPER, + Membership::SPACE_MANAGER, + Membership::SPACE_AUDITOR, + Membership::ORG_MANAGER], process_type.space.guid, process_type.space.organization.guid) end end - context 'when the process is invalid' do + context 'when the user cannot update the process due to membership' do before do - allow_any_instance_of(ProcessScale).to receive(:scale).and_raise(ProcessScale::InvalidProcess.new('errorz')) + allow(membership).to receive(:has_any_roles?).and_return(true, false) end - it 'returns 422' do + it 'raises an ApiError with a 403 code' do expect { - processes_controller.scale(process.guid) + put :terminate, { guid: process_type.guid, index: 0 } }.to raise_error do |error| - expect(error.name).to eq 'UnprocessableEntity' - expect(error.response_code).to eq(422) - expect(error.message).to match('errorz') + expect(error.name).to eq 'NotAuthorized' + expect(error.response_code).to eq 403 end + + expect(membership).to have_received(:has_any_roles?).with( + [Membership::SPACE_DEVELOPER], process_type.space.guid) end end + end - context 'when scaling is disabled' do - before { FeatureFlag.make(name: 'app_scaling', enabled: false, error_message: nil) } + describe '#scale' do + let(:req_body) { { instances: 2, memory_in_mb: 100, disk_in_mb: 200 } } + let(:process_type) { AppFactory.make } - context 'non-admin user' do - it 'raises 403' do - expect { - processes_controller.scale(process.guid) - }.to raise_error do |error| - expect(error.name).to eq 'FeatureDisabled' - expect(error.response_code).to eq 403 - expect(error.message).to match('app_scaling') - end - end + before do + @request.env.merge!(headers_for(User.make)) + allow(process_presenter).to receive(:present_json).and_return(expected_response) + allow(membership).to receive(:has_any_roles?).and_return(true) + end + + it 'scales the process and returns the correct things' do + expect(process_type.instances).not_to eq(2) + expect(process_type.memory).not_to eq(100) + expect(process_type.disk_quota).not_to eq(200) + + put :scale, { guid: process_type.guid, body: req_body } + + process_type.reload + expect(process_type.instances).to eq(2) + expect(process_type.memory).to eq(100) + expect(process_type.disk_quota).to eq(200) + expect(response.status).to eq(HTTP::OK) + expect(response.body).to eq(expected_response) end - context 'admin user' do - before { allow(roles).to receive(:admin?).and_return(true) } + context 'admin' do + before do + allow(roles).to receive(:admin?).and_return(true) + allow(membership).to receive(:has_any_roles?).and_return(false) + end it 'scales the process and returns the correct things' do - expect(process.instances).not_to eq(2) - expect(process.memory).not_to eq(100) - expect(process.disk_quota).not_to eq(200) - - status, body = processes_controller.scale(process.guid) - - process.reload - expect(process.instances).to eq(2) - expect(process.memory).to eq(100) - expect(process.disk_quota).to eq(200) - expect(status).to eq(HTTP::OK) - expect(body).to eq(expected_response) + expect(process_type.instances).not_to eq(2) + expect(process_type.memory).not_to eq(100) + expect(process_type.disk_quota).not_to eq(200) + + put :scale, { guid: process_type.guid, body: req_body } + + process_type.reload + expect(process_type.instances).to eq(2) + expect(process_type.memory).to eq(100) + expect(process_type.disk_quota).to eq(200) + expect(response.status).to eq(HTTP::OK) + expect(response.body).to eq(expected_response) end end - end - context 'when the user does not have write permissions' do - it 'raises an ApiError with a 403 code' do - expect(processes_controller).to receive(:check_write_permissions!). - and_raise(VCAP::Errors::ApiError.new_from_details('NotAuthorized')) - expect { - processes_controller.scale(process.guid) - }.to raise_error do |error| - expect(error.name).to eq 'NotAuthorized' - expect(error.response_code).to eq 403 + context 'when the process is invalid' do + before do + allow_any_instance_of(ProcessScale).to receive(:scale).and_raise(ProcessScale::InvalidProcess.new('errorz')) + end + + it 'returns 422' do + expect { + put :scale, { guid: process_type.guid, body: req_body } + }.to raise_error do |error| + expect(error.name).to eq 'UnprocessableEntity' + expect(error.response_code).to eq(422) + expect(error.message).to match('errorz') + end end end - end - context 'when the request body is invalid JSON' do - let(:req_body) { '{ invalid_json }' } - it 'returns an 400 Bad Request' do - expect { - processes_controller.scale(process.guid) - }.to raise_error do |error| - expect(error.name).to eq 'MessageParseError' - expect(error.response_code).to eq 400 + context 'when scaling is disabled' do + before { FeatureFlag.make(name: 'app_scaling', enabled: false, error_message: nil) } + + context 'non-admin user' do + it 'raises 403' do + expect { + put :scale, { guid: process_type.guid, body: req_body } + }.to raise_error do |error| + expect(error.name).to eq 'FeatureDisabled' + expect(error.response_code).to eq 403 + expect(error.message).to match('app_scaling') + end + end + end + + context 'admin user' do + before { allow(roles).to receive(:admin?).and_return(true) } + + it 'scales the process and returns the correct things' do + expect(process_type.instances).not_to eq(2) + expect(process_type.memory).not_to eq(100) + expect(process_type.disk_quota).not_to eq(200) + + put :scale, { guid: process_type.guid, body: req_body } + + process_type.reload + expect(process_type.instances).to eq(2) + expect(process_type.memory).to eq(100) + expect(process_type.disk_quota).to eq(200) + expect(response.status).to eq(HTTP::OK) + expect(response.body).to eq(expected_response) + end end end - end - context 'when the request provides invalid data' do - let(:req_body) { '{"instances": "wrong"}' } + context 'when the user does not have write permissions' do + before { @request.env['HTTP_AUTHORIZATION'] = '' } - it 'returns 422' do - expect { - processes_controller.scale(process.guid) - }.to raise_error do |error| - expect(error.name).to eq 'UnprocessableEntity' - expect(error.response_code).to eq(422) - expect(error.message).to match('Instances is not a number') + it 'raises an ApiError with a 403 code' do + expect { + put :scale, { guid: process_type.guid, body: req_body } + }.to raise_error do |error| + expect(error.name).to eq 'NotAuthorized' + expect(error.response_code).to eq 403 + end end end - end - context 'when the process does not exist' do - it 'raises 404' do - expect { - processes_controller.scale('made-up-guid') - }.to raise_error do |error| - expect(error.name).to eq 'ResourceNotFound' - expect(error.response_code).to eq(404) + context 'when the request provides invalid data' do + let(:req_body) { { instances: 'wrong' } } + + it 'returns 422' do + expect { + put :scale, { guid: process_type.guid, body: req_body } + }.to raise_error do |error| + expect(error.name).to eq 'UnprocessableEntity' + expect(error.response_code).to eq(422) + expect(error.message).to match('Instances is not a number') + end end end - end - context 'when the user cannot read the process' do - before do - allow(membership).to receive(:has_any_roles?).and_return(false) + context 'when the process does not exist' do + it 'raises 404' do + expect { + put :scale, { guid: 'fake-guid', body: req_body } + }.to raise_error do |error| + expect(error.name).to eq 'ResourceNotFound' + expect(error.response_code).to eq(404) + end + end end - it 'raises 404' do - expect { - processes_controller.scale(process.guid) - }.to raise_error do |error| - expect(error.name).to eq 'ResourceNotFound' - expect(error.response_code).to eq(404) + context 'when the user cannot read the process' do + before do + allow(membership).to receive(:has_any_roles?).and_return(false) end - expect(membership).to have_received(:has_any_roles?).with( - [Membership::SPACE_DEVELOPER, - Membership::SPACE_MANAGER, - Membership::SPACE_AUDITOR, - Membership::ORG_MANAGER], process.space.guid, process.space.organization.guid) - end - end + it 'raises 404' do + expect { + put :scale, { guid: process_type.guid, body: req_body } + }.to raise_error do |error| + expect(error.name).to eq 'ResourceNotFound' + expect(error.response_code).to eq(404) + end - context 'when the user cannot scale the process due to membership' do - before do - allow(membership).to receive(:has_any_roles?).and_return(true, false) + expect(membership).to have_received(:has_any_roles?).with( + [Membership::SPACE_DEVELOPER, + Membership::SPACE_MANAGER, + Membership::SPACE_AUDITOR, + Membership::ORG_MANAGER], process_type.space.guid, process_type.space.organization.guid) + end end - it 'raises an ApiError with a 403 code' do - expect { - processes_controller.scale(process.guid) - }.to raise_error do |error| - expect(error.name).to eq 'NotAuthorized' - expect(error.response_code).to eq 403 + context 'when the user cannot scale the process due to membership' do + before do + allow(membership).to receive(:has_any_roles?).and_return(true, false) end - expect(membership).to have_received(:has_any_roles?).with( - [Membership::SPACE_DEVELOPER], process.space.guid) + it 'raises an ApiError with a 403 code' do + expect { + put :scale, { guid: process_type.guid, body: req_body } + }.to raise_error do |error| + expect(error.name).to eq 'NotAuthorized' + expect(error.response_code).to eq 403 + end + + expect(membership).to have_received(:has_any_roles?).with( + [Membership::SPACE_DEVELOPER], process_type.space.guid) + end end end - end end end