From 0e1b65ae843d03b5f3dd48dc33e0857104a60a69 Mon Sep 17 00:00:00 2001 From: nicolasleger <570901+nicolasleger@users.noreply.github.com> Date: Thu, 26 Dec 2024 10:15:56 +0100 Subject: [PATCH] feat: add QuoteCheck ErrorDetails custom feedbacks route --- .../v1/quote_check_feedbacks_controller.rb | 14 +++- config/routes/api.rb | 6 ++ lib/quote_validator/base.rb | 2 +- spec/factories/quote_checks.rb | 2 +- .../api/v1/quote_check_feedbacks_doc_spec.rb | 5 +- ...dation_error_details_feedbacks_doc_spec.rb | 62 ++++++++++++++++ ...validation_error_details_feedbacks_spec.rb | 72 +++++++++++++++++++ .../mon-devis-sans-oublis_api_v1_swagger.yaml | 46 ++++++++++++ 8 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 spec/requests/api/v1/quote_check_validation_error_details_feedbacks_doc_spec.rb create mode 100644 spec/requests/api/v1/quote_check_validation_error_details_feedbacks_spec.rb diff --git a/app/controllers/api/v1/quote_check_feedbacks_controller.rb b/app/controllers/api/v1/quote_check_feedbacks_controller.rb index 06b26d1..63c7b9d 100644 --- a/app/controllers/api/v1/quote_check_feedbacks_controller.rb +++ b/app/controllers/api/v1/quote_check_feedbacks_controller.rb @@ -6,6 +6,7 @@ module V1 class QuoteCheckFeedbacksController < BaseController before_action :authorize_request before_action :quote_check + before_action :validation_error_details, if: -> { params[:validation_error_detail_id].present? } def create @quote_check_feedback = quote_check.feedbacks.create!(quote_check_feedback_params) @@ -22,7 +23,18 @@ def quote_check end def quote_check_feedback_params - params.permit(:validation_error_details_id, :is_helpful, :comment) + raw_params = params.permit(:validation_error_details_id, :is_helpful, :comment) + return raw_params unless defined?(@validation_error_details) + + raw_params.merge(validation_error_details_id: @validation_error_details.fetch("id")) + end + + def validation_error_details + # validation_error_detail_id is in singular in path params + @validation_error_details ||= quote_check.validation_error_details.detect do |details| + details.fetch("id") == params[:validation_error_detail_id] + end || raise(ActiveRecord::RecordNotFound, + "Couldn't find ValidationErrorDetails with 'id'=#{params[:validation_error_detail_id]}") end end end diff --git a/config/routes/api.rb b/config/routes/api.rb index c3a7ce0..02750dd 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -6,6 +6,12 @@ resources :profiles, only: %i[index] resources :quote_checks, only: %i[create show] do resources :feedbacks, only: %i[create], controller: "quote_check_feedbacks" + resources :quote_check_validation_error_details, + path: "error_details", + as: :validation_error_details, + only: %i[] do + resources :feedbacks, only: %i[create], controller: "quote_check_feedbacks" + end end end end diff --git a/lib/quote_validator/base.rb b/lib/quote_validator/base.rb index eb4645b..b35f534 100644 --- a/lib/quote_validator/base.rb +++ b/lib/quote_validator/base.rb @@ -46,7 +46,7 @@ def add_error(code, end error_details << { - id: "#{quote_id}##{error_details.count + 1}", + id: [quote_id, error_details.count + 1].compact.join("-"), code:, category:, type:, title: title || I18n.t("quote_validator.errors.#{code}"), diff --git a/spec/factories/quote_checks.rb b/spec/factories/quote_checks.rb index a268b44..d3c6bf5 100644 --- a/spec/factories/quote_checks.rb +++ b/spec/factories/quote_checks.rb @@ -37,7 +37,7 @@ validation_errors { validation_error_details&.map { |error_detail| error_detail.fetch(:code) } || [] } validation_error_details do [{ - id: "#1", + id: "1", code: "something" }] end diff --git a/spec/requests/api/v1/quote_check_feedbacks_doc_spec.rb b/spec/requests/api/v1/quote_check_feedbacks_doc_spec.rb index 5be45df..5b90b08 100644 --- a/spec/requests/api/v1/quote_check_feedbacks_doc_spec.rb +++ b/spec/requests/api/v1/quote_check_feedbacks_doc_spec.rb @@ -28,9 +28,10 @@ required: %w[validation_error_details_id is_helpful] } - let(:quote_check_id) { create(:quote_check, :invalid).id } + let(:quote_check) { create(:quote_check, :invalid) } + let(:quote_check_id) { quote_check.id } let(:quote_check_feedback) do - build(:quote_check_feedback, quote_check: QuoteCheck.find(quote_check_id)).attributes + build(:quote_check_feedback, quote_check: quote_check).attributes end response "201", "Retour téléversé" do diff --git a/spec/requests/api/v1/quote_check_validation_error_details_feedbacks_doc_spec.rb b/spec/requests/api/v1/quote_check_validation_error_details_feedbacks_doc_spec.rb new file mode 100644 index 0000000..c9b9a12 --- /dev/null +++ b/spec/requests/api/v1/quote_check_validation_error_details_feedbacks_doc_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "swagger_helper" + +describe "Devis API" do + path "/quote_checks/{quote_check_id}/error_details/{error_details_id}/feedbacks" do + # TODO: i18n? + post "Déposer un retour" do + tags "Devis" + security [basic_auth: []] + consumes "application/json" + produces "application/json" + + parameter name: :quote_check_id, in: :path, type: :string + parameter name: :error_details_id, in: :path, type: :string + parameter name: :quote_check_feedback, in: :body, schema: { + type: :object, + properties: { + is_helpful: { type: :boolean, nullable: false }, + comment: { + type: :string, + nullable: true, + maxLength: QuoteCheckFeedback.validators_on(:comment).detect do |validator| + validator.is_a?(ActiveModel::Validations::LengthValidator) + end&.options&.[](:maximum) + } + }, + required: %w[is_helpful] + } + + let(:quote_check) { create(:quote_check, :invalid) } + let(:quote_check_id) { quote_check.id } + let(:error_details_id) { quote_check.validation_error_details.first.fetch("id") } + let(:quote_check_feedback) do + build(:quote_check_feedback, quote_check: quote_check).attributes + end + + response "201", "Retour téléversé" do + schema "$ref" => "#/components/schemas/quote_check_feedback" + + # See https://github.com/rswag/rswag/issues/316 + let(:Authorization) { basic_auth_header.fetch("Authorization") } # rubocop:disable RSpec/VariableName + + run_test! + end + + response "422", "missing params" do + schema "$ref" => "#/components/schemas/api_error" + + let(:quote_check_feedback) do + build(:quote_check_feedback).attributes + .except("validation_error_details_id") + .merge("is_helpful" => nil) + end + + let(:Authorization) { basic_auth_header.fetch("Authorization") } # rubocop:disable RSpec/VariableName + + run_test! + end + end + end +end diff --git a/spec/requests/api/v1/quote_check_validation_error_details_feedbacks_spec.rb b/spec/requests/api/v1/quote_check_validation_error_details_feedbacks_spec.rb new file mode 100644 index 0000000..728268e --- /dev/null +++ b/spec/requests/api/v1/quote_check_validation_error_details_feedbacks_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +# spec/controllers/posts_controller_spec.rb +require "rails_helper" + +RSpec.describe "/api/v1/quote_checks/:quote_check_id/error_details/:validation_error_detail_id/feedbacks" do + let(:quote_check) { create(:quote_check, :invalid) } + let(:validation_error_details_id) { quote_check.validation_error_details.first.fetch("id") } + let(:quote_check_id) { quote_check.id } + + let(:json) { response.parsed_body } + + describe "POST /api/v1/quote_checks/:quote_check_id/error_details/:validation_error_detail_id/feedbacks" do + let(:quote_check_feedback_params) do + { + is_helpful: false, + comment: "FAUX" + } + end + + # rubocop:disable RSpec/ExampleLength + it "returns a successful response" do + post api_v1_quote_check_validation_error_detail_feedbacks_url( + quote_check_id: quote_check_id, + validation_error_detail_id: validation_error_details_id + ), params: quote_check_feedback_params, + headers: basic_auth_header + expect(response).to be_successful + end + + it "returns a created response" do + post api_v1_quote_check_validation_error_detail_feedbacks_url( + quote_check_id: quote_check_id, + validation_error_detail_id: validation_error_details_id + ), params: quote_check_feedback_params, + headers: basic_auth_header + expect(response).to have_http_status(:created) + end + + it "returns the QuoteCheckFeedback" do + post api_v1_quote_check_validation_error_detail_feedbacks_url( + quote_check_id: quote_check_id, + validation_error_detail_id: validation_error_details_id + ), params: quote_check_feedback_params, + headers: basic_auth_header + expect(json.fetch("quote_check_id")).to eq(quote_check_id) + end + + it "creates a QuoteCheckFeedback" do + expect do + post api_v1_quote_check_validation_error_detail_feedbacks_url( + quote_check_id: quote_check_id, + validation_error_detail_id: validation_error_details_id + ), params: quote_check_feedback_params, + headers: basic_auth_header + end.to change(QuoteCheckFeedback, :count).by(1) + end + + context "with wrong error details id" do + it "returns a not found response" do + post api_v1_quote_check_validation_error_detail_feedbacks_url( + quote_check_id: quote_check_id, + validation_error_detail_id: "wrong" + ), + params: quote_check_feedback_params, + headers: basic_auth_header + expect(response).to have_http_status(:not_found) + end + end + # rubocop:enable RSpec/ExampleLength + end +end diff --git a/swagger/v1/mon-devis-sans-oublis_api_v1_swagger.yaml b/swagger/v1/mon-devis-sans-oublis_api_v1_swagger.yaml index 5ab1a63..a8bbb2d 100644 --- a/swagger/v1/mon-devis-sans-oublis_api_v1_swagger.yaml +++ b/swagger/v1/mon-devis-sans-oublis_api_v1_swagger.yaml @@ -68,6 +68,52 @@ paths: required: - validation_error_details_id - is_helpful + "/quote_checks/{quote_check_id}/error_details/{error_details_id}/feedbacks": + post: + summary: Déposer un retour + tags: + - Devis + security: + - basic_auth: [] + parameters: + - name: quote_check_id + in: path + required: true + schema: + type: string + - name: error_details_id + in: path + required: true + schema: + type: string + responses: + '201': + description: Retour téléversé + content: + application/json: + schema: + "$ref": "#/components/schemas/quote_check_feedback" + '422': + description: missing params + content: + application/json: + schema: + "$ref": "#/components/schemas/api_error" + requestBody: + content: + application/json: + schema: + type: object + properties: + is_helpful: + type: boolean + nullable: false + comment: + type: string + nullable: true + maxLength: 1000 + required: + - is_helpful "/quote_checks": post: summary: Téléverser un devis