From b2cf192b3368cf11e60f061cad7c01993dcd2753 Mon Sep 17 00:00:00 2001 From: Andy Callaghan Date: Tue, 13 Feb 2024 16:22:15 +0000 Subject: [PATCH 1/3] Notification create should respond with 201 --- app/controllers/api/v1/notifications_controller.rb | 2 +- spec/requests/api/v1/notifications_controller_spec.rb | 2 +- swagger/v1/swagger.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index d4ad42880e..8c32faa281 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -17,7 +17,7 @@ def create @notification.tasks_status["search_for_or_add_a_product"] = "in_progress" @notification.save!(context: :search_for_or_add_a_product) - render action: :show + render action: :show, status: :created end private diff --git a/spec/requests/api/v1/notifications_controller_spec.rb b/spec/requests/api/v1/notifications_controller_spec.rb index 6b6fb70b54..3946d60192 100644 --- a/spec/requests/api/v1/notifications_controller_spec.rb +++ b/spec/requests/api/v1/notifications_controller_spec.rb @@ -64,7 +64,7 @@ parameter name: :notification, in: :body, schema: { '$ref' => '#/components/schemas/new_notification' } - response "200", "Notification created" do + response "201", "Notification created" do schema '$ref' => '#/components/schemas/notification_object' let(:Authorization) { "Authorization #{user.api_tokens.first&.token}" } let(:notification) do diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml index 005a4f6d09..a11606c901 100644 --- a/swagger/v1/swagger.yaml +++ b/swagger/v1/swagger.yaml @@ -204,7 +204,7 @@ paths: schema: type: string responses: - '200': + '201': description: Notification created '406': description: Notification not valid From 616d84dededd6866a3d5256af0eb88bef56c4c69 Mon Sep 17 00:00:00 2001 From: Andy Callaghan Date: Tue, 13 Feb 2024 16:22:43 +0000 Subject: [PATCH 2/3] New product API endpoint + enum values --- app/controllers/api/v1/products_controller.rb | 23 + config/routes.rb | 2 +- .../api/v1/products_controller_spec.rb | 71 ++- spec/swagger_helper.rb | 59 ++- swagger/v1/swagger.yaml | 439 +++++++++++++++++- 5 files changed, 585 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/v1/products_controller.rb b/app/controllers/api/v1/products_controller.rb index d920481338..9226e68fc2 100644 --- a/app/controllers/api/v1/products_controller.rb +++ b/app/controllers/api/v1/products_controller.rb @@ -10,6 +10,21 @@ def index @products = ProductDecorator.decorate_collection(@results) end + def create + @product_form = ProductForm.new(product_create_params) + + if @product_form.valid? + context = CreateProduct.call!( + @product_form.serializable_hash.merge(ushaser: current_user) + ) + + @product = context.product.decorate + render action: :show, status: :created, location: api_v1_product_path(context.product) + else + render json: { error: "Product parameters are not valid", errors: @product_form.errors }, status: :not_acceptable + end + end + def show; end private @@ -22,6 +37,14 @@ def set_search_params @search = SearchParams.new(query_params.except(:page_name)) end + def product_create_params + params.require(:product).permit( + :name, :brand, :category, :subcategory, :product_code, + :webpage, :description, :country_of_origin, :barcode, + :authenticity, :when_placed_on_market, :has_markings, markings: [] + ) + end + def query_params params.permit(:q, :sort_by, :sort_dir, :direction, :category, :retired_status, :page_name) end diff --git a/config/routes.rb b/config/routes.rb index cd85d5f574..456206d369 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -409,7 +409,7 @@ namespace :api, defaults: { format: :json } do namespace :v1 do resource :auth, only: %i[create destroy] - resources :products, only: %i[index show] + resources :products, only: %i[index create show] resources :notifications, only: %i[create show] end end diff --git a/spec/requests/api/v1/products_controller_spec.rb b/spec/requests/api/v1/products_controller_spec.rb index d433e3448f..f49dee0ba4 100644 --- a/spec/requests/api/v1/products_controller_spec.rb +++ b/spec/requests/api/v1/products_controller_spec.rb @@ -20,7 +20,7 @@ let(:Authorization) { "Authorization #{user.api_tokens.first&.token}" } - let(:id) { create(:product).id } + let(:id) { create(:product, country_of_origin: 'country:GB').id } run_test! do |response| end @@ -64,10 +64,75 @@ path "/api/v1/products" do post "Creates a Product" do - description "Creates a Product" + description "Creates a Product in PSD" tags "Products" - produces "application/json" security [bearer: []] + parameter name: :Authorization, in: :header, type: :string + let(:Authorization) { "Authorization #{user.api_tokens.first&.token}" } + + consumes 'application/json' + + request_body_example value: { + product: { + name: 'Super Vac 2020', + category: 'Electrical appliances and equipment', + subcategory: 'Vacuum cleaners', + country_of_origin: 'country:GB', + when_placed_on_market: 'on_or_after_2021', + authenticity: 'genuine', + has_markings: 'markings_no' + } + }, name: 'product', summary: "An sample product" + + parameter name: :product, in: :body, schema: { '$ref' => '#/components/schemas/new_product' } + + response "201", "Product created" do + schema '$ref' => '#/components/schemas/product_object' + let(:Authorization) { "Authorization #{user.api_tokens.first&.token}" } + let(:product) do + { + name: 'Super Vac 2020', + brand: 'SuperDuper', + category: 'Electrical appliances and equipment', + subcategory: 'Vacuum cleaners', + country_of_origin: 'country:GB', + when_placed_on_market: 'on_or_after_2021', + authenticity: 'genuine', + has_markings: 'markings_no' + } + end + + run_test! do |response| + json = JSON.parse(response.body, symbolize_names: true) + + expect(json[:id]).to be_present + expect(json[:name]).to eq('Super Vac 2020') + expect(json[:category]).to eq('Electrical appliances and equipment') + expect(json[:subcategory]).to eq('Vacuum cleaners') + expect(json[:country_of_origin]).to eq('country:GB') + end + end + + response "406", "Product not valid" do + let(:Authorization) { "Authorization #{user.api_tokens.first&.token}" } + let(:product) do + { + subcategory: 'Vacuum cleaners', + } + end + run_test! do |response| + json = JSON.parse(response.body, symbolize_names: true) + + expect(json[:errors]).to be_present + end + end + + response "401", "Unauthorised user" do + let(:Authorization) { "Authorization 0000" } + let(:id) { "invalid" } + run_test! + end end end + end diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index 9b1dfc427e..042240261d 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -35,6 +35,52 @@ non_compliant_reason: { type: :string, nullable: true } } }, + new_product: { + title: "New Product", + type: :object, + properties: { + name: { type: :string }, + brand: { type: :string }, + product_code: { type: :string, nullable: true }, + barcode: { type: :string, nullable: true }, + category: { '$ref': "#/components/schemas/product_category" }, + subcategory: { type: :string, nullable: true }, + description: { type: :string, nullable: true }, + country_of_origin: { '$ref': "#/components/schemas/country_code" }, + authenticity: { '$ref': "#/components/schemas/product_authenticity" }, + when_placed_on_market: { '$ref': "#/components/schemas/product_when_placed_on_market" }, + has_markings: { '$ref': "#/components/schemas/product_has_markings" }, + markings: { type: :array, items: { '$ref': "#/components/schemas/product_has_markings" } }, + webpage: { type: :string, nullable: true }, + } + }, + product_category: { + title: "Product (category)", + type: :string, + enum: Rails.application.config.product_constants["product_category"] + }, + product_when_placed_on_market: { + title: "Product (when_placed_on_market)", + type: :string, + enum: %w[on_or_after_2021 before_2021 unknown_date] + }, + product_authenticity: { + title: "Product (authenticity)", + type: :string, + enum: %w[genuine counterfeit unsure] + }, + product_markings: { + title: "Product (markings)", + type: :array, + items: { type: :string }, + enum: Product::MARKINGS + }, + product_has_markings: { + title: "Product (markings)", + type: :array, + items: { type: :string }, + enum: Product.has_markings.keys + }, notification_object: { title: "Notification", type: :object, @@ -68,10 +114,12 @@ brand: { type: :string }, product_code: { type: :string, nullable: true }, barcode: { type: :string, nullable: true }, - category: { type: :string }, + category: { '$ref': "#/components/schemas/product_category" }, subcategory: { type: :string, nullable: true }, - description: { type: :string }, - country_of_origin: { type: :string }, + description: { type: :string, nullable: true }, + country_of_origin: { '$ref': "#/components/schemas/country_code" }, + authenticity: { '$ref': "#/components/schemas/product_authenticity" }, + when_placed_on_market: { '$ref': "#/components/schemas/product_when_placed_on_market" }, webpage: { type: :string, nullable: true }, owning_team: { type: :object, properties: { @@ -81,6 +129,11 @@ product_images: { type: :array, items: { type: :object, properties: { url: { type: :string } } } }, }, required: %w[name] + }, + country_code: { + title: "Country", + type: :string, + enum: Country.notifying_countries.map { |c| c[1] }.sort } } }, diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml index a11606c901..b7677da0d4 100644 --- a/swagger/v1/swagger.yaml +++ b/swagger/v1/swagger.yaml @@ -26,6 +26,113 @@ components: non_compliant_reason: type: string nullable: true + new_product: + title: New Product + type: object + properties: + name: + type: string + brand: + type: string + product_code: + type: string + nullable: true + barcode: + type: string + nullable: true + category: + "$ref": "#/components/schemas/product_category" + subcategory: + type: string + nullable: true + description: + type: string + nullable: true + country_of_origin: + "$ref": "#/components/schemas/country_code" + authenticity: + "$ref": "#/components/schemas/product_authenticity" + when_placed_on_market: + "$ref": "#/components/schemas/product_when_placed_on_market" + has_markings: + "$ref": "#/components/schemas/product_has_markings" + markings: + type: array + items: + "$ref": "#/components/schemas/product_has_markings" + webpage: + type: string + nullable: true + product_category: + title: Product (category) + type: string + enum: + - Personal protective equipment (PPE) + - Chemical products + - Childcare articles and children's equipment + - Clothing, textiles and fashion items + - Communication and media equipment + - Construction products + - Cosmetics + - Decorative articles + - Eco-design + - Electrical appliances and equipment + - Explosive atmospheres equipment + - Food-imitating products + - Furniture + - Gadgets + - Gas appliances and components + - Hand tools + - Hobby / sports equipment + - Jewellery + - Kitchen / cooking accessories + - Laser pointers + - Lifts + - Lighters + - Lighting chains + - Lighting equipment + - Machinery + - Measuring instruments + - Motor vehicles (including spare parts) + - Pressure equipment / vessels + - Pyrotechnic articles + - Rail and guided transport + - Recreational crafts + - Stationery + - Toys + - Waste + product_when_placed_on_market: + title: Product (when_placed_on_market) + type: string + enum: + - on_or_after_2021 + - before_2021 + - unknown_date + product_authenticity: + title: Product (authenticity) + type: string + enum: + - genuine + - counterfeit + - unsure + product_markings: + title: Product (markings) + type: array + items: + type: string + enum: + - UKCA + - UKNI + - CE + product_has_markings: + title: Product (markings) + type: array + items: + type: string + enum: + - markings_yes + - markings_no + - markings_unknown notification_object: title: Notification type: object @@ -93,14 +200,19 @@ components: type: string nullable: true category: - type: string + "$ref": "#/components/schemas/product_category" subcategory: type: string nullable: true description: type: string + nullable: true country_of_origin: - type: string + "$ref": "#/components/schemas/country_code" + authenticity: + "$ref": "#/components/schemas/product_authenticity" + when_placed_on_market: + "$ref": "#/components/schemas/product_when_placed_on_market" webpage: type: string nullable: true @@ -122,6 +234,293 @@ components: type: string required: - name + country_code: + title: Country + type: string + enum: + - country:AD + - country:AE + - country:AF + - country:AG + - country:AL + - country:AM + - country:AO + - country:AR + - country:AT + - country:AU + - country:AZ + - country:BA + - country:BB + - country:BD + - country:BE + - country:BF + - country:BG + - country:BH + - country:BI + - country:BJ + - country:BN + - country:BO + - country:BR + - country:BS + - country:BT + - country:BW + - country:BY + - country:BZ + - country:CA + - country:CD + - country:CF + - country:CG + - country:CH + - country:CI + - country:CL + - country:CM + - country:CN + - country:CO + - country:CR + - country:CS + - country:CU + - country:CV + - country:CY + - country:CZ + - country:DD + - country:DE + - country:DJ + - country:DK + - country:DM + - country:DO + - country:DZ + - country:EC + - country:EE + - country:EG + - country:ER + - country:ES + - country:ET + - country:FI + - country:FJ + - country:FM + - country:FR + - country:GA + - country:GB + - country:GB-ENG + - country:GB-GBN + - country:GB-NIR + - country:GB-SCT + - country:GB-WLS + - country:GD + - country:GE + - country:GH + - country:GM + - country:GN + - country:GQ + - country:GR + - country:GT + - country:GW + - country:GY + - country:HN + - country:HR + - country:HT + - country:HU + - country:ID + - country:IE + - country:IL + - country:IN + - country:IQ + - country:IR + - country:IS + - country:IT + - country:JM + - country:JO + - country:JP + - country:KE + - country:KG + - country:KH + - country:KI + - country:KM + - country:KN + - country:KP + - country:KR + - country:KW + - country:KZ + - country:LA + - country:LB + - country:LC + - country:LI + - country:LK + - country:LR + - country:LS + - country:LT + - country:LU + - country:LV + - country:LY + - country:MA + - country:MC + - country:MD + - country:ME + - country:MG + - country:MH + - country:MK + - country:ML + - country:MM + - country:MN + - country:MR + - country:MT + - country:MU + - country:MV + - country:MW + - country:MX + - country:MY + - country:MZ + - country:NA + - country:NE + - country:NG + - country:NI + - country:NL + - country:NO + - country:NP + - country:NR + - country:NZ + - country:OM + - country:PA + - country:PE + - country:PG + - country:PH + - country:PK + - country:PL + - country:PT + - country:PW + - country:PY + - country:QA + - country:RO + - country:RS + - country:RU + - country:RW + - country:SA + - country:SB + - country:SC + - country:SD + - country:SE + - country:SG + - country:SI + - country:SK + - country:SL + - country:SM + - country:SN + - country:SO + - country:SR + - country:SS + - country:ST + - country:SU + - country:SV + - country:SY + - country:SZ + - country:TD + - country:TG + - country:TH + - country:TJ + - country:TL + - country:TM + - country:TN + - country:TO + - country:TR + - country:TT + - country:TV + - country:TZ + - country:UA + - country:UG + - country:US + - country:UY + - country:UZ + - country:VA + - country:VC + - country:VE + - country:VN + - country:VU + - country:WS + - country:XK + - country:YE + - country:YU + - country:ZA + - country:ZM + - country:ZW + - territory:AE-AJ + - territory:AE-AZ + - territory:AE-DU + - territory:AE-FU + - territory:AE-RK + - territory:AE-SH + - territory:AE-UQ + - territory:AI + - territory:AQ + - territory:AS + - territory:AW + - territory:AX + - territory:BAT + - territory:BL + - territory:BM + - territory:BQ-BO + - territory:BQ-SA + - territory:BQ-SE + - territory:BV + - territory:CC + - territory:CK + - territory:CW + - territory:CX + - territory:EH + - territory:ES-CE + - territory:ES-ML + - territory:FK + - territory:FO + - territory:GF + - territory:GG + - territory:GI + - territory:GL + - territory:GP + - territory:GS + - territory:GU + - territory:HK + - territory:HM + - territory:IM + - territory:IO + - territory:JE + - territory:KY + - territory:MF + - territory:MO + - territory:MP + - territory:MQ + - territory:MS + - territory:NC + - territory:NF + - territory:NU + - territory:PF + - territory:PM + - territory:PN + - territory:PR + - territory:PS + - territory:RE + - territory:SH-AC + - territory:SH-HL + - territory:SH-TA + - territory:SJ + - territory:SX + - territory:TC + - territory:TF + - territory:TK + - territory:TW + - territory:UM-67 + - territory:UM-71 + - territory:UM-76 + - territory:UM-79 + - territory:UM-81 + - territory:UM-84 + - territory:UM-86 + - territory:UM-89 + - territory:UM-95 + - territory:VG + - territory:VI + - territory:WF + - territory:XQZ + - territory:XXD + - territory:YT paths: "/api/v1/auth": post: @@ -272,3 +671,39 @@ paths: responses: '200': description: Search results returned + post: + summary: Creates a Product + description: Creates a Product in PSD + tags: + - Products + security: + - bearer: [] + parameters: + - name: Authorization + in: header + schema: + type: string + responses: + '201': + description: Product created + '406': + description: Product not valid + '401': + description: Unauthorised user + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/new_product" + examples: + product: + summary: An sample product + value: + product: + name: Super Vac 2020 + category: Electrical appliances and equipment + subcategory: Vacuum cleaners + country_of_origin: country:GB + when_placed_on_market: on_or_after_2021 + authenticity: genuine + has_markings: markings_no From 08e9a42f30d3b6ebbf2a20cdb63ea88b8d7f5477 Mon Sep 17 00:00:00 2001 From: Andy Callaghan Date: Tue, 13 Feb 2024 16:32:05 +0000 Subject: [PATCH 3/3] rubocop fix --- spec/swagger_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb index 042240261d..0fd85949e0 100644 --- a/spec/swagger_helper.rb +++ b/spec/swagger_helper.rb @@ -117,7 +117,7 @@ category: { '$ref': "#/components/schemas/product_category" }, subcategory: { type: :string, nullable: true }, description: { type: :string, nullable: true }, - country_of_origin: { '$ref': "#/components/schemas/country_code" }, + country_of_origin: { '$ref': "#/components/schemas/country_code" }, authenticity: { '$ref': "#/components/schemas/product_authenticity" }, when_placed_on_market: { '$ref': "#/components/schemas/product_when_placed_on_market" }, webpage: { type: :string, nullable: true },