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

Commit 8d85555

Browse files
author
Derik Evangelista
authored
v3(services): shared_spaces can include fields
Valid fields are: * space: name, guid, relationships.organization * space.organization: name, guid [#171954418](https://www.pivotaltracker.com/story/show/171954418)
1 parent ece8b14 commit 8d85555

13 files changed

Lines changed: 232 additions & 15 deletions

app/controllers/v3/service_instances_controller.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'messages/service_instance_create_managed_message'
77
require 'messages/service_instance_create_user_provided_message'
88
require 'messages/service_instance_show_message'
9+
require 'messages/shared_spaces_show_message'
910
require 'presenters/v3/relationship_presenter'
1011
require 'presenters/v3/to_many_relationship_presenter'
1112
require 'presenters/v3/paginated_list_presenter'
@@ -161,8 +162,16 @@ def relationships_shared_spaces
161162
service_instance = ServiceInstance.first(guid: hashed_params[:service_instance_guid])
162163
resource_not_found!(:service_instance) unless service_instance && can_read_space?(service_instance.space)
163164

165+
message = SharedSpacesShowMessage.from_params(query_params)
166+
invalid_param!(message.errors.full_messages) unless message.valid?
167+
164168
render status: :ok, json: Presenters::V3::ToManyRelationshipPresenter.new(
165-
"service_instances/#{service_instance.guid}", service_instance.shared_spaces, 'shared_spaces', build_related: false)
169+
"service_instances/#{service_instance.guid}",
170+
service_instance.shared_spaces,
171+
'shared_spaces',
172+
build_related: false,
173+
decorators: decorators_for_fields(message.fields)
174+
)
166175
end
167176

168177
def credentials

app/decorators/field_service_instance_organization_decorator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ def initialize(fields)
1212
@fields = fields[:'space.organization'].to_set.intersection(self.class.allowed)
1313
end
1414

15-
def decorate(hash, service_instances)
15+
def decorate(hash, resources)
1616
hash[:included] ||= {}
17-
spaces = service_instances.map(&:space).uniq
17+
spaces = resources.map { |r| r.try(:space) || r }.uniq
1818
orgs = spaces.map(&:organization).uniq
1919

2020
hash[:included][:organizations] = orgs.sort_by(&:created_at).map do |org|

app/decorators/field_service_instance_space_decorator.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ def initialize(fields)
1212
@fields = fields[:space].to_set.intersection(self.class.allowed)
1313
end
1414

15-
def decorate(hash, service_instances)
15+
def decorate(hash, resources)
1616
hash[:included] ||= {}
17-
spaces = service_instances.map(&:space).uniq
17+
18+
spaces = resources.map { |r| r.try(:space) || r }.uniq
1819

1920
hash[:included][:spaces] = spaces.sort_by(&:created_at).map do |space|
2021
temp = {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module VCAP::CloudController
2+
class SharedSpacesShowMessage < BaseMessage
3+
register_allowed_keys [:fields]
4+
5+
validates_with NoAdditionalParamsValidator
6+
validates :fields, allow_nil: true, fields: {
7+
allowed: {
8+
'space' => %w(name guid relationships.organization),
9+
'space.organization' => %w(name guid)
10+
}
11+
}
12+
13+
def self.from_params(params)
14+
instance = super(params, [], fields: %w(fields))
15+
instance
16+
end
17+
end
18+
end

app/presenters/v3/to_many_relationship_presenter.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@ module VCAP::CloudController
44
module Presenters
55
module V3
66
class ToManyRelationshipPresenter < BasePresenter
7-
def initialize(relation_url, relationships, relationship_path, build_related: true)
7+
def initialize(relation_url, relationships, relationship_path, build_related: true, decorators: [])
88
@relation_url = relation_url
99
@relationships = relationships
1010
@relationship_path = relationship_path
1111
@build_related = build_related
12+
@decorators = decorators
1213
end
1314

1415
def to_hash
15-
{
16-
data: build_relations,
16+
relations = build_relations
17+
h = {
18+
data: relations,
1719
links: build_links
1820
}
21+
22+
@decorators.reduce(h) { |memo, d| d.decorate(memo, @relationships) }
1923
end
2024

2125
private

docs/v3/source/includes/concepts/_fields.md.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ For information on `fields` support for each resource refer to its documentation
3030
Resource | Endpoint
3131
-------- | --------------
3232
**Service Instances** | [v3/service_instances](#list-service-instances), [v3/service_instances/:guid](/#get-a-service-instance)
33+
**Shared Spaces** | [/v3/service_instances/:guid/relationships/shared_spaces](#list-shared-spaces-relationship)
3334
**Service Offerings** | [v3/service_offerings](#list-service-offerings), [v3/service_offerings/:guid](/#get-a-service-offering)
3435
**Service Plans** | [v3/service_plans](#list-service-plans), [v3/service_plans/:guid](/#get-a-service-plan)
3536

docs/v3/source/includes/resources/service_instances/_list_shared_spaces.md.erb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ This endpoint lists the spaces that the service instance has been shared to.
2525
#### Definition
2626
`GET /v3/service_instances/:guid/relationships/shared_spaces`
2727

28+
#### Query parameters
29+
30+
Name | Type | Description
31+
---- | ---- | ------------
32+
**fields** (*experimental*)| [_fields parameter_](#fields-parameter) | [_Allowed values_](#shared-spaces-fields)
33+
34+
##### Shared Spaces List Fields
35+
36+
Resource | Allowed Keys
37+
------------------- | ----
38+
space | `guid`, `name`, `relationships.organization`
39+
space.organization| `guid`, `name`
40+
2841
#### Permitted roles (in the service instance's originating space)
2942
|
3043
--- | ---

spec/request/service_instances_spec.rb

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,99 @@ def check_filtered_instances(*instances)
660660
end
661661
end
662662

663+
describe 'GET /v3/service_instances/:guid/relationships/shared_spaces' do
664+
let(:user_header) { headers_for(user) }
665+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
666+
let(:other_space) { VCAP::CloudController::Space.make }
667+
668+
before(:each) do
669+
share_service_instance(instance, other_space)
670+
end
671+
672+
it 'returns a list of space guids where the service instance is shared to' do
673+
set_current_user_as_role(role: 'space_developer', org: space.organization, space: space, user: user)
674+
get "/v3/service_instances/#{instance.guid}/relationships/shared_spaces", nil, user_header
675+
expect(last_response.status).to eq(200)
676+
677+
expected_response = {
678+
data: [{ guid: other_space.guid }],
679+
links: {
680+
self: { href: "#{link_prefix}/v3/service_instances/#{instance.guid}/relationships/shared_spaces" },
681+
}
682+
}.with_indifferent_access
683+
684+
expect(parsed_response).to be_a_response_like(expected_response)
685+
end
686+
687+
it 'respond with 404 when the user cannot read the originating space' do
688+
set_current_user_as_role(role: 'space_developer', org: other_space.organization, space: other_space, user: user)
689+
get "/v3/service_instances/#{instance.guid}/relationships/shared_spaces", nil, user_header
690+
expect(last_response.status).to eq(404)
691+
end
692+
693+
describe 'fields' do
694+
it 'can include the space name, guid and organization relationship fields' do
695+
get "/v3/service_instances/#{instance.guid}/relationships/shared_spaces?fields[space]=name,guid,relationships.organization", nil, admin_headers
696+
expect(last_response).to have_status_code(200)
697+
698+
r = { organization: { data: { guid: other_space.organization.guid } } }
699+
included = {
700+
spaces: [
701+
{ name: other_space.name, guid: other_space.guid, relationships: r }
702+
]
703+
}
704+
705+
expect({ included: parsed_response['included'] }).to match_json_response({ included: included })
706+
end
707+
708+
it 'can include the organization name and guid fields through space' do
709+
get "/v3/service_instances/#{instance.guid}/relationships/shared_spaces?fields[space.organization]=name,guid", nil, admin_headers
710+
expect(last_response).to have_status_code(200)
711+
712+
included = {
713+
organizations: [
714+
{
715+
name: other_space.organization.name,
716+
guid: other_space.organization.guid
717+
}
718+
]
719+
}
720+
721+
expect({ included: parsed_response['included'] }).to match_json_response({ included: included })
722+
end
723+
724+
it 'fails for invalid resources' do
725+
get "/v3/service_instances/#{instance.guid}/relationships/shared_spaces?fields[fruit]=name", nil, admin_headers
726+
expect(last_response).to have_status_code(400)
727+
expect(parsed_response['errors']).to include(
728+
include(
729+
'detail' => "The query parameter is invalid: Fields [fruit] valid resources are: 'space', 'space.organization'"
730+
)
731+
)
732+
end
733+
734+
it 'fails for not allowed space fields' do
735+
get "/v3/service_instances/#{instance.guid}/relationships/shared_spaces?fields[space]=metadata", nil, admin_headers
736+
expect(last_response).to have_status_code(400)
737+
expect(parsed_response['errors']).to include(
738+
include(
739+
'detail' => "The query parameter is invalid: Fields valid keys for 'space' are: 'name', 'guid', 'relationships.organization'"
740+
)
741+
)
742+
end
743+
744+
it 'fails for not allowed space.organization fields' do
745+
get "/v3/service_instances/#{instance.guid}/relationships/shared_spaces?fields[space.organization]=metadata", nil, admin_headers
746+
expect(last_response).to have_status_code(400)
747+
expect(parsed_response['errors']).to include(
748+
include(
749+
'detail' => "The query parameter is invalid: Fields valid keys for 'space.organization' are: 'name', 'guid'"
750+
)
751+
)
752+
end
753+
end
754+
end
755+
663756
describe 'POST /v3/service_instances' do
664757
let(:api_call) { lambda { |user_headers| post '/v3/service_instances', request_body.to_json, user_headers } }
665758
let(:space_guid) { space.guid }

spec/unit/decorators/field_service_instance_organization_decorator_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,27 @@ module VCAP::CloudController
6868
expect(hash[:included][:organizations]).to have(1).element
6969
end
7070
end
71+
72+
context 'decorating relationships' do
73+
it 'includes the related resource correctly' do
74+
decorator = described_class.new({ 'space.organization': ['name', 'guid'] })
75+
undecorated_hash = { foo: 'bar', included: { monkeys: %w(zach greg) } }
76+
relationship = [space1, space2, space1]
77+
78+
hash = decorator.decorate(undecorated_hash, relationship)
79+
80+
expect(hash).to match({
81+
foo: 'bar',
82+
included: {
83+
monkeys: %w(zach greg),
84+
organizations: [
85+
{ name: org1.name, guid: org1.guid },
86+
{ name: org2.name, guid: org2.guid }
87+
]
88+
}
89+
})
90+
end
91+
end
7192
end
7293

7394
describe '.match?' do

spec/unit/decorators/field_service_instance_space_decorator_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,31 @@ module VCAP::CloudController
144144
expect(hash[:included][:spaces]).to have(1).element
145145
end
146146
end
147+
148+
context 'decorating relationships' do
149+
it 'includes the related resource correctly' do
150+
decorator = described_class.new({ 'space': ['guid'] })
151+
undecorated_hash = { foo: 'bar', included: { monkeys: %w(zach greg) } }
152+
relationship = [space1, space2, space1]
153+
154+
hash = decorator.decorate(undecorated_hash, relationship)
155+
156+
expect(hash).to match({
157+
foo: 'bar',
158+
included: {
159+
monkeys: %w(zach greg),
160+
spaces: [
161+
{
162+
guid: space1.guid,
163+
},
164+
{
165+
guid: space2.guid,
166+
}
167+
]
168+
}
169+
})
170+
end
171+
end
147172
end
148173

149174
describe '.match?' do

0 commit comments

Comments
 (0)