From 75b2fe1dd472655b9e79c50e1a79ed5fd305e11c Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Fri, 1 Aug 2025 01:48:21 +0500 Subject: [PATCH] revert API removals --- app/controllers/api/v0/products_controller.rb | 27 +++ config/routes/api.rb | 3 + .../api/v0/products_controller_spec.rb | 176 ++++++++++++++++++ 3 files changed, 206 insertions(+) diff --git a/app/controllers/api/v0/products_controller.rb b/app/controllers/api/v0/products_controller.rb index 1ba016cc5a..6f95e1e64e 100644 --- a/app/controllers/api/v0/products_controller.rb +++ b/app/controllers/api/v0/products_controller.rb @@ -38,12 +38,39 @@ module Api end end + def destroy + authorize! :delete, Spree::Product + @product = product_finder.find_product + authorize! :delete, @product + @product.destroyed_by = current_api_user + @product.destroy + head :no_content + end + + def bulk_products + @products = product_finder.bulk_products + + render_paged_products @products + end + def overridable @products = product_finder.products_for_producers render_paged_products @products, ::Api::Admin::ProductSimpleSerializer end + # POST /api/products/:product_id/clone + # + def clone + authorize! :create, Spree::Product + original_product = product_finder.find_product_to_be_cloned + authorize! :update, original_product + + @product = original_product.duplicate + + render json: @product, serializer: Api::Admin::ProductSerializer, status: :created + end + private def product_finder diff --git a/config/routes/api.rb b/config/routes/api.rb index 725d022b98..30bd7cd1b1 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -16,6 +16,7 @@ Openfoodnetwork::Application.routes.draw do namespace :v0 do resources :products do collection do + get :bulk_products get :overridable end post :clone @@ -74,6 +75,8 @@ Openfoodnetwork::Application.routes.draw do resources :enterprise_fees, only: [:destroy] + post '/product_images/:product_id', to: 'product_images#update_product_image' + resources :states, :only => [:index, :show] resources :taxons, except: %i[show edit] diff --git a/spec/controllers/api/v0/products_controller_spec.rb b/spec/controllers/api/v0/products_controller_spec.rb index 09dab8cc70..8150cf9332 100644 --- a/spec/controllers/api/v0/products_controller_spec.rb +++ b/spec/controllers/api/v0/products_controller_spec.rb @@ -62,6 +62,33 @@ RSpec.describe Api::V0::ProductsController do api_put :update, id: product.to_param, product: { name: "I hacked your store!" } assert_unauthorized! end + + it "cannot delete a product" do + api_delete :destroy, id: product.to_param + assert_unauthorized! + end + end + + context "as an enterprise user" do + let(:current_api_user) { supplier_enterprise_user(supplier) } + + it "can delete my product" do + expect(product.deleted_at).to be_nil + api_delete :destroy, id: product.to_param + + expect(response).to have_http_status(:no_content) + expect { product.reload }.not_to raise_error + expect(product.deleted_at).not_to be_nil + end + + it "is denied access to deleting another enterprises' product" do + expect(product_other_supplier.deleted_at).to be_nil + api_delete :destroy, id: product_other_supplier.to_param + + assert_unauthorized! + expect { product_other_supplier.reload }.not_to raise_error + expect(product_other_supplier.deleted_at).to be_nil + end end context "as an administrator" do @@ -110,6 +137,155 @@ RSpec.describe Api::V0::ProductsController do expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.") expect(json_response["errors"]["name"]).to eq(["can't be blank"]) end + + it "can delete a product" do + expect(product.deleted_at).to be_nil + api_delete :destroy, id: product.to_param + + expect(response).to have_http_status(:no_content) + expect(product.reload.deleted_at).not_to be_nil + end + end + + describe '#clone' do + context 'as a normal user' do + before do + allow(current_api_user) + .to receive(:admin?).and_return(false) + end + + it 'denies access' do + spree_post :clone, product_id: product.id, format: :json + + assert_unauthorized! + end + end + + context 'as an enterprise user' do + let(:current_api_user) { supplier_enterprise_user(supplier) } + let!(:variant) { create(:variant, product_id: product.id) } + + it 'responds with a successful response' do + spree_post :clone, product_id: product.id, format: :json + + expect(response).to have_http_status(:created) + end + + it 'clones the product' do + spree_post :clone, product_id: product.id, format: :json + + expect(json_response['name']).to eq("COPY OF #{product.name}") + end + + it 'clones a product with image' do + spree_post :clone, product_id: product_with_image.id, format: :json + + expect(response).to have_http_status(:created) + expect(json_response['name']).to eq("COPY OF #{product_with_image.name}") + end + + # test cases related to bug #660: product duplication clones master variant + + # stock info - clone is set to zero + it '(does not) clone the stock info of the product' do + spree_post :clone, product_id: product.id, format: :json + expect(json_response.dig("variants", 0, "on_hand")).to eq(0) + end + + # variants: only the master variant of the product is cloned + it '(does not) clone variants from a product with several variants' do + spree_post :clone, product_id: product.id, format: :json + expect(Spree::Product.second.variants.count).not_to eq Spree::Product.first.variants.count + end + end + + context 'as an administrator' do + before do + allow(current_api_user) + .to receive(:admin?).and_return(true) + end + + it 'responds with a successful response' do + spree_post :clone, product_id: product.id, format: :json + + expect(response).to have_http_status(:created) + end + + it 'clones the product' do + spree_post :clone, product_id: product.id, format: :json + + expect(json_response['name']).to eq("COPY OF #{product.name}") + end + + it 'clones a product with image' do + spree_post :clone, product_id: product_with_image.id, format: :json + + expect(response).to have_http_status(:created) + expect(json_response['name']).to eq("COPY OF #{product_with_image.name}") + end + end + end + + describe '#bulk_products' do + context "as an enterprise user" do + let!(:taxon) { create(:taxon) } + let!(:product2) { create(:product, supplier_id: supplier.id, primary_taxon: taxon) } + let!(:product3) { create(:product, supplier_id: supplier2.id, primary_taxon: taxon) } + let!(:product4) { create(:product, supplier_id: supplier2.id) } + let(:current_api_user) { supplier_enterprise_user(supplier) } + + before { current_api_user.enterprise_roles.create(enterprise: supplier2) } + + it "returns a list of products" do + api_get :bulk_products, { page: 1, per_page: 15 }, format: :json + expect(returned_product_ids).to eq [product4.id, product3.id, product2.id, + other_product.id, product.id] + end + + it "returns pagination data" do + api_get :bulk_products, { page: 1, per_page: 15 }, format: :json + expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, + "per_page" => 15 + end + + it "uses defaults when page and per_page are not supplied" do + api_get :bulk_products, format: :json + expect(json_response['pagination']).to eq "results" => 5, "pages" => 1, "page" => 1, + "per_page" => 15 + end + + it "returns paginated products by page" do + api_get :bulk_products, { page: 1, per_page: 2 }, format: :json + expect(returned_product_ids).to eq [product4.id, product3.id] + + api_get :bulk_products, { page: 2, per_page: 2 }, format: :json + expect(returned_product_ids).to eq [product2.id, other_product.id] + end + + it "filters results by supplier" do + api_get :bulk_products, + { page: 1, per_page: 15, q: { variants_supplier_id_eq: supplier.id } }, + format: :json + expect(returned_product_ids).to eq [product2.id, other_product.id, product.id] + end + + it "filters results by product category" do + api_get :bulk_products, + { page: 1, per_page: 15, q: { variants_primary_taxon_id_eq: taxon.id } }, + format: :json + expect(returned_product_ids).to eq [product3.id, product2.id] + end + + it "filters results by import_date" do + product.variants.first.update_attribute :import_date, 1.day.ago + product2.variants.first.update_attribute :import_date, 2.days.ago + product3.variants.first.update_attribute :import_date, 1.day.ago + + api_get :bulk_products, { page: 1, per_page: 15, import_date: 1.day.ago.to_date.to_s }, + format: :json + expect(returned_product_ids).to eq [product3.id, product.id] + end + end end private