-
Notifications
You must be signed in to change notification settings - Fork 371
Allow current=true filter on GET /v3/droplets to return only current droplets across all apps #5360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bde5dbe
910592f
3e2f62a
b733165
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -659,6 +659,7 @@ | |
| space_guids | ||
| app_guids | ||
| organization_guids | ||
| current | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This moves |
||
| ] | ||
| end | ||
| let(:params) do | ||
|
|
@@ -668,7 +669,6 @@ | |
| order_by: 'updated_at', | ||
| guids: 'foo,bar', | ||
| app_guid: app_model.guid, | ||
| current: true, | ||
| package_guid: package_model.guid, | ||
| states: %w[test foo], | ||
| label_selector: 'foo,bar', | ||
|
|
@@ -941,6 +941,50 @@ | |
| returned_guids = parsed_response['resources'].pluck('guid') | ||
| expect(returned_guids).to contain_exactly(droplet1.guid, droplet2.guid, droplet3.guid) | ||
| end | ||
|
|
||
| it 'filters by current=true as admin' do | ||
| current_droplet_app1 = create(:droplet_model, app: app_model, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| current_droplet_app2 = create(:droplet_model, app: app_model2, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| current_droplet_app3 = create(:droplet_model, app: app_model3, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| app_model.update(droplet: current_droplet_app1) | ||
| app_model2.update(droplet: current_droplet_app2) | ||
| app_model3.update(droplet: current_droplet_app3) | ||
|
|
||
| get '/v3/droplets?current=true', nil, admin_headers | ||
|
|
||
| expect(last_response.status).to eq(200) | ||
| returned_guids = parsed_response['resources'].pluck('guid') | ||
| expect(returned_guids).to contain_exactly(current_droplet_app1.guid, current_droplet_app2.guid, current_droplet_app3.guid) | ||
| end | ||
|
|
||
| it 'filters by current=true as a non-admin developer, returning only current droplets in readable spaces' do | ||
| current_droplet_app1 = create(:droplet_model, app: app_model, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| current_droplet_app2 = create(:droplet_model, app: app_model2, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| current_droplet_app3 = create(:droplet_model, app: app_model3, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| app_model.update(droplet: current_droplet_app1) | ||
| app_model2.update(droplet: current_droplet_app2) | ||
| app_model3.update(droplet: current_droplet_app3) | ||
|
|
||
| get '/v3/droplets?current=true', nil, developer_headers | ||
|
|
||
| expect(last_response.status).to eq(200) | ||
| returned_guids = parsed_response['resources'].pluck('guid') | ||
| expect(returned_guids).to contain_exactly(current_droplet_app1.guid, current_droplet_app2.guid) | ||
| expect(returned_guids).not_to include(current_droplet_app3.guid) | ||
| end | ||
|
|
||
| it 'filters by current=true combined with app_guids' do | ||
| current_droplet_app1 = create(:droplet_model, app: app_model, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| current_droplet_app2 = create(:droplet_model, app: app_model2, state: VCAP::CloudController::DropletModel::STAGED_STATE) | ||
| app_model.update(droplet: current_droplet_app1) | ||
| app_model2.update(droplet: current_droplet_app2) | ||
|
|
||
| get "/v3/droplets?current=true&app_guids=#{app_model.guid}", nil, developer_headers | ||
|
|
||
| expect(last_response.status).to eq(200) | ||
| returned_guids = parsed_response['resources'].pluck('guid') | ||
| expect(returned_guids).to contain_exactly(current_droplet_app1.guid) | ||
| end | ||
| end | ||
|
|
||
| context 'label_selector' do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -125,6 +125,76 @@ module VCAP::CloudController | |
| expect(results).to contain_exactly(staged_droplet_for_app1) | ||
| end | ||
| end | ||
|
|
||
| context 'filtering by current=true' do | ||
| let(:filters) { { current: 'true' } } | ||
|
|
||
| context 'when some apps have a current droplet set' do | ||
| before do | ||
| app1.update(droplet: staged_droplet_for_app1) | ||
| app2.update(droplet: staged_droplet_for_app2) | ||
| end | ||
|
|
||
| it 'returns only current droplets' do | ||
| results = fetcher.fetch_all(message).all | ||
| expect(results).to contain_exactly(staged_droplet_for_app1, staged_droplet_for_app2) | ||
| end | ||
|
|
||
| it 'does not return non-current droplets' do | ||
| results = fetcher.fetch_all(message).all | ||
| expect(results).not_to include(failed_droplet_for_app1) | ||
| end | ||
| end | ||
|
|
||
| context 'when no apps have a current droplet set' do | ||
| it 'returns an empty list' do | ||
| results = fetcher.fetch_all(message).all | ||
| expect(results).to be_empty | ||
| end | ||
| end | ||
|
|
||
| context 'when combined with app_guids filter' do | ||
| before do | ||
| app1.update(droplet: staged_droplet_for_app1) | ||
| app2.update(droplet: staged_droplet_for_app2) | ||
| end | ||
|
|
||
| let(:filters) { { current: 'true', app_guids: [app1.guid] } } | ||
|
|
||
| it 'returns only the current droplet for the specified app' do | ||
| results = fetcher.fetch_all(message).all | ||
| expect(results).to contain_exactly(staged_droplet_for_app1) | ||
| end | ||
| end | ||
|
|
||
| context 'when combined with space_guids filter' do | ||
| before do | ||
| app1.update(droplet: staged_droplet_for_app1) | ||
| app2.update(droplet: staged_droplet_for_app2) | ||
| end | ||
|
|
||
| let(:filters) { { current: 'true', space_guids: [app1.space.guid] } } | ||
|
|
||
| it 'returns only current droplets in the specified space' do | ||
| results = fetcher.fetch_all(message).all | ||
| expect(results).to contain_exactly(staged_droplet_for_app1) | ||
| end | ||
| end | ||
|
|
||
| context 'when combined with organization_guids filter' do | ||
| before do | ||
| app1.update(droplet: staged_droplet_for_app1) | ||
| app2.update(droplet: staged_droplet_for_app2) | ||
| end | ||
|
|
||
| let(:filters) { { current: 'true', organization_guids: [app1.organization.guid] } } | ||
|
|
||
| it 'returns only current droplets in the specified organization' do | ||
| results = fetcher.fetch_all(message).all | ||
| expect(results).to contain_exactly(staged_droplet_for_app1) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
|
|
||
| describe '#fetch_for_spaces' do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -125,8 +125,23 @@ module VCAP::CloudController | |
|
|
||
| context 'when the query is not nested under an app' do | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a test asserting current is still rejected on the package-nested route (package_guid present, app_guid absent)?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| context 'when the request contains current field' do | ||
| it 'is invalid' do | ||
| it 'is valid' do | ||
| message = DropletsListMessage.from_params({ current: 'true' }) | ||
| expect(message).to be_valid | ||
| end | ||
|
|
||
| it 'validates current must be true' do | ||
| message = DropletsListMessage.from_params({ current: 'false' }) | ||
| expect(message).not_to be_valid | ||
| expect(message.errors[:current]).to include("only accepts the value 'true'") | ||
| end | ||
| end | ||
| end | ||
|
|
||
| context 'when the query is nested under a package' do | ||
| context 'when the request contains current field' do | ||
| it 'is invalid' do | ||
| message = DropletsListMessage.from_params({ package_guid: 'some-package-guid', current: 'true' }) | ||
| expect(message).not_to be_valid | ||
| expect(message.errors[:base][0]).to include("Unknown query parameter(s): 'current'") | ||
| end | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we convert that method into a package-specific validator rather than removing it?
not_app_nested_requestgated all routes where app_guid is absent, that's both the globalGET /v3/dropletsroute and the package-nestedGET /v3/packages/:guid/dropletsroute. Removing it enablescurrent=trueon the global route as intended, but also silently enables it on the package route, which flips that endpoint from returning 422 Unknown query parameter to 200 with a join executed. That's outside the PR's stated scope and untested. Apackage_nested_request validator(firing on package_guid.present?) would still reject current there while allowing it on the global route.