Browse Source

CLDC-3518 Add collection resources page (#2673)

* Dynamically display collection resources for years

* Display mandatory resources on collection resources page

* Move download resources to collection resources controller and refactor routes

* Put managing resources button behind a feature flag

* Everyone should be able to download mandatory resources

* Remove mentions of legacy template and update bu path methods

* Update styling

* Update tests and routes

* CLDC-3518 Allow updating mandatory collection resources (#2676)

* Add update resource page

* Allow updating mandatory collection resources

* CLDC-3518 Add file validation to collection resources (#2678)

* Add file validation to collection resources

* Move some tests to feature tests

* Extract validations into translations file

* Rebase changes

* More rebase updates

* Read file

* Update tests
pull/2683/head
kosiakkatrina 3 months ago committed by GitHub
parent
commit
932e766cf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 112
      app/controllers/collection_resources_controller.rb
  2. 67
      app/controllers/start_controller.rb
  3. 47
      app/helpers/collection_resources_helper.rb
  4. 10
      app/models/collection_resource.rb
  5. 8
      app/models/forms/bulk_upload_lettings/guidance.rb
  6. 21
      app/models/forms/bulk_upload_lettings/prepare_your_file.rb
  7. 8
      app/models/forms/bulk_upload_sales/guidance.rb
  8. 21
      app/models/forms/bulk_upload_sales/prepare_your_file.rb
  9. 8
      app/services/collection_resources_service.rb
  10. 8
      app/services/feature_toggle.rb
  11. 56
      app/services/mandatory_collection_resources_service.rb
  12. 7
      app/services/storage/s3_service.rb
  13. 4
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb
  14. 1
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb
  15. 2
      app/views/bulk_upload_shared/guidance.html.erb
  16. 32
      app/views/collection_resources/_collection_resource_summary_list.erb
  17. 28
      app/views/collection_resources/edit.html.erb
  18. 21
      app/views/collection_resources/index.html.erb
  19. 117
      app/views/layouts/_collection_resources.html.erb
  20. 8
      config/locales/en.yml
  21. 25
      config/routes.rb
  22. 10
      spec/factories/collection_resource.rb
  23. 168
      spec/features/collection_resources_spec.rb
  24. BIN
      spec/fixtures/files/excel_file.xlsx
  25. BIN
      spec/fixtures/files/pdf_file.pdf
  26. 132
      spec/helpers/collection_resources_helper_spec.rb
  27. 302
      spec/requests/collection_resources_controller_spec.rb
  28. 24
      spec/requests/start_controller_spec.rb
  29. 19
      spec/services/collection_resources_service_spec.rb
  30. 42
      spec/services/mandatory_collection_resources_service_spec.rb

112
app/controllers/collection_resources_controller.rb

@ -0,0 +1,112 @@
class CollectionResourcesController < ApplicationController
include CollectionResourcesHelper
before_action :authenticate_user!, except: %i[download_mandatory_collection_resource]
def index
render_not_found unless current_user.support?
@mandatory_lettings_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("lettings", editable_collection_resource_years)
@mandatory_sales_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("sales", editable_collection_resource_years)
end
def download_mandatory_collection_resource
log_type = params[:log_type]
year = params[:year].to_i
resource_type = params[:resource_type]
return render_not_found unless resource_for_year_can_be_downloaded?(year)
resource = MandatoryCollectionResourcesService.generate_resource(log_type, year, resource_type)
return render_not_found unless resource
download_resource(resource.download_filename)
end
def edit
return render_not_found unless current_user.support?
year = params[:year].to_i
resource_type = params[:resource_type]
log_type = params[:log_type]
return render_not_found unless resource_for_year_can_be_updated?(year)
@collection_resource = MandatoryCollectionResourcesService.generate_resource(log_type, year, resource_type)
return render_not_found unless @collection_resource
render "collection_resources/edit"
end
def update
return render_not_found unless current_user.support?
year = resource_params[:year].to_i
resource_type = resource_params[:resource_type]
log_type = resource_params[:log_type]
file = resource_params[:file]
return render_not_found unless resource_for_year_can_be_updated?(year)
@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
CollectionResourcesService.new.upload_collection_resource(filename, file)
rescue StandardError
@collection_resource.errors.add(:file, :error_uploading)
return render "collection_resources/edit"
end
flash[:notice] = "The #{log_type} #{text_year_range_format(year)} #{@collection_resource.short_display_name.downcase} has been updated"
redirect_to collection_resources_path
end
private
def resource_params
params.require(:collection_resource).permit(:year, :log_type, :resource_type, :file)
end
def download_resource(filename)
file = CollectionResourcesService.new.get_file(filename)
return render_not_found unless file
send_data(file, disposition: "attachment", filename:)
end
def resource_for_year_can_be_downloaded?(year)
return true if current_user&.support? && editable_collection_resource_years.include?(year)
displayed_collection_resource_years.include?(year)
end
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, :blank) unless file
return @collection_resource.errors.add(:file, :above_100_mb) 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, :must_be_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, :must_be_xlsx, resource: @collection_resource.short_display_name.downcase)
end
end
end
end

67
app/controllers/start_controller.rb

@ -2,74 +2,11 @@ class StartController < ApplicationController
include CollectionResourcesHelper
def index
@mandatory_lettings_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("lettings", displayed_collection_resource_years)
@mandatory_sales_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("sales", displayed_collection_resource_years)
if current_user
@homepage_presenter = HomepagePresenter.new(current_user)
render "home/index"
end
end
def download_24_25_sales_form
download_resource("2024_25_sales_paper_form.pdf", "2024-25 Sales paper form.pdf")
end
def download_23_24_sales_form
download_resource("2023_24_sales_paper_form.pdf", "2023-24 Sales paper form.pdf")
end
def download_24_25_lettings_form
download_resource("2024_25_lettings_paper_form.pdf", "2024-25 Lettings paper form.pdf")
end
def download_23_24_lettings_form
download_resource("2023_24_lettings_paper_form.pdf", "2023-24 Lettings paper form.pdf")
end
def download_24_25_lettings_bulk_upload_template
download_resource("bulk-upload-lettings-template-2024-25.xlsx", "2024-25-lettings-bulk-upload-template.xlsx")
end
def download_24_25_lettings_bulk_upload_specification
download_resource("bulk-upload-lettings-specification-2024-25.xlsx", "2024-25-lettings-bulk-upload-specification.xlsx")
end
def download_24_25_sales_bulk_upload_template
download_resource("bulk-upload-sales-template-2024-25.xlsx", "2024-25-sales-bulk-upload-template.xlsx")
end
def download_24_25_sales_bulk_upload_specification
download_resource("bulk-upload-sales-specification-2024-25.xlsx", "2024-25-sales-bulk-upload-specification.xlsx")
end
def download_23_24_lettings_bulk_upload_template
download_resource("bulk-upload-lettings-template-2023-24.xlsx", "2023-24-lettings-bulk-upload-template.xlsx")
end
def download_23_24_lettings_bulk_upload_legacy_template
download_resource("bulk-upload-lettings-legacy-template-2023-24.xlsx", "2023-24-lettings-bulk-upload-legacy-template.xlsx")
end
def download_23_24_lettings_bulk_upload_specification
download_resource("bulk-upload-lettings-specification-2023-24.xlsx", "2023-24-lettings-bulk-upload-specification.xlsx")
end
def download_23_24_sales_bulk_upload_template
download_resource("bulk-upload-sales-template-2023-24.xlsx", "2023-24-sales-bulk-upload-template.xlsx")
end
def download_23_24_sales_bulk_upload_legacy_template
download_resource("bulk-upload-sales-legacy-template-2023-24.xlsx", "2023-24-sales-bulk-upload-legacy-template.xlsx")
end
def download_23_24_sales_bulk_upload_specification
download_resource("bulk-upload-sales-specification-2023-24.xlsx", "2023-24-sales-bulk-upload-specification.xlsx")
end
private
def download_resource(filename, download_filename)
file = CollectionResourcesService.new.get_file(filename)
return render_not_found unless file
send_data(file, disposition: "attachment", filename: download_filename)
end
end

47
app/helpers/collection_resources_helper.rb

@ -1,4 +1,6 @@
module CollectionResourcesHelper
include CollectionTimeHelper
HUMAN_READABLE_CONTENT_TYPE = { "application/pdf": "PDF",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Microsoft Excel",
"application/vnd.ms-excel": "Microsoft Excel (Old Format)",
@ -19,4 +21,49 @@ module CollectionResourcesHelper
file_type = HUMAN_READABLE_CONTENT_TYPE[metadata["content_type"].to_sym] || "Unknown File Type"
[file_type, file_size, file_pages].compact.join(", ")
end
def displayed_collection_resource_years
return [previous_collection_start_year, current_collection_start_year] if FormHandler.instance.in_edit_crossover_period?
[current_collection_start_year]
end
def editable_collection_resource_years
return [previous_collection_start_year, current_collection_start_year] if FormHandler.instance.in_edit_crossover_period?
return [next_collection_start_year, current_collection_start_year] if (Time.zone.today >= Time.zone.local(Time.zone.today.year, 1, 1) && Time.zone.today < Time.zone.local(Time.zone.today.year, 4, 1)) || FeatureToggle.allow_future_resource_updates?
[current_collection_start_year]
end
def year_range_format(year)
"#{year % 100}/#{(year + 1) % 100}"
end
def text_year_range_format(year)
"#{year} to #{year + 1}"
end
def document_list_component_items(resources)
resources.map do |resource|
{
name: "Download the #{resource.display_name}",
href: resource.download_path,
metadata: file_type_size_and_pages(resource.download_filename),
}
end
end
def document_list_edit_component_items(resources)
resources.map do |resource|
{
name: resource.download_filename,
href: resource.download_path,
metadata: file_type_size_and_pages(resource.download_filename),
}
end
end
def file_exists_on_s3?(file)
CollectionResourcesService.new.file_exists_on_s3?(file)
end
end

10
app/models/collection_resource.rb

@ -0,0 +1,10 @@
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, :file
def download_path
download_mandatory_collection_resource_path(log_type:, year:, resource_type:)
end
end

8
app/models/forms/bulk_upload_lettings/guidance.rb

@ -24,10 +24,6 @@ module Forms
end
end
def lettings_legacy_template_path
Forms::BulkUploadLettings::PrepareYourFile.new.legacy_template_path
end
def lettings_template_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).template_path
end
@ -36,10 +32,6 @@ module Forms
Forms::BulkUploadLettings::PrepareYourFile.new(year:).specification_path
end
def sales_legacy_template_path
Forms::BulkUploadSales::PrepareYourFile.new.legacy_template_path
end
def sales_template_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).template_path
end

21
app/models/forms/bulk_upload_lettings/prepare_your_file.rb

@ -32,29 +32,12 @@ module Forms
bulk_upload_lettings_log_path(id: "upload-your-file", form: { year:, needstype:, organisation_id: }.compact)
end
def legacy_template_path
case year
when 2023
download_23_24_lettings_bulk_upload_legacy_template_path
end
end
def template_path
case year
when 2023
download_23_24_lettings_bulk_upload_template_path
when 2024
download_24_25_lettings_bulk_upload_template_path
end
download_mandatory_collection_resource_path(year:, log_type: "lettings", resource_type: "bulk_upload_template")
end
def specification_path
case year
when 2023
download_23_24_lettings_bulk_upload_specification_path
when 2024
download_24_25_lettings_bulk_upload_specification_path
end
download_mandatory_collection_resource_path(year:, log_type: "lettings", resource_type: "bulk_upload_specification")
end
def year_combo

8
app/models/forms/bulk_upload_sales/guidance.rb

@ -24,10 +24,6 @@ module Forms
end
end
def lettings_legacy_template_path
Forms::BulkUploadLettings::PrepareYourFile.new.legacy_template_path
end
def lettings_template_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).template_path
end
@ -36,10 +32,6 @@ module Forms
Forms::BulkUploadLettings::PrepareYourFile.new(year:).specification_path
end
def sales_legacy_template_path
Forms::BulkUploadSales::PrepareYourFile.new.legacy_template_path
end
def sales_template_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).template_path
end

21
app/models/forms/bulk_upload_sales/prepare_your_file.rb

@ -31,29 +31,12 @@ module Forms
bulk_upload_sales_log_path(id: "upload-your-file", form: { year:, organisation_id: }.compact)
end
def legacy_template_path
case year
when 2023
download_23_24_sales_bulk_upload_legacy_template_path
end
end
def template_path
case year
when 2023
download_23_24_sales_bulk_upload_template_path
when 2024
download_24_25_sales_bulk_upload_template_path
end
download_mandatory_collection_resource_path(year:, log_type: "sales", resource_type: "bulk_upload_template")
end
def specification_path
case year
when 2023
download_23_24_sales_bulk_upload_specification_path
when 2024
download_24_25_sales_bulk_upload_specification_path
end
download_mandatory_collection_resource_path(year:, log_type: "sales", resource_type: "bulk_upload_specification")
end
def year_combo

8
app/services/collection_resources_service.rb

@ -18,4 +18,12 @@ class CollectionResourcesService
rescue StandardError
nil
end
def file_exists_on_s3?(file)
@storage_service.file_exists?(file)
end
def upload_collection_resource(filename, file)
@storage_service.write_file(filename, file)
end
end

8
app/services/feature_toggle.rb

@ -38,4 +38,12 @@ class FeatureToggle
def self.local_storage?
Rails.env.development?
end
def self.allow_future_resource_updates?
!Rails.env.production? && !Rails.env.test?
end
def self.managing_resources_enabled?
!Rails.env.production?
end
end

56
app/services/mandatory_collection_resources_service.rb

@ -0,0 +1,56 @@
class MandatoryCollectionResourcesService
MANDATORY_RESOURCES = %w[paper_form bulk_upload_template bulk_upload_specification].freeze
def self.generate_resources(log_type, collection_years)
mandatory_resources_per_year = {}
collection_years.map do |year|
mandatory_resources_per_year[year] = resources_per_year(year, log_type)
end
mandatory_resources_per_year
end
def self.resources_per_year(year, log_type)
MANDATORY_RESOURCES.map do |resource|
generate_resource(log_type, year, resource)
end
end
def self.generate_resource(log_type, year, resource_type)
return unless log_type && year && resource_type
return unless %w[lettings sales].include?(log_type)
return unless MANDATORY_RESOURCES.include?(resource_type)
CollectionResource.new(
resource_type:,
display_name: display_name(resource_type, year, log_type),
short_display_name: resource_type.humanize,
year:,
log_type:,
download_filename: download_filename(resource_type, year, log_type),
)
end
def self.display_name(resource, year, log_type)
year_range = "#{year} to #{year + 1}"
case resource
when "paper_form"
"#{log_type} log for tenants (#{year_range})"
when "bulk_upload_template"
"#{log_type} bulk upload template (#{year_range})"
when "bulk_upload_specification"
"#{log_type} bulk upload specification (#{year_range})"
end
end
def self.download_filename(resource, year, log_type)
year_range = "#{year}_#{(year + 1) % 100}"
case resource
when "paper_form"
"#{year_range}_#{log_type}_paper_form.pdf"
when "bulk_upload_template"
"bulk-upload-#{log_type}-template-#{year_range.dasherize}.xlsx"
when "bulk_upload_specification"
"bulk-upload-#{log_type}-specification-#{year_range.dasherize}.xlsx"
end
end
end

7
app/services/storage/s3_service.rb

@ -48,6 +48,13 @@ module Storage
@client.head_object(bucket: @configuration.bucket_name, key: file_name)
end
def file_exists?(file_name)
@client.head_object(bucket: @configuration.bucket_name, key: file_name)
true
rescue Aws::S3::Errors::NotFound
false
end
private
def create_configuration

4
app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb

@ -18,10 +18,6 @@
<li>
<%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form.
</li>
<li>
<%= govuk_link_to "Download the legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end.
</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>

1
app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb

@ -16,7 +16,6 @@
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p>
<ul class="govuk-list govuk-list--bullet">
<li><%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form.</li>
<li><%= govuk_link_to "Download the legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end.</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>

2
app/views/bulk_upload_shared/guidance.html.erb

@ -52,8 +52,6 @@
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2023 to 2024) – New question ordering", @form.sales_template_path %></p>
<p class="govuk-body"><strong>Legacy template</strong>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end. Use this template if you have not updated your system to match the new template yet.</p>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2023 to 2024) - Legacy version", @form.lettings_legacy_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2023 to 2024) – Legacy version", @form.sales_legacy_template_path %></p>
<% else %>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2024 to 2025)", @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2024 to 2025)", @form.sales_template_path %></p>

32
app/views/collection_resources/_collection_resource_summary_list.erb

@ -0,0 +1,32 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= govuk_summary_list do |summary_list| %>
<% mandatory_resources.each do |resource| %>
<% summary_list.with_row do |row| %>
<% row.with_key { resource.short_display_name } %>
<% if file_exists_on_s3?(resource.download_filename) %>
<% row.with_value do %>
<%= render DocumentListComponent.new(items: document_list_edit_component_items([resource]), label: "") %>
<% end %>
<% row.with_action(
text: "Change",
href: edit_mandatory_collection_resource_path(year: resource.year, log_type: resource.log_type, resource_type: resource.resource_type),
) %>
<% else %>
<% row.with_value do %>
<p class="govuk-body app-!-colour-muted">No file uploaded</p>
<% end %>
<% row.with_action(
text: "Upload",
href: "/",
) %>
<% end %>
<% end %>
<% end %>
<% end %>
<div class="govuk-!-margin-bottom-6">
<%= govuk_link_to "Add new #{mandatory_resources.first.log_type} #{text_year_range_format(mandatory_resources.first.year)} resource", href: "/" %>
</div>
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
</div>
</div>

28
app/views/collection_resources/edit.html.erb

@ -0,0 +1,28 @@
<% content_for :before_content do %>
<%= govuk_back_link href: collection_resources_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: @collection_resource, url: update_mandatory_collection_resource_path, method: :patch do |f| %>
<%= f.hidden_field :year %>
<%= f.hidden_field :log_type %>
<%= f.hidden_field :resource_type %>
<%= f.govuk_error_summary %>
<span class="govuk-caption-l"><%= "#{@collection_resource.log_type.humanize} #{text_year_range_format(@collection_resource.year)}" %></span>
<h1 class="govuk-heading-l">Change the <%= @collection_resource.resource_type.humanize.downcase %></h1>
<p class="govuk-body">
This file will be available for all users to download.
</p>
<%= f.govuk_file_field :file,
label: { text: "Upload file", size: "m" } %>
<%= f.govuk_submit "Save changes" %>
<%= govuk_button_link_to "Cancel", collection_resources_path, secondary: true %>
<% end %>
</div>
</div>

21
app/views/collection_resources/index.html.erb

@ -0,0 +1,21 @@
<% title = "Collection resources" %>
<% content_for :title, title %>
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<h1 class="govuk-heading-l"><%= title %></h1>
<% @mandatory_lettings_collection_resources_per_year.each do |year, mandatory_resources| %>
<h2 class="govuk-heading-m">
Lettings <%= text_year_range_format(year) %>
</h2>
<%= render partial: "collection_resource_summary_list", locals: { mandatory_resources: } %>
<% end %>
<% @mandatory_sales_collection_resources_per_year.each do |year, mandatory_resources| %>
<h2 class="govuk-heading-m">
Sales <%= text_year_range_format(year) %>
</h2>
<%= render partial: "collection_resource_summary_list", locals: { mandatory_resources: } %>
<% end %>

117
app/views/layouts/_collection_resources.html.erb

@ -5,113 +5,22 @@
<% else %>
<h2 class="govuk-heading-m">Collection resources</h2>
<% end %>
<p class="govuk-body">Use the 2024 to 2025 forms for lettings that start and sales that complete between 1 April 2024 and 31 March 2025.</p>
<% if FormHandler.instance.lettings_form_for_start_year(2023) && FormHandler.instance.lettings_form_for_start_year(2023).edit_end_date > Time.zone.today %>
<p class="govuk-body">Use the 2023 to 2024 forms for lettings that start and sales that complete between 1 April 2023 and 31 March 2024.</p>
<% displayed_collection_resource_years.each do |collection_start_year| %>
<p class="govuk-body">Use the <%= collection_start_year %> to <%= collection_start_year + 1 %> forms for lettings that start and sales that complete between 1 April <%= collection_start_year %> and 31 March <%= collection_start_year + 1 %>.</p>
<% end %>
<div class="app-tab__list-view">
<%= govuk_tabs(title: "Collection resources", classes: %w[app-tab__small-headers]) do |c| %>
<% if FormHandler.instance.lettings_form_for_start_year(2024) && FormHandler.instance.lettings_form_for_start_year(2024).edit_end_date > Time.zone.today %>
<% c.with_tab(label: "Lettings 24/25") do %>
<%= render DocumentListComponent.new(
items: [
{
name: "Download the lettings log for tenants (2024 to 2025)",
href: download_24_25_lettings_form_path,
metadata: file_type_size_and_pages("2024_25_lettings_paper_form.pdf", number_of_pages: 8),
},
{
name: "Download the lettings bulk upload template (2024 to 2025)",
href: download_24_25_lettings_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-template-2024-25.xlsx"),
},
{
name: "Download the lettings bulk upload specification (2024 to 2025)",
href: download_24_25_lettings_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-specification-2024-25.xlsx"),
},
],
label: "Lettings 2024 to 2025",
) %>
<%= govuk_tabs(title: "Collection resources", classes: %w[app-tab__small-headers]) do |c| %>
<% @mandatory_lettings_collection_resources_per_year.each do |year, resources| %>
<% c.with_tab(label: "Lettings #{year_range_format(year)}") do %>
<%= render DocumentListComponent.new(items: document_list_component_items(resources), label: "Lettings #{text_year_range_format(year)}") %>
<% end %>
<% end %>
<% c.with_tab(label: "Sales 24/25") do %>
<%= render DocumentListComponent.new(
items: [
{
name: "Download the sales log for buyers (2024 to 2025)",
href: download_24_25_sales_form_path,
metadata: file_type_size_and_pages("2024_25_sales_paper_form.pdf", number_of_pages: 8),
},
{
name: "Download the sales bulk upload template (2024 to 2025)",
href: download_24_25_sales_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-sales-template-2024-25.xlsx"),
},
{
name: "Download the sales bulk upload specification (2024 to 2025)",
href: download_24_25_sales_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-sales-specification-2024-25.xlsx"),
},
],
label: "Sales 2024 to 2025",
) %>
<% @mandatory_sales_collection_resources_per_year.each do |year, resources| %>
<% c.with_tab(label: "Sales #{year_range_format(year)}") do %>
<%= render DocumentListComponent.new(items: document_list_component_items(resources), label: "Sales #{text_year_range_format(year)}") %>
<% end %>
<% end %>
<% end %>
<% if FormHandler.instance.lettings_form_for_start_year(2023) && FormHandler.instance.lettings_form_for_start_year(2023).edit_end_date > Time.zone.today %>
<% c.with_tab(label: "Lettings 23/24") do %>
<%= render DocumentListComponent.new(
items: [
{
name: "Download the lettings log for tenants (2023 to 2024)",
href: download_23_24_lettings_form_path,
metadata: file_type_size_and_pages("2023_24_lettings_paper_form.pdf", number_of_pages: 8),
},
{
name: "Download the lettings bulk upload template (2023 to 2024) – New question ordering",
href: download_23_24_lettings_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-template-2023-24.xlsx"),
},
{
name: "Download the lettings bulk upload template (2023 to 2024) – Legacy version",
href: download_23_24_lettings_bulk_upload_legacy_template_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-legacy-template-2023-24.xlsx"),
},
{
name: "Download the lettings bulk upload specification (2023 to 2024)",
href: download_23_24_lettings_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-specification-2023-24.xlsx"),
},
],
label: "Lettings 2023 to 2024",
) %>
<% end %>
<% c.with_tab(label: "Sales 23/24") do %>
<%= render DocumentListComponent.new(
items: [
{
name: "Download the sales log for buyers (2023 to 2024)",
href: download_23_24_sales_form_path,
metadata: file_type_size_and_pages("2023_24_sales_paper_form.pdf", number_of_pages: 8),
},
{
name: "Download the sales bulk upload template (2023 to 2024) – New question ordering",
href: download_23_24_sales_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-sales-template-2023-24.xlsx"),
},
{
name: "Download the sales bulk upload template (2023 to 2024) – Legacy version",
href: download_23_24_sales_bulk_upload_legacy_template_path,
metadata: file_type_size_and_pages("bulk-upload-sales-legacy-template-2023-24.xlsx"),
},
{
name: "Download the sales bulk upload specification (2023 to 2024)",
href: download_23_24_sales_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-sales-specification-2023-24.xlsx"),
},
],
label: "Sales 2023 to 2024",
) %>
<% end %>
<% end %>
<% end %>
</div>
<%= govuk_button_link_to "Manage collection resources", collection_resources_path, secondary: true, class: "govuk-!-margin-bottom-2" if current_user&.support? && FeatureToggle.managing_resources_enabled? %>

8
config/locales/en.yml

@ -117,6 +117,14 @@ en:
attributes:
confirm_soft_errors:
blank: "You must select if there are errors in these fields."
collection_resource:
attributes:
file:
error_uploading: There was an error uploading this file.
blank: Select which file to upload.
above_100_mb: The file is above 100MB.
must_be_pdf: The paper form must be a PDF.
must_be_xlsx: The %{resource} must be a Microsoft Excel file.
activerecord:
attributes:

25
config/routes.rb

@ -40,23 +40,14 @@ Rails.application.routes.draw do
get "/service-moved", to: "maintenance#service_moved"
get "/service-unavailable", to: "maintenance#service_unavailable"
get "/download-23-24-lettings-form", to: "start#download_23_24_lettings_form"
get "/download-23-24-lettings-bulk-upload-template", to: "start#download_23_24_lettings_bulk_upload_template"
get "/download-23-24-lettings-bulk-upload-legacy-template", to: "start#download_23_24_lettings_bulk_upload_legacy_template"
get "/download-23-24-lettings-bulk-upload-specification", to: "start#download_23_24_lettings_bulk_upload_specification"
get "/download-23-24-sales-form", to: "start#download_23_24_sales_form"
get "/download-23-24-sales-bulk-upload-template", to: "start#download_23_24_sales_bulk_upload_template"
get "/download-23-24-sales-bulk-upload-legacy-template", to: "start#download_23_24_sales_bulk_upload_legacy_template"
get "/download-23-24-sales-bulk-upload-specification", to: "start#download_23_24_sales_bulk_upload_specification"
get "/download-24-25-lettings-form", to: "start#download_24_25_lettings_form"
get "/download-24-25-lettings-bulk-upload-template", to: "start#download_24_25_lettings_bulk_upload_template"
get "/download-24-25-lettings-bulk-upload-specification", to: "start#download_24_25_lettings_bulk_upload_specification"
get "/download-24-25-sales-form", to: "start#download_24_25_sales_form"
get "/download-24-25-sales-bulk-upload-template", to: "start#download_24_25_sales_bulk_upload_template"
get "/download-24-25-sales-bulk-upload-specification", to: "start#download_24_25_sales_bulk_upload_specification"
get "collection-resources", to: "collection_resources#index"
get "/collection-resources/:log_type/:year/:resource_type/download", to: "collection_resources#download_mandatory_collection_resource", as: :download_mandatory_collection_resource
get "/collection-resources/:log_type/:year/:resource_type/edit", to: "collection_resources#edit", as: :edit_mandatory_collection_resource
patch "/collection-resources", to: "collection_resources#update", as: :update_mandatory_collection_resource
resources :collection_resources, path: "/collection-resources" do
get "/download", to: "collection_resources#download_additional_collection_resource" # when we get to adding them
end
get "clear-filters", to: "sessions#clear_filters"

10
spec/factories/collection_resource.rb

@ -0,0 +1,10 @@
FactoryBot.define do
factory :collection_resource, class: "CollectionResource" do
resource_type { "paper_form" }
display_name { "lettings log for tenants (2021 to 2022)" }
short_display_name { "Paper Form" }
year { 2024 }
log_type { "lettings" }
download_filename { "24_25_lettings_paper_form.pdf" }
end
end

168
spec/features/collection_resources_spec.rb

@ -0,0 +1,168 @@
require "rails_helper"
RSpec.describe "Collection resources" do
let(:user) { create(:user, :support) }
let(:collection_resources_service) { instance_double(CollectionResourcesService, file_exists_on_s3?: true) }
before do
allow(CollectionResourcesService).to receive(:new).and_return(collection_resources_service)
allow(collection_resources_service).to receive(:upload_collection_resource)
allow(collection_resources_service).to receive(:get_file_metadata).and_return({ "content_type" => "application/pdf", "content_length" => 1000 })
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.")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("2024_25_lettings_paper_form.pdf", anything)
expect(page).to have_content("The lettings 2024 to 2025 paper form has been updated")
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.")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("2024_25_sales_paper_form.pdf", anything)
expect(page).to have_content("The sales 2024 to 2025 paper form has been updated")
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.")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("bulk-upload-lettings-template-2024-25.xlsx", anything)
expect(page).to have_content("The lettings 2024 to 2025 bulk upload template has been updated")
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.")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("bulk-upload-sales-template-2024-25.xlsx", anything)
expect(page).to have_content("The sales 2024 to 2025 bulk upload template has been updated")
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.")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("bulk-upload-lettings-specification-2024-25.xlsx", anything)
expect(page).to have_content("The lettings 2024 to 2025 bulk upload specification has been updated")
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.")
expect(collection_resources_service).to have_received(:upload_collection_resource).with("bulk-upload-sales-specification-2024-25.xlsx", anything)
expect(page).to have_content("The sales 2024 to 2025 bulk upload specification has been updated")
end
it "displays error message if the upload fails" do
allow(collection_resources_service).to receive(:upload_collection_resource).and_raise(StandardError)
visit("/collection-resources/sales/2024/bulk_upload_specification/edit")
attach_file "file", file_fixture("excel_file.xlsx")
click_button("Save changes")
expect(page).to have_content("There was an error uploading this file.")
end
end
end

BIN
spec/fixtures/files/excel_file.xlsx vendored

Binary file not shown.

BIN
spec/fixtures/files/pdf_file.pdf vendored

Binary file not shown.

132
spec/helpers/collection_resources_helper_spec.rb

@ -31,4 +31,136 @@ RSpec.describe CollectionResourcesHelper do
end
end
end
describe "#editable_collection_resource_years" do
context "when in crossover period" do
before do
allow(FormHandler.instance).to receive(:in_edit_crossover_period?).and_return(true)
allow(Time.zone).to receive(:today).and_return(Time.zone.local(2024, 4, 8))
end
it "returns previous and current years" do
expect(editable_collection_resource_years).to eq([2023, 2024])
end
end
context "when not in crossover period" do
before do
allow(FormHandler.instance).to receive(:in_edit_crossover_period?).and_return(false)
end
context "and after 1st January" do
before do
allow(Time.zone).to receive(:today).and_return(Time.zone.local(2025, 2, 1))
end
it "returns current and next years" do
expect(editable_collection_resource_years).to match_array([2024, 2025])
end
end
context "and before 1st January" do
before do
allow(Time.zone).to receive(:today).and_return(Time.zone.local(2024, 12, 1))
end
it "returns current year" do
expect(editable_collection_resource_years).to eq([2024])
end
end
end
end
describe "#displayed_collection_resource_years" do
context "when in crossover period" do
before do
allow(FormHandler.instance).to receive(:in_edit_crossover_period?).and_return(true)
allow(Time.zone).to receive(:today).and_return(Time.zone.local(2024, 4, 8))
end
it "returns previous and current years" do
expect(displayed_collection_resource_years).to eq([2023, 2024])
end
end
context "when not in crossover period" do
before do
allow(FormHandler.instance).to receive(:in_edit_crossover_period?).and_return(false)
end
it "returns current year" do
expect(displayed_collection_resource_years).to eq([2024])
end
end
end
describe "#year_range_format" do
it "returns formatted year range" do
expect(year_range_format(2023)).to eq("23/24")
end
end
describe "#text_year_range_format" do
it "returns formatted text year range" do
expect(text_year_range_format(2023)).to eq("2023 to 2024")
end
end
describe "#document_list_component_items" do
let(:resources) do
[
build(:collection_resource, year: 2023, resource_type: "paper_form", display_name: "lettings log for tenants (2023 to 2024)", download_filename: "2023_24_lettings_paper_form.pdf"),
build(:collection_resource, year: 2023, resource_type: "bulk_upload_template", display_name: "bulk upload template (2023 to 2024)", download_filename: "2023_24_lettings_bulk_upload_template.xlsx"),
]
end
before do
allow(storage_service).to receive(:get_file_metadata).with("2023_24_lettings_paper_form.pdf").and_return("content_length" => 292_864, "content_type" => "application/pdf")
allow(storage_service).to receive(:get_file_metadata).with("2023_24_lettings_bulk_upload_template.xlsx").and_return("content_length" => 19_456, "content_type" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
end
it "returns component items" do
expect(document_list_component_items(resources)).to eq([
{
name: "Download the lettings log for tenants (2023 to 2024)",
href: "/collection-resources/lettings/2023/paper_form/download",
metadata: "PDF, 286 KB",
},
{
name: "Download the bulk upload template (2023 to 2024)",
href: "/collection-resources/lettings/2023/bulk_upload_template/download",
metadata: "Microsoft Excel, 19 KB",
},
])
end
end
describe "#document_list_edit_component_items" do
let(:resources) do
[
build(:collection_resource, year: 2023, resource_type: "paper_form", display_name: "lettings log for tenants (2023 to 2024)", download_filename: "2023_24_lettings_paper_form.pdf"),
build(:collection_resource, year: 2023, resource_type: "bulk_upload_template", display_name: "bulk upload template (2023 to 2024)", download_filename: "2023_24_lettings_bulk_upload_template.xlsx"),
]
end
before do
allow(storage_service).to receive(:get_file_metadata).with("2023_24_lettings_paper_form.pdf").and_return("content_length" => 292_864, "content_type" => "application/pdf")
allow(storage_service).to receive(:get_file_metadata).with("2023_24_lettings_bulk_upload_template.xlsx").and_return("content_length" => 19_456, "content_type" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
end
it "returns component items" do
expect(document_list_edit_component_items(resources)).to eq([
{
name: "2023_24_lettings_paper_form.pdf",
href: "/collection-resources/lettings/2023/paper_form/download",
metadata: "PDF, 286 KB",
},
{
name: "2023_24_lettings_bulk_upload_template.xlsx",
href: "/collection-resources/lettings/2023/bulk_upload_template/download",
metadata: "Microsoft Excel, 19 KB",
},
])
end
end
end

302
spec/requests/collection_resources_controller_spec.rb

@ -0,0 +1,302 @@
require "rails_helper"
RSpec.describe CollectionResourcesController, type: :request do
let(:page) { Capybara::Node::Simple.new(response.body) }
let(:storage_service) { instance_double(Storage::S3Service, get_file_metadata: nil) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:configuration).and_return(OpenStruct.new(bucket_name: "core-test-collection-resources"))
end
describe "GET #index" do
context "when user is not signed in" do
it "redirects to the sign in page" do
get collection_resources_path
expect(response).to redirect_to(new_user_session_path)
end
end
context "when user is signed in as a data coordinator" do
let(:user) { create(:user, :data_coordinator) }
before do
sign_in user
end
it "returns page not found" do
get collection_resources_path
expect(response).to have_http_status(:not_found)
end
end
context "when user is signed in as a data provider" do
let(:user) { create(:user, :data_provider) }
before do
sign_in user
end
it "returns page not found" do
get collection_resources_path
expect(response).to have_http_status(:not_found)
end
end
context "when user is signed in as a support user" do
let(:user) { create(:user, :support) }
before do
allow(Time.zone).to receive(:today).and_return(Time.zone.local(2025, 1, 8))
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
allow(storage_service).to receive(:file_exists?).and_return(true)
sign_in user
end
it "displays collection resources" do
get collection_resources_path
expect(page).to have_content("Lettings 2024 to 2025")
expect(page).to have_content("Lettings 2025 to 2026")
expect(page).to have_content("Sales 2024 to 2025")
expect(page).to have_content("Sales 2025 to 2026")
end
it "displays mandatory filed" do
get collection_resources_path
expect(page).to have_content("Paper form")
expect(page).to have_content("Bulk upload template")
expect(page).to have_content("Bulk upload specification")
end
context "when files are on S3" do
before do
allow(storage_service).to receive(:file_exists?).and_return(true)
get collection_resources_path
end
it "displays file names with download links" do
expect(page).to have_link("2024_25_lettings_paper_form.pdf", href: download_mandatory_collection_resource_path(year: 2024, log_type: "lettings", resource_type: "paper_form"))
expect(page).to have_link("bulk-upload-lettings-template-2024-25.xlsx", href: download_mandatory_collection_resource_path(year: 2024, log_type: "lettings", resource_type: "bulk_upload_template"))
expect(page).to have_link("bulk-upload-lettings-specification-2024-25.xlsx", href: download_mandatory_collection_resource_path(year: 2024, log_type: "lettings", resource_type: "bulk_upload_specification"))
expect(page).to have_link("2024_25_sales_paper_form.pdf", href: download_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "paper_form"))
expect(page).to have_link("bulk-upload-sales-template-2024-25.xlsx", href: download_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_template"))
expect(page).to have_link("bulk-upload-sales-specification-2024-25.xlsx", href: download_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_specification"))
expect(page).to have_link("2025_26_lettings_paper_form.pdf", href: download_mandatory_collection_resource_path(year: 2025, log_type: "lettings", resource_type: "paper_form"))
expect(page).to have_link("bulk-upload-lettings-template-2025-26.xlsx", href: download_mandatory_collection_resource_path(year: 2025, log_type: "lettings", resource_type: "bulk_upload_template"))
expect(page).to have_link("bulk-upload-lettings-specification-2025-26.xlsx", href: download_mandatory_collection_resource_path(year: 2025, log_type: "lettings", resource_type: "bulk_upload_specification"))
expect(page).to have_link("2025_26_sales_paper_form.pdf", href: download_mandatory_collection_resource_path(year: 2025, log_type: "sales", resource_type: "paper_form"))
expect(page).to have_link("bulk-upload-sales-template-2025-26.xlsx", href: download_mandatory_collection_resource_path(year: 2025, log_type: "sales", resource_type: "bulk_upload_template"))
expect(page).to have_link("bulk-upload-sales-specification-2025-26.xlsx", href: download_mandatory_collection_resource_path(year: 2025, log_type: "sales", resource_type: "bulk_upload_specification"))
end
it "displays change links" do
expect(page).to have_selector(:link_or_button, "Change", count: 12)
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2024, log_type: "lettings", resource_type: "paper_form"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2024, log_type: "lettings", resource_type: "bulk_upload_template"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2024, log_type: "lettings", resource_type: "bulk_upload_specification"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "paper_form"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_template"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_specification"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2025, log_type: "lettings", resource_type: "paper_form"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2025, log_type: "lettings", resource_type: "bulk_upload_template"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2025, log_type: "lettings", resource_type: "bulk_upload_specification"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2025, log_type: "sales", resource_type: "paper_form"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2025, log_type: "sales", resource_type: "bulk_upload_template"))
expect(page).to have_link("Change", href: edit_mandatory_collection_resource_path(year: 2025, log_type: "sales", resource_type: "bulk_upload_specification"))
end
end
context "when files are not on S3" do
before do
allow(storage_service).to receive(:file_exists?).and_return(false)
get collection_resources_path
end
it "displays No file uploaded" do
expect(page).to have_content("No file uploaded")
end
it "displays upload links" do
expect(page).to have_selector(:link_or_button, "Upload", count: 12)
end
end
end
end
describe "GET #download_mandatory_collection_resource" do
before do
# rubocop:disable RSpec/AnyInstance
allow_any_instance_of(CollectionResourcesHelper).to receive(:editable_collection_resource_years).and_return([2025, 2026])
allow_any_instance_of(CollectionResourcesHelper).to receive(:displayed_collection_resource_years).and_return([2025])
# rubocop:enable RSpec/AnyInstance
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
end
context "when user is signed in as a data coordinator" do
let(:user) { create(:user, :data_coordinator) }
context "when the file exists on S3" do
before do
allow(storage_service).to receive(:get_file).and_return("file")
get download_mandatory_collection_resource_path(log_type: "lettings", year: 2025, resource_type: "paper_form")
end
it "downloads the file" do
expect(response.body).to eq("file")
end
end
context "when the file does not exist on S3" do
before do
allow(storage_service).to receive(:get_file).and_return(nil)
get download_mandatory_collection_resource_path(log_type: "lettings", year: 2024, resource_type: "paper_form")
end
it "returns page not found" do
expect(response).to have_http_status(:not_found)
end
end
context "when resource isn't a mandatory resources" do
before do
get download_mandatory_collection_resource_path(log_type: "lettings", year: 2024, resource_type: "invalid_resource")
end
it "returns page not found" do
expect(response).to have_http_status(:not_found)
end
end
context "when year not in displayed_collection_resource_years" do
before do
get download_mandatory_collection_resource_path(log_type: "lettings", year: 2026, resource_type: "paper_form")
end
it "returns page not found" do
expect(response).to have_http_status(:not_found)
end
end
end
context "when user is signed in as a support user" do
let(:user) { create(:user, :support) }
context "when year is in editable_collection_resource_years but not in displayed_collection_resource_years" do
before do
allow(storage_service).to receive(:get_file).and_return("file")
get download_mandatory_collection_resource_path(log_type: "lettings", year: 2026, resource_type: "paper_form")
end
it "downloads the file" do
expect(response.status).to eq(200)
expect(response.body).to eq("file")
end
end
end
end
describe "GET #edit_mandatory_collection_resource" do
context "when user is not signed in" do
it "redirects to the sign in page" do
get edit_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_template")
expect(response).to redirect_to(new_user_session_path)
end
end
context "when user is signed in as a data coordinator" do
let(:user) { create(:user, :data_coordinator) }
before do
sign_in user
end
it "returns page not found" do
get edit_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_template")
expect(response).to have_http_status(:not_found)
end
end
context "when user is signed in as a data provider" do
let(:user) { create(:user, :data_provider) }
before do
sign_in user
end
it "returns page not found" do
get edit_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_template")
expect(response).to have_http_status(:not_found)
end
end
context "when user is signed in as a support user" do
let(:user) { create(:user, :support) }
before do
allow(Time.zone).to receive(:today).and_return(Time.zone.local(2025, 1, 8))
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
end
it "displays update collection resources page content" do
get edit_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_template")
expect(page).to have_content("Sales 2024 to 2025")
expect(page).to have_content("Change the bulk upload template")
expect(page).to have_content("This file will be available for all users to download.")
expect(page).to have_content("Upload file")
expect(page).to have_button("Save changes")
expect(page).to have_link("Back", href: collection_resources_path)
expect(page).to have_link("Cancel", href: collection_resources_path)
end
end
end
describe "PATCH #update_mandatory_collection_resource" do
let(:some_file) { File.open(file_fixture("blank_bulk_upload_sales.csv")) }
let(:params) { { collection_resource: { year: 2024, log_type: "sales", resource_type: "bulk_upload_template", file: some_file } } }
let(:collection_resource_service) { instance_double(CollectionResourcesService) }
before do
allow(CollectionResourcesService).to receive(:new).and_return(collection_resource_service)
end
context "when user is not signed in" do
it "redirects to the sign in page" do
patch update_mandatory_collection_resource_path(year: 2024, log_type: "sales", resource_type: "bulk_upload_template", file: some_file)
expect(response).to redirect_to(new_user_session_path)
end
end
context "when user is signed in as a data coordinator" do
let(:user) { create(:user, :data_coordinator) }
before do
sign_in user
end
it "returns page not found" do
patch update_mandatory_collection_resource_path, params: params
expect(response).to have_http_status(:not_found)
end
end
context "when user is signed in as a data provider" do
let(:user) { create(:user, :data_provider) }
before do
sign_in user
end
it "returns page not found" do
patch update_mandatory_collection_resource_path, params: params
expect(response).to have_http_status(:not_found)
end
end
end
end

24
spec/requests/start_controller_spec.rb

@ -324,7 +324,7 @@ RSpec.describe StartController, type: :request do
context "and 2023 collection window is open for editing" do
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2024, 1, 1))
allow(Time).to receive(:now).and_return(Time.zone.local(2024, 4, 1))
end
it "displays correct resources for 2023/24 and 2024/25 collection years" do
@ -342,10 +342,10 @@ RSpec.describe StartController, type: :request do
context "and 2023 collection window is closed for editing" do
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2025, 1, 1))
allow(Time).to receive(:now).and_return(Time.zone.local(2024, 12, 1))
end
it "displays correct resources for 2023/24 and 2024/25 collection years" do
it "displays correct resources" do
get root_path
expect(page).to have_content("Lettings 24/25")
expect(page).not_to have_content("Lettings 23/24")
@ -367,6 +367,24 @@ RSpec.describe StartController, type: :request do
get root_path
expect(page).to have_content("About this service")
end
context "with support user" do
let(:user) { create(:user, :support) }
it "displays link to edit collection resources" do
get root_path
expect(page).to have_link("Manage collection resources", href: collection_resources_path)
end
end
context "with data coordinator" do
it "does not display the link to edit collection resources" do
get root_path
expect(page).not_to have_link("Manage collection resources", href: collection_resources_path)
end
end
end
end

19
spec/services/collection_resources_service_spec.rb

@ -0,0 +1,19 @@
require "rails_helper"
describe CollectionResourcesService do
let(:service) { described_class.new }
let(:some_file) { File.open(file_fixture("blank_bulk_upload_sales.csv")) }
let(:storage_service) { instance_double(Storage::S3Service) }
describe "#upload_collection_resource" do
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:write_file)
end
it "calls write_file on S3 service" do
expect(storage_service).to receive(:write_file).with("2025_26_lettings_paper_form.pdf", some_file)
service.upload_collection_resource("2025_26_lettings_paper_form.pdf", some_file)
end
end
end

42
spec/services/mandatory_collection_resources_service_spec.rb

@ -0,0 +1,42 @@
require "rails_helper"
describe MandatoryCollectionResourcesService do
let(:service) { described_class }
describe "#generate_resource" do
it "returns a CollectionResource object" do
resource = service.generate_resource("lettings", 2024, "paper_form")
expect(resource).to be_a(CollectionResource)
end
it "returns nil if resource type is not in the MANDATORY_RESOURCES list" do
resource = service.generate_resource("lettings", 2024, "invalid_resource")
expect(resource).to be_nil
end
it "returns a CollectionResource object with the correct attributes" do
resource = service.generate_resource("lettings", 2024, "paper_form")
expect(resource.resource_type).to eq("paper_form")
expect(resource.display_name).to eq("lettings log for tenants (2024 to 2025)")
expect(resource.short_display_name).to eq("Paper form")
expect(resource.year).to eq(2024)
expect(resource.log_type).to eq("lettings")
expect(resource.download_filename).to eq("2024_25_lettings_paper_form.pdf")
end
end
describe "#generate_resources" do
it "generates all mandatory resources for given years" do
resources = service.generate_resources("lettings", [2024, 2025])
expect(resources[2024].map(&:resource_type)).to eq(%w[paper_form bulk_upload_template bulk_upload_specification])
expect(resources[2025].map(&:resource_type)).to eq(%w[paper_form bulk_upload_template bulk_upload_specification])
end
end
describe "#resources_per_year" do
it "generates all mandatory resources for a specific year" do
resources = service.resources_per_year(2024, "lettings")
expect(resources.map(&:resource_type)).to eq(%w[paper_form bulk_upload_template bulk_upload_specification])
end
end
end
Loading…
Cancel
Save