Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit 566caa4

Browse files
author
Derik Evangelista
authored
v3(bindings): allow the binding app to be included (cloudfoundry#1794)
in the List and Get endpoints [#173173649](https://www.pivotaltracker.com/story/show/173173649)
1 parent f816203 commit 566caa4

12 files changed

Lines changed: 303 additions & 18 deletions

app/controllers/v3/service_credential_bindings_controller.rb

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
require 'fetchers/service_credential_binding_list_fetcher'
33
require 'presenters/v3/service_credential_binding_presenter'
44
require 'messages/service_credential_bindings_list_message'
5+
require 'messages/service_credential_bindings_show_message'
6+
require 'decorators/include_binding_app_decorator'
57

68
class ServiceCredentialBindingsController < ApplicationController
79
def index
@@ -15,18 +17,31 @@ def index
1517
paginated_result: SequelPaginator.new.get_page(results, pagination_options),
1618
path: '/v3' + service_credential_bindings_path,
1719
message: message,
20+
decorators: decorators(message)
1821
)
1922

2023
render status: :ok, json: presenter
2124
end
2225

2326
def show
27+
message = ServiceCredentialBindingsShowMessage.from_params(query_params)
28+
invalid_param!(message.errors.full_messages) unless message.valid?
29+
2430
ensure_service_credential_binding_is_accessible!
25-
render status: :ok, json: serialized
31+
32+
render status: :ok, json: serialized(message)
2633
end
2734

2835
private
2936

37+
AVAILABLE_DECORATORS = [
38+
IncludeBindingAppDecorator
39+
].freeze
40+
41+
def decorators(message)
42+
AVAILABLE_DECORATORS.select { |d| d.match?(message.include) }.reduce([]) { |decorators, d| decorators << d }
43+
end
44+
3045
def service_credential_binding
3146
@service_credential_binding ||= fetcher.fetch(hashed_params[:guid], space_guids: space_guids)
3247
end
@@ -44,8 +59,8 @@ def pagination_options
4459
ListMessage.from_params(query_params_with_order_by, []).pagination_options
4560
end
4661

47-
def serialized
48-
Presenters::V3::ServiceCredentialBindingPresenter.new(service_credential_binding).to_hash
62+
def serialized(message)
63+
Presenters::V3::ServiceCredentialBindingPresenter.new(service_credential_binding, decorators: decorators(message)).to_hash
4964
end
5065

5166
def ensure_service_credential_binding_is_accessible!
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module VCAP::CloudController
2+
class IncludeBindingAppDecorator
3+
class << self
4+
def match?(include)
5+
include&.any? { |i| %w(app).include?(i) }
6+
end
7+
8+
def decorate(hash, bindings)
9+
hash.deep_merge({
10+
included: {
11+
apps: apps(bindings).map { |app| Presenters::V3::AppPresenter.new(app).to_hash }
12+
}
13+
})
14+
end
15+
16+
private
17+
18+
def apps(bindings)
19+
AppModel.where(guid: bindings.map { |x| x[:app_guid] }.uniq).order(:created_at)
20+
end
21+
end
22+
end
23+
end

app/messages/service_credential_bindings_list_message.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,27 @@
22

33
module VCAP::CloudController
44
class ServiceCredentialBindingsListMessage < ListMessage
5-
register_allowed_keys [
5+
ARRAY_KEYS = [
66
:names,
77
:service_instance_guids,
88
:service_instance_names,
99
:app_guids,
1010
:app_names,
11+
:include
12+
].freeze
13+
14+
SINGLE_KEYS = [
1115
:type
12-
]
16+
].freeze
17+
18+
register_allowed_keys ARRAY_KEYS + SINGLE_KEYS
1319

1420
validates_with NoAdditionalParamsValidator
1521
validates :type, allow_nil: true, inclusion: { in: %w(app key), message: "must be one of 'app', 'key'" }
22+
validates_with IncludeParamValidator, valid_values: %w(app)
1623

1724
def self.from_params(params)
18-
super(params, %w(names service_instance_guids service_instance_names app_guids app_names))
25+
super(params, ARRAY_KEYS.map(&:to_s))
1926
end
2027
end
2128
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
require 'messages/base_message'
2+
3+
module VCAP::CloudController
4+
class ServiceCredentialBindingsShowMessage < BaseMessage
5+
ARRAY_KEYS = [
6+
:include
7+
].freeze
8+
9+
register_allowed_keys ARRAY_KEYS
10+
11+
validates_with NoAdditionalParamsValidator
12+
validates_with IncludeParamValidator, valid_values: %w(app)
13+
14+
def self.from_params(params)
15+
super(params, ARRAY_KEYS.map(&:to_s))
16+
end
17+
end
18+
end

app/presenters/v3/service_credential_binding_presenter.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,24 @@ module Presenters
66
module V3
77
class ServiceCredentialBindingPresenter < BasePresenter
88
def to_hash
9+
base_hash.merge(extra).merge(decorations)
10+
end
11+
12+
private
13+
14+
def base_hash
915
{
1016
guid: @resource.guid,
1117
created_at: @resource.created_at,
1218
updated_at: @resource.updated_at,
1319
name: @resource.name,
1420
type: type
15-
}.merge(extra)
21+
}
1622
end
1723

18-
private
24+
def decorations
25+
@decorators.reduce({}) { |memo, d| d.decorate(memo, [@resource]) }
26+
end
1927

2028
def type
2129
case @resource

docs/v3/source/includes/experimental_resources/service_credential_bindings/_get.md.erb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ This endpoint retrieves the service credential binding by GUID.
3838
#### Definition
3939
`GET /v3/service_service_credential_bindings/:guid`
4040

41+
#### Query parameters
42+
43+
Name | Type | Description
44+
---- | ---- | ------------
45+
**include** | _list of strings_ | Optionally include a list of unique related resources in the response. Valid values are: `app`
46+
4147
#### Permitted roles
4248
|
4349
--- | ---

docs/v3/source/includes/experimental_resources/service_credential_bindings/_list.md.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ This endpoint retrieves the service credential bindings the user has access to.
2727
#### Definition
2828
`GET <%= @service_credential_binding_list_path %>`
2929

30+
#### Query parameters
31+
3032
Name | Type | Description
3133
---- | ---- | ------------
3234
**names** | _list of strings_ | Comma-delimited list of service credential binding names to filter by
@@ -35,6 +37,7 @@ Name | Type | Description
3537
**app_guids** | _list of strings_ | Comma-delimited list of app guids to filter by
3638
**app_names** | _strings_ | Type of credential binding to filter by. Valid values are: `app` or `key`
3739
**type** | _list of strings_ | Comma-delimited list of app names to filter by
40+
**include** | _list of strings_ | Optionally include a list of unique related resources in the response. Valid values are: `app`
3841
**page** | _integer_ | Page to display; valid values are integers >= 1
3942
**per_page** | _integer_ | Number of results per page; <br>valid values are 1 through 5000
4043
**order_by** | _string_ | Value to sort by. Defaults to ascending; prepend with `-` to sort descending<br>Valid values are `created_at`, `updated_at`, and `name`

spec/request/service_credential_bindings_spec.rb

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ def check_filtered_bindings(*bindings)
215215
'names',
216216
'service_instance_guids', 'service_instance_names',
217217
'app_guids', 'app_names',
218-
'type',
218+
'include',
219+
'type'
219220
]
220221
}
221222

@@ -229,6 +230,27 @@ def check_filtered_bindings(*bindings)
229230
}))
230231
end
231232
end
233+
234+
describe 'include' do
235+
it 'can include `app`' do
236+
get '/v3/service_credential_bindings?include=app', nil, admin_headers
237+
expect(last_response).to have_status_code(200)
238+
239+
expect(parsed_response['included']['apps']).to have(2).items
240+
guids = parsed_response['included']['apps'].map { |x| x['guid'] }
241+
expect(guids).to contain_exactly(app_binding.app.guid, other_app_binding.app.guid)
242+
end
243+
244+
it 'returns a 400 for invalid includes' do
245+
get '/v3/service_credential_bindings?include=routes', nil, admin_headers
246+
expect(last_response).to have_status_code(400)
247+
expect(parsed_response['errors']).to include(include({
248+
'detail' => include("Invalid included resource: 'routes'"),
249+
'title' => 'CF-BadQueryParameter',
250+
'code' => 10005,
251+
}))
252+
end
253+
end
232254
end
233255

234256
describe 'GET /v3/service_credential_bindings/:missing_key' do
@@ -284,6 +306,17 @@ def check_filtered_bindings(*bindings)
284306
it_behaves_like 'permissions for single object endpoint', LOCAL_ROLES
285307
end
286308
end
309+
310+
describe 'query params' do
311+
describe 'include' do
312+
it 'can include `app`' do
313+
get "/v3/service_credential_bindings/#{key.guid}?include=app", nil, admin_headers
314+
expect(last_response).to have_status_code(200)
315+
316+
expect(parsed_response['included']['apps']).to have(0).items
317+
end
318+
end
319+
end
287320
end
288321

289322
describe 'GET /v3/service_credential_bindings/:app_guid' do
@@ -340,6 +373,47 @@ def check_filtered_bindings(*bindings)
340373
end
341374
end
342375
end
376+
377+
describe 'include' do
378+
it 'can include `app`' do
379+
get "/v3/service_credential_bindings/#{app_binding.guid}?include=app", nil, admin_headers
380+
expect(last_response).to have_status_code(200)
381+
382+
expect(parsed_response['included']['apps']).to have(1).items
383+
guids = parsed_response['included']['apps'].map { |x| x['guid'] }
384+
expect(guids).to contain_exactly(app_to_bind_to.guid)
385+
end
386+
end
387+
end
388+
389+
describe 'GET /v3/service_credential_bindings/:guid' do
390+
let(:binding) { VCAP::CloudController::ServiceBinding.make }
391+
392+
describe 'query params' do
393+
it 'returns 400 for invalid query params' do
394+
get "/v3/service_credential_bindings/#{binding.guid}?bahamas=yes-please", nil, admin_headers
395+
396+
expect(last_response).to have_status_code(400)
397+
expect(parsed_response['errors']).to include(include({
398+
'detail' => "The query parameter is invalid: Unknown query parameter(s): 'bahamas'. Valid parameters are: 'include'",
399+
'title' => 'CF-BadQueryParameter',
400+
'code' => 10005,
401+
}))
402+
end
403+
404+
describe 'includes' do
405+
it 'returns 400 for invalid includes' do
406+
get "/v3/service_credential_bindings/#{binding.guid}?include=routes", nil, admin_headers
407+
408+
expect(last_response).to have_status_code(400)
409+
expect(parsed_response['errors']).to include(include({
410+
'detail' => include("Invalid included resource: 'routes'"),
411+
'title' => 'CF-BadQueryParameter',
412+
'code' => 10005,
413+
}))
414+
end
415+
end
416+
end
343417
end
344418

345419
def expected_json(binding)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
require 'spec_helper'
2+
require 'decorators/include_binding_app_decorator'
3+
4+
module VCAP::CloudController
5+
RSpec.describe IncludeBindingAppDecorator do
6+
subject(:decorator) { IncludeBindingAppDecorator }
7+
let(:bindings) { [ServiceBinding.make, ServiceBinding.make, ServiceBinding.make] }
8+
let(:apps) {
9+
bindings.map { |b| Presenters::V3::AppPresenter.new(b.app).to_hash }
10+
}
11+
12+
it 'decorates the given hash with organizations from apps' do
13+
dict = { foo: 'bar' }
14+
hash = subject.decorate(dict, bindings)
15+
expect(hash[:foo]).to eq('bar')
16+
expect(hash[:included][:apps]).to match_array(apps)
17+
end
18+
19+
it 'does not overwrite other included fields' do
20+
dict = { foo: 'bar', included: { fruits: ['tomato', 'banana'] } }
21+
hash = subject.decorate(dict, bindings)
22+
expect(hash[:foo]).to eq('bar')
23+
expect(hash[:included][:apps]).to match_array(apps)
24+
expect(hash[:included][:fruits]).to match_array(['tomato', 'banana'])
25+
end
26+
27+
describe '.match?' do
28+
it 'matches include arrays containing "app"' do
29+
expect(decorator.match?(['potato', 'app', 'turnip'])).to be_truthy
30+
end
31+
32+
it 'does not match other include arrays' do
33+
expect(decorator.match?(['potato', 'turnip'])).to be_falsey
34+
end
35+
end
36+
end
37+
end

spec/unit/messages/service_credential_bindings_list_message_spec.rb

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ module VCAP::CloudController
1515
'names' => 'name1, name2',
1616
'app_guids' => 'app-1-guid, app-2-guid, app-3-guid',
1717
'app_names' => 'app-1-name, app-2-name, app-3-name',
18-
'type' => 'app'
18+
'type' => 'app',
19+
'include' => 'app'
1920
}
2021
end
2122

@@ -31,6 +32,7 @@ module VCAP::CloudController
3132
expect(message.app_guids).to match_array(['app-1-guid', 'app-2-guid', 'app-3-guid'])
3233
expect(message.app_names).to match_array(['app-1-name', 'app-2-name', 'app-3-name'])
3334
expect(message.type).to eq('app')
35+
expect(message.include).to match_array(['app'])
3436
end
3537

3638
it 'converts requested keys to symbols' do
@@ -43,6 +45,7 @@ module VCAP::CloudController
4345
expect(message.requested?(:app_guids)).to be_truthy
4446
expect(message.requested?(:app_names)).to be_truthy
4547
expect(message.requested?(:type)).to be_truthy
48+
expect(message.requested?(:include)).to be_truthy
4649
end
4750
end
4851

@@ -58,20 +61,35 @@ module VCAP::CloudController
5861
end
5962

6063
it 'returns false for invalid fields' do
61-
message = described_class.from_params({ foobar: 'pants' })
64+
message = described_class.from_params({ 'foobar' => 'pants' })
6265
expect(message).not_to be_valid
6366
expect(message.errors[:base][0]).to include("Unknown query parameter(s): 'foobar'")
6467
end
6568

66-
it 'returns true for valid types' do
67-
expect(described_class.from_params({ type: 'app' })).to be_valid
68-
expect(described_class.from_params({ type: 'key' })).to be_valid
69+
context 'type' do
70+
it 'returns true for valid types' do
71+
expect(described_class.from_params({ 'type' => 'app' })).to be_valid
72+
expect(described_class.from_params({ 'type' => 'key' })).to be_valid
73+
end
74+
75+
it 'returns false for invalid types' do
76+
message = described_class.from_params({ 'type' => 'route' })
77+
expect(message).not_to be_valid
78+
expect(message.errors[:type]).to include("must be one of 'app', 'key'")
79+
end
6980
end
7081

71-
it 'returns false for invalid types' do
72-
message = described_class.from_params({ type: 'route' })
73-
expect(message).not_to be_valid
74-
expect(message.errors[:type]).to include("must be one of 'app', 'key'")
82+
context 'include' do
83+
it 'returns false for arbitrary values' do
84+
message = described_class.from_params({ 'include' => 'route' })
85+
expect(message).not_to be_valid
86+
expect(message.errors[:base]).to include(include("Invalid included resource: 'route'"))
87+
end
88+
89+
it 'returns true for valid values' do
90+
message = described_class.from_params({ 'include' => 'app' })
91+
expect(message).to be_valid
92+
end
7593
end
7694
end
7795
end

0 commit comments

Comments
 (0)