diff --git a/app/actions/app_feature_update.rb b/app/actions/app_feature_update.rb index 7e6efdc44f8..1fc865f78b0 100644 --- a/app/actions/app_feature_update.rb +++ b/app/actions/app_feature_update.rb @@ -6,6 +6,8 @@ def self.update(feature_name, app, message) app.update(enable_ssh: message.enabled) when 'revisions' app.update(revisions_enabled: message.enabled) + when 'file-based-service-bindings' + app.update(file_based_service_bindings_enabled: message.enabled) end end end diff --git a/app/controllers/v3/app_features_controller.rb b/app/controllers/v3/app_features_controller.rb index d8c361d0c6f..7359e524f76 100644 --- a/app/controllers/v3/app_features_controller.rb +++ b/app/controllers/v3/app_features_controller.rb @@ -2,6 +2,7 @@ require 'controllers/v3/mixins/app_sub_resource' require 'presenters/v3/app_ssh_feature_presenter' require 'presenters/v3/app_revisions_feature_presenter' +require 'presenters/v3/app_file_based_service_bindings_feature_presenter' require 'presenters/v3/app_ssh_status_presenter' require 'actions/app_feature_update' @@ -10,8 +11,9 @@ class AppFeaturesController < ApplicationController SSH_FEATURE = 'ssh'.freeze REVISIONS_FEATURE = 'revisions'.freeze + FILE_BASED_SERVICE_BINDINGS_FEATURE = 'file-based-service-bindings'.freeze - TRUSTED_APP_FEATURES = [SSH_FEATURE].freeze + TRUSTED_APP_FEATURES = [SSH_FEATURE, FILE_BASED_SERVICE_BINDINGS_FEATURE].freeze UNTRUSTED_APP_FEATURES = [REVISIONS_FEATURE].freeze APP_FEATURES = (TRUSTED_APP_FEATURES + UNTRUSTED_APP_FEATURES).freeze @@ -80,7 +82,8 @@ def present_unpagination_hash(result, path) def feature_presenter_for(feature_name, app) presenters = { SSH_FEATURE => Presenters::V3::AppSshFeaturePresenter, - REVISIONS_FEATURE => Presenters::V3::AppRevisionsFeaturePresenter + REVISIONS_FEATURE => Presenters::V3::AppRevisionsFeaturePresenter, + FILE_BASED_SERVICE_BINDINGS_FEATURE => Presenters::V3::AppFileBasedServiceBindingsFeaturePresenter } presenters[feature_name].new(app) end @@ -88,7 +91,8 @@ def feature_presenter_for(feature_name, app) def presented_app_features(app) [ Presenters::V3::AppSshFeaturePresenter.new(app), - Presenters::V3::AppRevisionsFeaturePresenter.new(app) + Presenters::V3::AppRevisionsFeaturePresenter.new(app), + Presenters::V3::AppFileBasedServiceBindingsFeaturePresenter.new(app) ] end end diff --git a/app/presenters/v3/app_file_based_service_bindings_feature_presenter.rb b/app/presenters/v3/app_file_based_service_bindings_feature_presenter.rb new file mode 100644 index 00000000000..fbd5928c7a3 --- /dev/null +++ b/app/presenters/v3/app_file_based_service_bindings_feature_presenter.rb @@ -0,0 +1,19 @@ +require 'presenters/v3/base_presenter' + +module VCAP::CloudController::Presenters::V3 + class AppFileBasedServiceBindingsFeaturePresenter < BasePresenter + def to_hash + { + name: 'file-based-service-bindings', + description: 'Enable file-based service bindings for the app', + enabled: app.file_based_service_bindings_enabled + } + end + + private + + def app + @resource + end + end +end diff --git a/docs/v3/source/includes/api_resources/_app_features.erb b/docs/v3/source/includes/api_resources/_app_features.erb index 51fe2b7d44b..f947f9c0a6c 100644 --- a/docs/v3/source/includes/api_resources/_app_features.erb +++ b/docs/v3/source/includes/api_resources/_app_features.erb @@ -18,10 +18,15 @@ "name": "revisions", "description": "Enable versioning of an application", "enabled": false + }, + { + "name": "file-based-service-bindings", + "description": "Enable file-based service bindings for the app", + "enabled": false } ], "pagination": { - "total_results": 1, + "total_results": 3, "total_pages": 1, "first": { "href": "/v3/apps/05d39de4-2c9e-4c76-8fd6-10417da07e42/features" }, "last": { "href": "/v3/apps/05d39de4-2c9e-4c76-8fd6-10417da07e42/features" }, diff --git a/docs/v3/source/includes/resources/app_features/_supported_features.md.erb b/docs/v3/source/includes/resources/app_features/_supported_features.md.erb index 558e3b11dda..7f8bfede853 100644 --- a/docs/v3/source/includes/resources/app_features/_supported_features.md.erb +++ b/docs/v3/source/includes/resources/app_features/_supported_features.md.erb @@ -6,3 +6,4 @@ Name | Description ---- | ----------- **ssh** | Enable SSHing into the app **revisions** | Enable [versioning](#revisions) of an application +**file-based-service-bindings** | Enable file-based service bindings for the app (experimental) diff --git a/spec/request/app_features_spec.rb b/spec/request/app_features_spec.rb index 9f6d098d608..1efcc9eea6d 100644 --- a/spec/request/app_features_spec.rb +++ b/spec/request/app_features_spec.rb @@ -7,7 +7,7 @@ let(:admin_header) { admin_headers_for(user) } let(:org) { VCAP::CloudController::Organization.make(created_at: 3.days.ago) } let(:space) { VCAP::CloudController::Space.make(organization: org) } - let(:app_model) { VCAP::CloudController::AppModel.make(space: space, enable_ssh: true) } + let(:app_model) { VCAP::CloudController::AppModel.make(space: space, enable_ssh: true, file_based_service_bindings_enabled: true) } describe 'GET /v3/apps/:guid/features' do context 'getting a list of available features for the app' do @@ -24,11 +24,16 @@ 'name' => 'revisions', 'description' => 'Enable versioning of an application', 'enabled' => true + }, + { + 'name' => 'file-based-service-bindings', + 'description' => 'Enable file-based service bindings for the app', + 'enabled' => true } ], 'pagination' => { - 'total_results' => 2, + 'total_results' => 3, 'total_pages' => 1, 'first' => { 'href' => "/v3/apps/#{app_model.guid}/features" }, 'last' => { 'href' => "/v3/apps/#{app_model.guid}/features" }, @@ -94,6 +99,19 @@ it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS end + + context 'file-based-service-bindings app feature' do + let(:api_call) { ->(user_headers) { get "/v3/apps/#{app_model.guid}/features/file-based-service-bindings", nil, user_headers } } + let(:feature_response_object) do + { + 'name' => 'file-based-service-bindings', + 'description' => 'Enable file-based service bindings for the app', + 'enabled' => true + } + end + + it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS + end end describe 'PATCH /v3/apps/:guid/features/:name' do @@ -172,5 +190,39 @@ it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS end end + + context 'file-based-service-bindings app feature' do + let(:api_call) { ->(user_headers) { patch "/v3/apps/#{app_model.guid}/features/file-based-service-bindings", request_body.to_json, user_headers } } + let(:feature_response_object) do + { + 'name' => 'file-based-service-bindings', + 'description' => 'Enable file-based service bindings for the app', + 'enabled' => false + } + end + + let(:expected_codes_and_responses) do + h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED) + %w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } } + %w[admin space_developer].each { |r| h[r] = { code: 200, response_object: feature_response_object } } + h + end + + it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS + + context 'when organization is suspended' do + let(:expected_codes_and_responses) do + h = super() + h['space_developer'] = { code: 403, errors: CF_ORG_SUSPENDED } + h + end + + before do + org.update(status: VCAP::CloudController::Organization::SUSPENDED) + end + + it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS + end + end end end diff --git a/spec/unit/actions/app_feature_update_spec.rb b/spec/unit/actions/app_feature_update_spec.rb index f52edad7930..071c00287a9 100644 --- a/spec/unit/actions/app_feature_update_spec.rb +++ b/spec/unit/actions/app_feature_update_spec.rb @@ -5,7 +5,7 @@ module VCAP::CloudController RSpec.describe AppFeatureUpdate do subject(:app_feature_update) { AppFeatureUpdate } - let(:app) { AppModel.make(enable_ssh: false, revisions_enabled: false) } + let(:app) { AppModel.make(enable_ssh: false, revisions_enabled: false, file_based_service_bindings_enabled: false) } let(:message) { AppFeatureUpdateMessage.new(enabled: true) } describe '.update' do @@ -24,6 +24,14 @@ module VCAP::CloudController end.to change { app.reload.revisions_enabled }.to(true) end end + + context 'when the feature name is file-based-service-bindings' do + it 'updates the file_based_service_bindings_enabled column on the app' do + expect do + AppFeatureUpdate.update('file-based-service-bindings', app, message) + end.to change { app.reload.file_based_service_bindings_enabled }.to(true) + end + end end end end diff --git a/spec/unit/controllers/v3/app_features_controller_spec.rb b/spec/unit/controllers/v3/app_features_controller_spec.rb index 9cfa39ecb5e..f1317edcb39 100644 --- a/spec/unit/controllers/v3/app_features_controller_spec.rb +++ b/spec/unit/controllers/v3/app_features_controller_spec.rb @@ -4,12 +4,15 @@ ## NOTICE: Prefer request specs over controller specs as per ADR #0003 ## RSpec.describe AppFeaturesController, type: :controller do - let(:app_model) { VCAP::CloudController::AppModel.make(enable_ssh: true) } + let(:app_model) { VCAP::CloudController::AppModel.make(enable_ssh: true, file_based_service_bindings_enabled: true) } let(:space) { app_model.space } let(:org) { space.organization } let(:user) { VCAP::CloudController::User.make } let(:app_feature_ssh_response) { { 'name' => 'ssh', 'description' => 'Enable SSHing into the app.', 'enabled' => true } } let(:app_feature_revisions_response) { { 'name' => 'revisions', 'description' => 'Enable versioning of an application', 'enabled' => true } } + let(:app_feature_file_based_service_bindings_response) do + { 'name' => 'file-based-service-bindings', 'description' => 'Enable file-based service bindings for the app', 'enabled' => true } + end before do space.update(allow_ssh: true) @@ -20,7 +23,7 @@ describe '#index' do let(:pagination_hash) do { - 'total_results' => 2, + 'total_results' => 3, 'total_pages' => 1, 'first' => { 'href' => "/v3/apps/#{app_model.guid}/features" }, 'last' => { 'href' => "/v3/apps/#{app_model.guid}/features" }, @@ -39,7 +42,7 @@ it 'returns app features' do get :index, params: { app_guid: app_model.guid } expect(parsed_body).to eq( - 'resources' => [app_feature_ssh_response, app_feature_revisions_response], + 'resources' => [app_feature_ssh_response, app_feature_revisions_response, app_feature_file_based_service_bindings_response], 'pagination' => pagination_hash ) end @@ -67,6 +70,11 @@ expect(parsed_body).to eq(app_feature_revisions_response) end + it 'returns the file-based-service-bindings app feature' do + get :show, params: { app_guid: app_model.guid, name: 'file-based-service-bindings' } + expect(parsed_body).to eq(app_feature_file_based_service_bindings_response) + end + it 'throws 404 for a non-existent feature' do set_current_user_as_role(role: 'admin', org: org, space: space, user: user) diff --git a/spec/unit/presenters/v3/app_feature_presenter_spec.rb b/spec/unit/presenters/v3/app_feature_presenter_spec.rb index bb6195ea630..c720a02f9de 100644 --- a/spec/unit/presenters/v3/app_feature_presenter_spec.rb +++ b/spec/unit/presenters/v3/app_feature_presenter_spec.rb @@ -1,5 +1,6 @@ require 'spec_helper' require 'presenters/v3/app_ssh_feature_presenter' +require 'presenters/v3/app_file_based_service_bindings_feature_presenter' module VCAP::CloudController::Presenters::V3 RSpec.describe AppSshFeaturePresenter do @@ -14,4 +15,17 @@ module VCAP::CloudController::Presenters::V3 end end end + + RSpec.describe AppFileBasedServiceBindingsFeaturePresenter do + let(:app) { VCAP::CloudController::AppModel.make } + + describe '#to_hash' do + it 'presents the app feature as json' do + result = AppFileBasedServiceBindingsFeaturePresenter.new(app).to_hash + expect(result[:name]).to eq('file-based-service-bindings') + expect(result[:description]).to eq('Enable file-based service bindings for the app') + expect(result[:enabled]).to eq(app.file_based_service_bindings_enabled) + end + end + end end