From a3d73a562795e2b5d496277efcfa91d89a81e067 Mon Sep 17 00:00:00 2001 From: Kat Date: Mon, 7 Oct 2024 14:42:54 +0100 Subject: [PATCH] Add file validation to collection resources --- .../collection_resources_controller.rb | 23 +++ app/models/collection_resource.rb | 2 +- spec/features/collection_resources_spec.rb | 142 ++++++++++++++++++ spec/fixtures/files/excel_file.xlsx | Bin 0 -> 8884 bytes spec/fixtures/files/pdf_file.pdf | Bin 0 -> 11871 bytes 5 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 spec/features/collection_resources_spec.rb create mode 100644 spec/fixtures/files/excel_file.xlsx create mode 100644 spec/fixtures/files/pdf_file.pdf diff --git a/app/controllers/collection_resources_controller.rb b/app/controllers/collection_resources_controller.rb index 3bec72371..266be6d39 100644 --- a/app/controllers/collection_resources_controller.rb +++ b/app/controllers/collection_resources_controller.rb @@ -52,6 +52,10 @@ class CollectionResourcesController < ApplicationController @collection_resource = MandatoryCollectionResourcesService.generate_resource(log_type, year, resource_type) render_not_found unless @collection_resource + validate_file(file) + + return render "collection_resources/edit" if @collection_resource.errors.any? + filename = @collection_resource.download_filename begin UploadCollectionResourcesService.upload_collection_resource(filename, file) @@ -95,4 +99,23 @@ private def resource_for_year_can_be_updated?(year) editable_collection_resource_years.include?(year) end + + def validate_file(file) + return @collection_resource.errors.add(:file, "Select which file to upload") unless file + return @collection_resource.errors.add(:file, "The file is above 100MB") if file.size > 100.megabytes + + argv = %W[file --brief --mime-type -- #{file.path}] + output = `#{argv.shelljoin}` + + case @collection_resource.resource_type + when "paper_form" + unless output.match?(/application\/pdf/) + @collection_resource.errors.add(:file, "The paper form must be a PDF.") + end + when "bulk_upload_template", "bulk_upload_specification" + unless output.match?(/application\/vnd\.ms-excel|application\/vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet/) + @collection_resource.errors.add(:file, "The #{@collection_resource.short_display_name.downcase} must be a Microsoft Excel file.") + end + end + end end diff --git a/app/models/collection_resource.rb b/app/models/collection_resource.rb index 442594d8c..d5c3c895b 100644 --- a/app/models/collection_resource.rb +++ b/app/models/collection_resource.rb @@ -2,7 +2,7 @@ class CollectionResource include ActiveModel::Model include Rails.application.routes.url_helpers - attr_accessor :resource_type, :display_name, :short_display_name, :year, :log_type, :download_filename + attr_accessor :resource_type, :display_name, :short_display_name, :year, :log_type, :download_filename, :file def download_path download_mandatory_collection_resource_path(log_type:, year:, resource_type:) diff --git a/spec/features/collection_resources_spec.rb b/spec/features/collection_resources_spec.rb new file mode 100644 index 000000000..306b4355d --- /dev/null +++ b/spec/features/collection_resources_spec.rb @@ -0,0 +1,142 @@ +require "rails_helper" + +RSpec.describe "Collection resources" do + let(:user) { create(:user, :support) } + + before do + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in user + end + + context "when uploading paper form" do + it "only allows pdf files for lettings" do + visit("/collection-resources/lettings/2024/paper_form/edit") + + click_button("Save changes") + + expect(page).to have_content("Select which file to upload") + + expect(page).to have_content("Change the paper form") + expect(page).to have_content("Lettings 2024 to 2025") + + attach_file "file", file_fixture("excel_file.xlsx") + click_button("Save changes") + + expect(page).to have_content("The paper form must be a PDF.") + + attach_file "file", file_fixture("pdf_file.pdf") + click_button("Save changes") + + expect(page).not_to have_content("The paper form must be a PDF.") + end + + it "only allows pdf files for sales" do + visit("/collection-resources/sales/2024/paper_form/edit") + + click_button("Save changes") + + expect(page).to have_content("Select which file to upload") + + expect(page).to have_content("Change the paper form") + expect(page).to have_content("Sales 2024 to 2025") + + attach_file "file", file_fixture("excel_file.xlsx") + click_button("Save changes") + + expect(page).to have_content("The paper form must be a PDF.") + + attach_file "file", file_fixture("pdf_file.pdf") + click_button("Save changes") + + expect(page).not_to have_content("The paper form must be a PDF.") + end + end + + context "when uploading bu template" do + it "only allows excel files for lettings" do + visit("/collection-resources/lettings/2024/bulk_upload_template/edit") + + click_button("Save changes") + + expect(page).to have_content("Select which file to upload") + + expect(page).to have_content("Change the bulk upload template") + expect(page).to have_content("Lettings 2024 to 2025") + + attach_file "file", file_fixture("pdf_file.pdf") + click_button("Save changes") + + expect(page).to have_content("The bulk upload template must be a Microsoft Excel file.") + + attach_file "file", file_fixture("excel_file.xlsx") + click_button("Save changes") + + expect(page).not_to have_content("The bulk upload template must be a Microsoft Excel file.") + end + + it "only allows excel files for sales" do + visit("/collection-resources/sales/2024/bulk_upload_template/edit") + + click_button("Save changes") + + expect(page).to have_content("Select which file to upload") + + expect(page).to have_content("Change the bulk upload template") + expect(page).to have_content("Sales 2024 to 2025") + + attach_file "file", file_fixture("pdf_file.pdf") + click_button("Save changes") + + expect(page).to have_content("The bulk upload template must be a Microsoft Excel file.") + + attach_file "file", file_fixture("excel_file.xlsx") + click_button("Save changes") + + expect(page).not_to have_content("The bulk upload template must be a Microsoft Excel file.") + end + end + + context "when uploading bu specification" do + it "only allows excel files for lettings" do + visit("/collection-resources/lettings/2024/bulk_upload_specification/edit") + + click_button("Save changes") + + expect(page).to have_content("Select which file to upload") + + expect(page).to have_content("Change the bulk upload specification") + expect(page).to have_content("Lettings 2024 to 2025") + + attach_file "file", file_fixture("pdf_file.pdf") + click_button("Save changes") + + expect(page).to have_content("The bulk upload specification must be a Microsoft Excel file.") + + attach_file "file", file_fixture("excel_file.xlsx") + click_button("Save changes") + + expect(page).not_to have_content("The bulk upload specification must be a Microsoft Excel file.") + end + + it "only allows excel files for sales" do + visit("/collection-resources/sales/2024/bulk_upload_specification/edit") + + click_button("Save changes") + + expect(page).to have_content("Select which file to upload") + + expect(page).to have_content("Change the bulk upload specification") + expect(page).to have_content("Sales 2024 to 2025") + + attach_file "file", file_fixture("pdf_file.pdf") + click_button("Save changes") + + expect(page).to have_content("The bulk upload specification must be a Microsoft Excel file.") + + attach_file "file", file_fixture("excel_file.xlsx") + click_button("Save changes") + + expect(page).not_to have_content("The bulk upload specification must be a Microsoft Excel file.") + end + end +end diff --git a/spec/fixtures/files/excel_file.xlsx b/spec/fixtures/files/excel_file.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..dcc5aaaf34a86215de60383dc5b8bece6aaa0abe GIT binary patch literal 8884 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 literal 0 HcmV?d00001 diff --git a/spec/fixtures/files/pdf_file.pdf b/spec/fixtures/files/pdf_file.pdf new file mode 100644 index 0000000000000000000000000000000000000000..65a01d0a996c342b3b07ed73877d437004eb45d0 GIT binary patch literal 11871 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 literal 0 HcmV?d00001