From 9f0123a703a5a43cf00a7d45d19975407f658690 Mon Sep 17 00:00:00 2001 From: Markus Kahl Date: Mon, 27 Mar 2017 13:38:45 +0100 Subject: [PATCH] query update and query update form wip --- app/contracts/queries/base_contract.rb | 7 + app/contracts/queries/create_contract.rb | 7 - app/contracts/queries/update_contract.rb | 35 ++ app/services/queries/update_query_service.rb | 58 ++++ config/locales/en.yml | 2 +- lib/api/v3/queries/create_query.rb | 29 ++ lib/api/v3/queries/queries_api.rb | 6 + lib/api/v3/queries/update_form_api.rb | 62 ++++ lib/api/v3/queries/update_form_representer.rb | 68 ++++ .../api/v3/queries/create_query_spec.rb | 7 + .../api/v3/queries/update_form_api_spec.rb | 309 ++++++++++++++++++ .../api/v3/queries/update_query_spec.rb | 186 +++++++++++ 12 files changed, 768 insertions(+), 8 deletions(-) create mode 100644 app/contracts/queries/update_contract.rb create mode 100644 app/services/queries/update_query_service.rb create mode 100644 lib/api/v3/queries/update_form_api.rb create mode 100644 lib/api/v3/queries/update_form_representer.rb create mode 100644 spec/requests/api/v3/queries/update_form_api_spec.rb create mode 100644 spec/requests/api/v3/queries/update_query_spec.rb diff --git a/app/contracts/queries/base_contract.rb b/app/contracts/queries/base_contract.rb index 82f492002d..3117009b68 100644 --- a/app/contracts/queries/base_contract.rb +++ b/app/contracts/queries/base_contract.rb @@ -46,6 +46,7 @@ module Queries attr_reader :user validate :validate_project + validate :user_allowed_to_make_public def initialize(query, user) super query @@ -60,5 +61,11 @@ module Queries def project_visible? Project.visible(user).where(id: project_id).exists? end + + def user_allowed_to_make_public + if is_public && !user.allowed_to?(:manage_public_queries, model.project) + errors.add :public, :error_unauthorized + end + end end end diff --git a/app/contracts/queries/create_contract.rb b/app/contracts/queries/create_contract.rb index c683922264..3accf395e0 100644 --- a/app/contracts/queries/create_contract.rb +++ b/app/contracts/queries/create_contract.rb @@ -31,12 +31,5 @@ require 'queries/base_contract' module Queries class CreateContract < BaseContract - validate :user_allowed_to_make_public - - def user_allowed_to_make_public - if is_public && !user.allowed_to?(:manage_public_queries, model.project) - errors.add :public, :error_unauthorized - end - end end end diff --git a/app/contracts/queries/update_contract.rb b/app/contracts/queries/update_contract.rb new file mode 100644 index 0000000000..9e55879e45 --- /dev/null +++ b/app/contracts/queries/update_contract.rb @@ -0,0 +1,35 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +require 'queries/base_contract' + +module Queries + class UpdateContract < BaseContract + end +end diff --git a/app/services/queries/update_query_service.rb b/app/services/queries/update_query_service.rb new file mode 100644 index 0000000000..1e14f4a610 --- /dev/null +++ b/app/services/queries/update_query_service.rb @@ -0,0 +1,58 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +class UpdateQueryService + include Concerns::Contracted + + attr_reader :user + + self.contract = Queries::UpdateContract + + def initialize(user:) + @user = user + end + + def call(query) + update query + end + + private + + def update(query) + initialize_contract! query + + result, errors = validate_and_save query + + ServiceResult.new success: result, errors: errors, result: query + end + + def initialize_contract!(query) + self.contract = self.class.contract.new query, user + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 910a3e5bf1..45f201a733 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -251,7 +251,7 @@ en: activemodel: errors: models: - "queries/create_contract": + "queries/base_contract": attributes: project: error_not_found: "not found" diff --git a/lib/api/v3/queries/create_query.rb b/lib/api/v3/queries/create_query.rb index 810a2b6f88..a1906eebfa 100644 --- a/lib/api/v3/queries/create_query.rb +++ b/lib/api/v3/queries/create_query.rb @@ -28,6 +28,7 @@ require 'api/v3/queries/query_representer' require 'queries/create_query_service' +require 'queries/update_query_service' module API module V3 @@ -45,6 +46,18 @@ module API end end + def update_query(query, request_body, current_user) + rep = representer.new query, current_user: current_user + query = rep.from_hash request_body + call = ::UpdateQueryService.new(user: current_user).call query + + if call.success? + representer.new call.result, current_user: current_user, embed_links: true + else + fail ::API::Errors::ErrorBase.create_and_merge_errors(call.errors) + end + end + def representer ::API::V3::Queries::QueryRepresenter end @@ -59,6 +72,22 @@ module API # the service mutates the given query in place so we just return it query end + + def foo + rep = representer.new Relation.new, current_user: current_user + relation = rep.from_json request.body.read + attributes = filter_attributes relation + service = ::UpdateRelationService.new relation: Relation.find_by_id!(params[:id]), + user: current_user + call = service.call attributes: attributes, + send_notifications: !(params[:notify] == 'false') + + if call.success? + representer.new call.result, current_user: current_user, embed_links: true + else + fail ::API::Errors::ErrorBase.create_and_merge_errors(call.errors) + end + end end end end diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb index 2cfc9ebbf3..d1aa5afb92 100644 --- a/lib/api/v3/queries/queries_api.rb +++ b/lib/api/v3/queries/queries_api.rb @@ -98,6 +98,8 @@ module API requires :id, desc: 'Query id' end route_param :id do + mount API::V3::Queries::UpdateFormAPI + before do @query = Query.find(params[:id]) @@ -106,6 +108,10 @@ module API end end + patch do + update_query @query, request_body, current_user + end + get do query_representer_response(@query, params) end diff --git a/lib/api/v3/queries/update_form_api.rb b/lib/api/v3/queries/update_form_api.rb new file mode 100644 index 0000000000..67012f6082 --- /dev/null +++ b/lib/api/v3/queries/update_form_api.rb @@ -0,0 +1,62 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +require 'api/v3/queries/query_representer' +require 'queries/create_query_service' + +module API + module V3 + module Queries + class UpdateFormAPI < ::API::OpenProjectAPI + resource :form do + helpers ::API::V3::Queries::CreateQuery + + post do + query = @query + representer = ::API::V3::Queries::QueryRepresenter.create query, current_user: current_user + query = representer.from_hash Hash(request_body) + contract = ::Queries::UpdateContract.new query, current_user + contract.validate + + query.user = current_user + + api_errors = ::API::Errors::ErrorBase.create_errors(contract.errors) + + # errors for invalid data (e.g. validation errors) are handled inside the form + if api_errors.all? { |error| error.code == 422 } + status 200 + UpdateFormRepresenter.new query, current_user: current_user, errors: api_errors + else + fail ::API::Errors::MultipleErrors.create_if_many(api_errors) + end + end + end + end + end + end +end diff --git a/lib/api/v3/queries/update_form_representer.rb b/lib/api/v3/queries/update_form_representer.rb new file mode 100644 index 0000000000..31a7a21ec6 --- /dev/null +++ b/lib/api/v3/queries/update_form_representer.rb @@ -0,0 +1,68 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +module API + module V3 + module Queries + class CreateFormRepresenter < FormRepresenter + link :self do + { + href: api_v3_paths.query_form(represented.id), + method: :post + } + end + + link :validate do + { + href: api_v3_paths.query_form(represented.id), + method: :post + } + end + + link :commit do + if allow_commit? + { + href: api_v3_paths.queries(represented.id), + method: :post + } + end + end + + private + + def allow_commit? + represented.name.present? && ( + (!represented.is_public && current_user.allowed_to?(:save_queries, represented.project)) || + (represented.is_public && current_user.allowed_to?(:manage_public_queries, represented.project)) + ) && @errors.empty? + end + end + end + end +end diff --git a/spec/requests/api/v3/queries/create_query_spec.rb b/spec/requests/api/v3/queries/create_query_spec.rb index 1312ce4e36..8da6e2e5cc 100644 --- a/spec/requests/api/v3/queries/create_query_spec.rb +++ b/spec/requests/api/v3/queries/create_query_spec.rb @@ -120,6 +120,13 @@ describe "POST /api/v3/queries", type: :request do expect(query.columns.map(&:name)).to eq [:id, :subject, :status, :assigned_to] expect(query.user).to eq user expect(query.project).to eq project + + expect(query.filters.size).to eq 1 + filter = query.filters.first + + expect(filter.name).to eq :status_id + expect(filter.operator).to eq "=" + expect(filter.values).to eq [status.id.to_s] end end diff --git a/spec/requests/api/v3/queries/update_form_api_spec.rb b/spec/requests/api/v3/queries/update_form_api_spec.rb new file mode 100644 index 0000000000..f597601296 --- /dev/null +++ b/spec/requests/api/v3/queries/update_form_api_spec.rb @@ -0,0 +1,309 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2015 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. + +require 'spec_helper' +require 'rack/test' + +describe "POST /api/v3/queries/form", type: :request do + include API::V3::Utilities::PathHelper + + let(:path) { api_v3_paths.query_form } + let(:user) { FactoryGirl.create(:admin) } + let!(:project) { FactoryGirl.create(:project_with_types) } + let!(:query) { FactoryGirl.create :query, name: "Existing Query", is_public: true } + + let(:parameters) { {} } + let(:override_params) { {} } + let(:form) { JSON.parse response.body } + + before do + login_as(user) + + post path, + params: parameters.merge(override_params).to_json, + headers: { 'CONTENT_TYPE' => 'application/json' } + end + + it 'should return 200(OK)' do + expect(response.status).to eq(200) + end + + it 'should be of type form' do + expect(form["_type"]).to eq "Form" + end + + it 'has the available_projects link for creation in the schema' do + expect(form.dig("_embedded", "schema", "project", "_links", "allowedValues", "href")) + .to eq "/api/v3/queries/available_projects" + end + + describe 'with empty parameters' do + it 'has 0 validation errors' do + require 'pry'; binding.pry + + expect(form.dig("_embedded", "validationErrors").size).to eq 0 + end + end + + describe 'with all minimum parameters' do + let(:parameters) do + { + name: "Some Query" + } + end + + it 'has 0 validation errors' do + expect(form.dig("_embedded", "validationErrors")).to be_empty + end + + it 'has the given name set' do + expect(form.dig("_embedded", "payload", "name")).to eq parameters[:name] + end + end + + describe 'with all parameters given' do + let(:status) { FactoryGirl.create :status } + + let(:parameters) do + { + name: "Some Query", + public: true, + sums: true, + filters: [ + { + name: "Status", + _links: { + filter: { + href: "/api/v3/queries/filters/status" + }, + operator: { + "href": "/api/v3/queries/operators/=" + }, + values: [ + { + href: "/api/v3/statuses/#{status.id}", + } + ] + } + } + ], + _links: { + project: { + href: "/api/v3/projects/#{project.id}" + }, + columns: [ + { + href: "/api/v3/queries/columns/id" + }, + { + href: "/api/v3/queries/columns/subject" + } + ], + sortBy: [ + { + href: "/api/v3/queries/sort_bys/id-desc" + }, + { + href: "/api/v3/queries/sort_bys/assignee-asc" + } + ], + groupBy: { + href: "/api/v3/queries/group_bys/assignee" + } + } + } + end + + it 'has 0 validation errors' do + expect(form.dig("_embedded", "validationErrors")).to be_empty + end + + it 'has a commit link' do + expect(form.dig("_links", "commit")).to be_present + end + + it 'has the given name set' do + expect(form.dig("_embedded", "payload", "name")).to eq parameters[:name] + end + + it 'has the project set' do + project_link = { "href" => "/api/v3/projects/#{project.id}" } + + expect(form.dig("_embedded", "payload", "_links", "project")).to eq project_link + end + + it 'is set to public' do + expect(form.dig("_embedded", "payload", "public")).to eq true + end + + it 'has the filters set' do + filters = [ + { + "_links" => { + "filter" => { "href" => "/api/v3/queries/filters/status" }, + "operator" => { "href" => "/api/v3/queries/operators/=" }, + "values" => [ + { "href" => "/api/v3/statuses/#{status.id}" } + ] + } + } + ] + + expect(form.dig("_embedded", "payload", "filters")).to eq filters + end + + it 'has the columns set' do + columns = [ + { "href" => "/api/v3/queries/columns/id" }, + { "href" => "/api/v3/queries/columns/subject" } + ] + + expect(form.dig("_embedded", "payload", "_links", "columns")).to eq columns + end + + it 'has the groupBy set' do + group_by = { "href" => "/api/v3/queries/group_bys/assignee" } + + expect(form.dig("_embedded", "payload", "_links", "groupBy")).to eq group_by + end + + it 'has the columns set' do + sort_by = [ + { "href" => "/api/v3/queries/sort_bys/id-desc" }, + { "href" => "/api/v3/queries/sort_bys/assignee-asc" } + ] + + expect(form.dig("_embedded", "payload", "_links", "sortBy")).to eq sort_by + end + + context "with the project referred to by its identifier" do + let(:override_params) do + links = parameters[:_links] + + links[:project] = { + href: "/api/v3/projects/#{project.identifier}" + } + + { _links: links } + end + + it "still finds the project" do + project_link = { "href" => "/api/v3/projects/#{project.id}" } + + expect(form.dig("_embedded", "payload", "_links", "project")).to eq project_link + end + end + + context "with groupBy specified as a GET parameter" do + let(:path) { api_v3_paths.query_form + "?groupBy=author"} + let(:override_params) do + links = parameters[:_links] + + links.delete :groupBy + + { _links: links } + end + + it "initializes the form with the given groupBy" do + expect(form.dig("_embedded", "payload", "_links", "groupBy", "href")) + .to eq "/api/v3/queries/group_bys/author" + end + end + + context "with an unknown filter" do + let(:override_params) do + filter = parameters[:filters][0] + + filter[:_links][:filter][:href] = "/api/v3/queries/filters/statuz" + + { filters: [filter] } + end + + it "returns a validation error" do + expect(form.dig("_embedded", "validationErrors", "base", "message")).to eq "Statuz does not exist." + end + end + + context "with an unknown column" do + let(:override_params) do + column = { href: "/api/v3/queries/columns/wurst" } + links = parameters[:_links] + + links[:columns] = links[:columns] + [column] + + { _links: links } + end + + it "returns a validation error" do + expect(form.dig("_embedded", "validationErrors", "columnNames", "message")) + .to eq "Invalid query column: wurst" + end + end + + context "with an invalid groupBy column" do + let(:override_params) do + column = { href: "/api/v3/queries/group_bys/foobar" } + links = parameters[:_links] + + links[:groupBy] = column + + { _links: links } + end + + it "returns a validation error" do + expect(form.dig("_embedded", "validationErrors", "groupBy", "message")) + .to eq "Can't group by: foobar" + end + end + + context "with an invalid sort criterion" do + let(:override_params) do + sort_criterion = { href: "/api/v3/queries/sort_bys/spentTime-desc" } + links = parameters[:_links] + + links[:sortBy] = links[:sortBy] + [sort_criterion] + + { _links: links } + end + + it "returns a validation error" do + expect(form.dig("_embedded", "validationErrors", "sortCriteria", "message")) + .to eq "Can't sort by column: spent_hours" + end + end + + context "with an unauthorized user trying to set the query public" do + let(:user) { FactoryGirl.create :user } + + it "should reject the request" do + expect(form.dig("_embedded", "validationErrors", "public", "message")) + .to eq "Public - The user has no permission to create public queries." + end + end + end +end diff --git a/spec/requests/api/v3/queries/update_query_spec.rb b/spec/requests/api/v3/queries/update_query_spec.rb new file mode 100644 index 0000000000..9bb748ddab --- /dev/null +++ b/spec/requests/api/v3/queries/update_query_spec.rb @@ -0,0 +1,186 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe "PATCH /api/v3/queries/:id", type: :request do + let(:user) { FactoryGirl.create :admin } + let(:status) { FactoryGirl.create :status } + let(:project) { FactoryGirl.create :project } + + let!(:query) { FactoryGirl.create :global_query, name: "A Query", is_public: false, display_sums: false } + + let(:params) do + { + name: "Dummy Query", + public: true, + filters: [ + { + name: "Status", + _links: { + filter: { + href: "/api/v3/queries/filters/status" + }, + operator: { + "href": "/api/v3/queries/operators/=" + }, + schema: { + "href": "/api/v3/queries/filter_instance_schemas/status" + }, + values: [ + { + href: "/api/v3/statuses/#{status.id}", + } + ] + } + } + ], + _links: { + project: { + href: "/api/v3/projects/#{project.id}" + }, + columns: [ + { + href: "/api/v3/queries/columns/id" + }, + { + href: "/api/v3/queries/columns/subject" + }, + { + href: "/api/v3/queries/columns/status" + }, + { + href: "/api/v3/queries/columns/assignee" + } + ], + sortBy: [ + { + href: "/api/v3/queries/sort_bys/id-desc" + }, + { + href: "/api/v3/queries/sort_bys/assignee-asc" + } + ], + groupBy: { + href: "/api/v3/queries/group_bys/assignee" + } + } + } + end + + before do + login_as user + end + + describe "updating a query" do + before do + patch "/api/v3/queries/#{query.id}", + params: params.to_json, + headers: { "Content-Type": "application/json" } + end + + it 'should return 200 (ok)' do + expect(response.status).to eq(200) + end + + it 'should render the created query' do + json = JSON.parse(response.body) + + expect(json["_type"]).to eq "Query" + expect(json["name"]).to eq "Dummy Query" + end + + it 'should update the query correctly' do + query = Query.first + + expect(query.group_by_column.name).to eq :assigned_to + expect(query.sort_criteria).to eq [["id", "desc"], ["assigned_to", "asc"]] + expect(query.columns.map(&:name)).to eq [:id, :subject, :status, :assigned_to] + expect(query.project).to eq project + expect(query.is_public).to eq true + expect(query.display_sums).to eq false + + expect(query.filters.size).to eq 1 + filter = query.filters.first + + expect(filter.name).to eq :status_id + expect(filter.operator).to eq "=" + expect(filter.values).to eq [status.id.to_s] + end + + describe "with empty params" do + let(:params) { {} } + + it "should not change anything" do + json = JSON.parse(response.body) + + expect(json["_type"]).to eq "Query" + expect(json["name"]).to eq "A Query" + end + end + end + + context "with invalid parameters" do + def post! + patch "/api/v3/queries/#{query.id}", + params: params.to_json, + headers: { "Content-Type": "application/json" } + end + + def json + JSON.parse response.body + end + + it "yields a 422 error given an unknown project" do + params[:_links][:project][:href] = "/api/v3/projects/#{project.id}42" + + post! + + expect(response.status).to eq 422 + expect(json["message"]).to eq "Project not found" + end + + it "yields a 422 error given an unknown operator" do + params[:filters][0][:_links][:operator][:href] = "/api/v3/queries/operators/wut" + + post! + + expect(response.status).to eq 422 + expect(json["message"]).to eq "Status Operator is not included in the list" + end + + it "yields a 422 error given an unknown filter" do + params[:filters][0][:_links][:filter][:href] = "/api/v3/queries/filters/statuz" + + post! + + expect(response.status).to eq 422 + expect(json["message"]).to eq "Statuz does not exist." + end + end +end