- <% if @data_sharing_agreement %>
- This agreement is made the <%= @data_sharing_agreement.signed_at.day.ordinalize %> day of <%= @data_sharing_agreement.signed_at.strftime("%B") %> <%= @data_sharing_agreement.signed_at.year %>
+ <% if @data_protection_confirmation&.confirmed? %>
+ This agreement is made the <%= @data_protection_confirmation.created_at.day.ordinalize %> day of <%= @data_protection_confirmation.created_at.strftime("%B") %> <%= @data_protection_confirmation.created_at.year %>
<% elsif current_user.is_dpo? %>
This agreement is made the <%= Time.zone.now.day.ordinalize %> day of <%= Time.zone.now.strftime("%B") %> <%= Time.zone.now.year %>
<% else %>
@@ -15,8 +15,8 @@
<% end %>
1) <%= @data_sharing_agreement.organisation_name %> of <%= @data_sharing_agreement.organisation_address %> (“CORE Data Provider”)
1) <%= @data_protection_confirmation.organisation.name %> of <%= @data_protection_confirmation.organisation.address_row %> (“CORE Data Provider”)
1) <%= @organisation.name %> of <%= @organisation.address_row %> (“CORE Data Provider”)
12.1. CORE data providers and DLUHC will each appoint an Authorised Representative to be the primary point of contact in all day-to-day matters relating to this Agreement:
- <%= section_12_2(data_sharing_agreement: @data_sharing_agreement, user: current_user, organisation: @organisation) %>
+ <%= present_section_12_2(data_protection_confirmation: @data_protection_confirmation, user: current_user, organisation: @organisation) %>
12.3. For DLUHC: Name: Rachel Worledge,
Postal Address: South-west section, 4th Floor, Fry Building, 2 Marsham Street, London, SW1P 4DF,
@@ -123,9 +123,9 @@
16.1. The Parties shall comply with all relevant legislation, regulations, orders, statutory instruments and any amendments or re-enactments thereof from the commencement of this agreement.
As witness of which the parties have set their hands on the day and year first above written
- signed for and on behalf of the Data Protection Officer for <%= org_name_for_data_sharing_agreement(@data_sharing_agreement, current_user) %>, by:
+ signed for and on behalf of the Data Protection Officer for <%= org_name_for_data_sharing_agreement(@data_protection_confirmation, current_user) %>, by:
SIGNED for and on behalf of the deputy director of the data, analytics & statistics in the Department for Levelling Up, Housing and Communities, by:
- <% if current_user.is_dpo? && @organisation.data_sharing_agreement.nil? %>
-
+ <% if current_user.is_dpo? && !(@organisation.data_protection_confirmed?) %>
+
<%= govuk_button_to("Accept this agreement", data_sharing_agreement_organisation_path(@organisation), method: :post) %>
<%= govuk_button_link_to("Cancel", details_organisation_path(@organisation), secondary: true) %>
diff --git a/app/views/organisations/logs.html.erb b/app/views/organisations/logs.html.erb
index 55c130c29..6b269590c 100644
--- a/app/views/organisations/logs.html.erb
+++ b/app/views/organisations/logs.html.erb
@@ -5,6 +5,11 @@
<%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %>
+<%= render DataProtectionConfirmationBannerComponent.new(
+ user: current_user,
+ organisation: @organisation,
+) %>
+
<% if current_user.support? %>
<%= render SubNavigationComponent.new(
items: secondary_items(request.path, @organisation.id),
@@ -13,14 +18,7 @@
<% end %>
-
- <% if current_page?(controller: 'organisations', action: 'lettings_logs') %>
- <%= govuk_button_to "Create a new lettings log for this organisation", lettings_logs_path(lettings_log: { owning_organisation_id: @organisation.id }, method: :post) %>
- <% end %>
- <% if current_page?(controller: 'organisations', action: 'sales_logs') %>
- <%= govuk_button_to "Create a new sales log for this organisation", sales_logs_path(sales_log: { owning_organisation_id: @organisation.id }, method: :post) %>
- <% end %>
-
+ <%= render partial: "logs/create_for_org_actions" %>
<%= render partial: "logs/log_filters" %>
@@ -37,6 +35,6 @@
csv_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: false),
csv_codes_only_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: true),
} %>
- <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
+ <%= render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
diff --git a/app/views/organisations/show.html.erb b/app/views/organisations/show.html.erb
index 3f2d153a1..f1afb275c 100644
--- a/app/views/organisations/show.html.erb
+++ b/app/views/organisations/show.html.erb
@@ -33,7 +33,7 @@
<% end %>
<% end %>
<% end %>
- <% if FeatureToggle.new_data_sharing_agreement? %>
+ <% if FeatureToggle.new_data_protection_confirmation? %>
<%= data_sharing_agreement_row(organisation: @organisation, user: current_user, summary_list:) %>
<% end %>
<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 6cba97954..f5d8a56b7 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -105,7 +105,6 @@ en:
confirm_soft_errors:
blank: You must select if there are errors in these fields
-
activerecord:
attributes:
user:
@@ -178,6 +177,7 @@ en:
validations:
organisation:
+ data_sharing_agreement_not_signed: Your organisation must accept the Data Sharing Agreement before you can create any logs.
name_missing: "Enter the name of the organisation"
provider_type_missing: "Select the organisation type"
stock_owner:
diff --git a/db/migrate/20230609101144_drop_data_sharing_adreement_table.rb b/db/migrate/20230609101144_drop_data_sharing_adreement_table.rb
new file mode 100644
index 000000000..25dfff66f
--- /dev/null
+++ b/db/migrate/20230609101144_drop_data_sharing_adreement_table.rb
@@ -0,0 +1,5 @@
+class DropDataSharingAdreementTable < ActiveRecord::Migration[7.0]
+ def change
+ drop_table :data_sharing_agreements # rubocop:disable Rails/ReversibleMigration
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d01fa5935..8337c5305 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_05_30_094653) do
+ActiveRecord::Schema[7.0].define(version: 2023_06_09_101144) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -56,22 +56,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_30_094653) do
t.index ["organisation_id"], name: "index_data_protection_confirmations_on_organisation_id"
end
- create_table "data_sharing_agreements", force: :cascade do |t|
- t.bigint "organisation_id"
- t.bigint "data_protection_officer_id"
- t.datetime "signed_at", null: false
- t.string "organisation_name", null: false
- t.string "organisation_address", null: false
- t.string "organisation_phone_number"
- t.string "dpo_email", null: false
- t.string "dpo_name", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["data_protection_officer_id"], name: "index_data_sharing_agreements_on_data_protection_officer_id"
- t.index ["organisation_id", "data_protection_officer_id"], name: "data_sharing_agreements_unique", unique: true
- t.index ["organisation_id"], name: "index_data_sharing_agreements_on_organisation_id"
- end
-
create_table "la_rent_ranges", force: :cascade do |t|
t.integer "ranges_rent_id"
t.integer "lettype"
diff --git a/db/seeds.rb b/db/seeds.rb
index 400308d1c..7a45dd063 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -7,6 +7,14 @@
# Character.create(name: 'Luke', movie: movies.first)
# rubocop:disable Rails/Output
+def create_data_protection_confirmation(user)
+ DataProtectionConfirmation.find_or_create_by!(
+ organisation: user.organisation,
+ confirmed: true,
+ data_protection_officer: user,
+ )
+end
+
unless Rails.env.test?
stock_owner1 = Organisation.find_or_create_by!(
name: "Stock Owner 1",
@@ -86,6 +94,7 @@ unless Rails.env.test?
) do |user|
user.password = "password"
user.confirmed_at = Time.zone.now
+ create_data_protection_confirmation(user)
end
User.find_or_create_by!(
@@ -96,6 +105,7 @@ unless Rails.env.test?
) do |user|
user.password = "password"
user.confirmed_at = Time.zone.now
+ create_data_protection_confirmation(user)
end
standalone_no_stock = Organisation.find_or_create_by!(
@@ -177,6 +187,8 @@ unless Rails.env.test?
user.confirmed_at = Time.zone.now
end
+ create_data_protection_confirmation(support_user)
+
pp "Seeded 3 dummy users"
end
diff --git a/spec/components/create_log_actions_component_spec.rb b/spec/components/create_log_actions_component_spec.rb
new file mode 100644
index 000000000..3ae7851eb
--- /dev/null
+++ b/spec/components/create_log_actions_component_spec.rb
@@ -0,0 +1,170 @@
+require "rails_helper"
+
+RSpec.describe CreateLogActionsComponent, type: :component do
+ include GovukComponentsHelper
+ include GovukLinkHelper
+ let(:component) { described_class.new(user:, log_type:, bulk_upload:) }
+ let(:render) { render_inline(component) }
+
+ let(:log_type) { "lettings" }
+ let(:user) { create(:user) }
+
+ context "when bulk upload present" do
+ let(:bulk_upload) { true }
+
+ it "does not render actions" do
+ expect(component.display_actions?).to eq(false)
+ end
+ end
+
+ context "when bulk upload nil" do
+ let(:bulk_upload) { nil }
+
+ context "when flag disabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
+ end
+
+ it "renders actions" do
+ expect(component.display_actions?).to eq(true)
+ end
+
+ it "returns create button copy" do
+ expect(component.create_button_copy).to eq("Create a new lettings log")
+ end
+
+ it "returns create button href" do
+ render
+ expect(component.create_button_href).to eq("/lettings-logs")
+ end
+
+ it "returns upload button copy" do
+ expect(component.upload_button_copy).to eq("Upload lettings logs in bulk")
+ end
+
+ it "returns upload button href" do
+ render
+ expect(component.upload_button_href).to eq("/lettings-logs/bulk-upload-logs/start")
+ end
+
+ context "when sales log type" do
+ let(:log_type) { "sales" }
+
+ it "renders actions" do
+ expect(component.display_actions?).to eq(true)
+ end
+
+ it "returns create button copy" do
+ expect(component.create_button_copy).to eq("Create a new sales log")
+ end
+
+ it "returns create button href" do
+ render
+ expect(component.create_button_href).to eq("/sales-logs")
+ end
+ end
+ end
+
+ context "when flag enabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
+ end
+
+ context "when support user" do
+ let(:user) { create(:user, :support) }
+
+ it "renders actions" do
+ expect(component.display_actions?).to eq(true)
+ end
+
+ it "returns create button copy" do
+ expect(component.create_button_copy).to eq("Create a new lettings log")
+ end
+
+ it "returns create button href" do
+ render
+ expect(component.create_button_href).to eq("/lettings-logs")
+ end
+
+ it "returns upload button copy" do
+ expect(component.upload_button_copy).to eq("Upload lettings logs in bulk")
+ end
+
+ it "returns upload button href" do
+ render
+ expect(component.upload_button_href).to eq("/lettings-logs/bulk-upload-logs/start")
+ end
+
+ context "when sales log type" do
+ let(:log_type) { "sales" }
+
+ it "renders actions" do
+ expect(component.display_actions?).to eq(true)
+ end
+
+ it "returns create button copy" do
+ expect(component.create_button_copy).to eq("Create a new sales log")
+ end
+
+ it "returns create button href" do
+ render
+ expect(component.create_button_href).to eq("/sales-logs")
+ end
+ end
+ end
+
+ context "when not support user" do
+ context "without data sharing agreement" do
+ let(:user) { create(:user, organisation: create(:organisation, :without_dpc)) }
+
+ it "does not render actions" do
+ expect(component).not_to be_display_actions
+ end
+ end
+
+ context "when has data sharing agremeent" do
+ let(:user) { create(:user, :support) }
+
+ it "renders actions" do
+ expect(component.display_actions?).to eq(true)
+ end
+
+ it "returns create button copy" do
+ expect(component.create_button_copy).to eq("Create a new lettings log")
+ end
+
+ it "returns create button href" do
+ render
+ expect(component.create_button_href).to eq("/lettings-logs")
+ end
+
+ it "returns upload button copy" do
+ expect(component.upload_button_copy).to eq("Upload lettings logs in bulk")
+ end
+
+ it "returns upload button href" do
+ render
+ expect(component.upload_button_href).to eq("/lettings-logs/bulk-upload-logs/start")
+ end
+
+ context "when sales log type" do
+ let(:log_type) { "sales" }
+
+ it "renders actions" do
+ expect(component.display_actions?).to eq(true)
+ end
+
+ it "returns create button copy" do
+ expect(component.create_button_copy).to eq("Create a new sales log")
+ end
+
+ it "returns create button href" do
+ render
+ expect(component.create_button_href).to eq("/sales-logs")
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/components/data_protection_confirmation_banner_component_spec.rb b/spec/components/data_protection_confirmation_banner_component_spec.rb
new file mode 100644
index 000000000..a76db4435
--- /dev/null
+++ b/spec/components/data_protection_confirmation_banner_component_spec.rb
@@ -0,0 +1,128 @@
+require "rails_helper"
+
+RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
+ let(:component) { described_class.new(user:, organisation:) }
+ let(:render) { render_inline(component) }
+ let(:user) { create(:user) }
+ let(:organisation) { user.organisation }
+
+ context "when flag disabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
+ end
+
+ it "does not display banner" do
+ expect(component.display_banner?).to eq(false)
+ end
+ end
+
+ context "when flag enabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
+ end
+
+ describe "#data_protection_officers_text" do
+ it "returns the correct text" do
+ expect(component.data_protection_officers_text).to eq("You can ask: Danny Rojas")
+ end
+
+ context "with two DPOs" do
+ before do
+ create(:user, organisation:, is_dpo: true, name: "Test McTest")
+ end
+
+ it "returns the correct text" do
+ expect(component.data_protection_officers_text).to eq("You can ask: Danny Rojas, Test McTest")
+ end
+ end
+ end
+
+ context "when user is not support and not dpo" do
+ let(:user) { create(:user) }
+
+ context "when org blank" do
+ let(:organisation) { nil }
+
+ before do
+ allow(DataProtectionConfirmation).to receive(:exists?).and_call_original
+ end
+
+ context "when data sharing agreement present" do
+ it "does not display banner" do
+ expect(component.display_banner?).to eq(false)
+ end
+
+ it "verifies DSA exists for organisation" do
+ render
+ expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation: user.organisation, confirmed: true)
+ end
+ end
+
+ context "when data sharing agreement not present" do
+ let(:user) { create(:user, organisation: create(:organisation, :without_dpc)) }
+
+ it "displays the banner" do
+ expect(component.display_banner?).to eq(true)
+ end
+
+ it "produces the correct link" do
+ render
+ expect(component.data_sharing_agreement_href).to eq("/organisations/#{user.organisation.id}/data-sharing-agreement")
+ end
+
+ it "verifies DSA exists for organisation" do
+ render
+ expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation: user.organisation, confirmed: true)
+ end
+ end
+ end
+ end
+
+ context "when user is support" do
+ let(:user) { create(:user, :support) }
+
+ context "when org blank" do
+ let(:organisation) { nil }
+
+ it "does not display banner" do
+ expect(component.display_banner?).to eq(false)
+ end
+ end
+
+ context "when org present" do
+ before do
+ allow(DataProtectionConfirmation).to receive(:exists?).and_call_original
+ end
+
+ context "when data sharing agreement present" do
+ it "does not display banner" do
+ expect(component.display_banner?).to eq(false)
+ end
+
+ it "verifies DSA exists for organisation" do
+ render
+ expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation:, confirmed: true)
+ end
+ end
+
+ context "when data sharing agreement not present" do
+ let(:organisation) { create(:organisation, :without_dpc) }
+
+ it "displays the banner" do
+ expect(component.display_banner?).to eq(true)
+ end
+
+ it "produces the correct link" do
+ render
+ expect(component.data_sharing_agreement_href).to eq("/organisations/#{organisation.id}/data-sharing-agreement")
+ end
+
+ it "verifies DSA exists for organisation" do
+ render
+ expect(DataProtectionConfirmation).to have_received(:exists?).with(organisation:, confirmed: true)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/modules/search_filter_spec.rb b/spec/controllers/modules/search_filter_spec.rb
index b46878d82..52d9f0cd1 100644
--- a/spec/controllers/modules/search_filter_spec.rb
+++ b/spec/controllers/modules/search_filter_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe Modules::SearchFilter do
let(:search_term) { nil }
it "returns all the users" do
- expect(instance.filtered_users(user_list, search_term).count).to eq(7)
+ expect(instance.filtered_users(user_list, search_term).count).to eq(user_list.count)
end
end
end
diff --git a/spec/db/seeds_spec.rb b/spec/db/seeds_spec.rb
index fb41ccf36..316f04ba6 100644
--- a/spec/db/seeds_spec.rb
+++ b/spec/db/seeds_spec.rb
@@ -24,12 +24,12 @@ RSpec.describe "seeding process", type: task do
it "sets up correct data" do
expect {
Rails.application.load_seed
- }.to change(User, :count).by(7)
- .and change(Organisation, :count).by(8)
- .and change(OrganisationRelationship, :count).by(4)
- .and change(Scheme, :count).by(3)
- .and change(Location, :count).by(3)
- .and change(LaRentRange, :count).by(22_850)
+ }.to change(User, :count)
+ .and change(Organisation, :count)
+ .and change(OrganisationRelationship, :count)
+ .and change(Scheme, :count)
+ .and change(Location, :count)
+ .and change(LaRentRange, :count)
end
it "is idempotent" do
diff --git a/spec/factories/data_protection_confirmation.rb b/spec/factories/data_protection_confirmation.rb
index a6f42d350..3646ea735 100644
--- a/spec/factories/data_protection_confirmation.rb
+++ b/spec/factories/data_protection_confirmation.rb
@@ -1,7 +1,8 @@
FactoryBot.define do
factory :data_protection_confirmation do
- organisation
- data_protection_officer { FactoryBot.create(:user, :data_protection_officer) }
+ organisation { association :organisation, data_protection_confirmation: instance }
+ data_protection_officer { association :user, :data_protection_officer, organisation: (instance.organisation || organisation) }
+
confirmed { true }
old_org_id { "7c5bd5fb549c09a2c55d7cb90d7ba84927e64618" }
old_id { "7c5bd5fb549c09a2c55d7cb90d7ba84927e64618" }
diff --git a/spec/factories/data_sharing_agreement.rb b/spec/factories/data_sharing_agreement.rb
deleted file mode 100644
index 11e9eef02..000000000
--- a/spec/factories/data_sharing_agreement.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-FactoryBot.define do
- factory :data_sharing_agreement do
- organisation
- data_protection_officer { create(:user, is_dpo: true) }
- signed_at { Time.zone.now }
- created_at { Time.zone.now }
- updated_at { Time.zone.now }
-
- dpo_name { data_protection_officer.name }
- dpo_email { data_protection_officer.email }
- organisation_address { organisation.address_string }
- organisation_phone_number { organisation.phone }
- organisation_name { organisation.name }
- end
-end
diff --git a/spec/factories/organisation.rb b/spec/factories/organisation.rb
index fa2650eb5..c65533b3d 100644
--- a/spec/factories/organisation.rb
+++ b/spec/factories/organisation.rb
@@ -10,6 +10,20 @@ FactoryBot.define do
updated_at { Time.zone.now }
holds_own_stock { true }
+ transient do
+ with_dsa { true }
+ end
+
+ after(:create) do |org, evaluator|
+ if evaluator.with_dsa
+ create(
+ :data_protection_confirmation,
+ organisation: org,
+ data_protection_officer: org.users.any? ? org.users.first : create(:user, :data_protection_officer, organisation: org),
+ )
+ end
+ end
+
trait :with_old_visible_id do
old_visible_id { rand(9_999_999).to_s }
end
@@ -21,6 +35,14 @@ FactoryBot.define do
trait :does_not_own_stock do
holds_own_stock { false }
end
+
+ trait :without_dpc do
+ transient do
+ with_dsa { false }
+ end
+
+ data_protection_confirmation { nil }
+ end
end
factory :organisation_rent_period do
diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb
index 464f67d49..7f1e77d2a 100644
--- a/spec/features/lettings_log_spec.rb
+++ b/spec/features/lettings_log_spec.rb
@@ -85,7 +85,7 @@ RSpec.describe "Lettings Log Features" do
click_link("Set up this lettings log")
select(support_user.organisation.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue")
- select(support_user.name, from: "lettings-log-created-by-id-field")
+ select("#{support_user.name} (#{support_user.email})", from: "lettings-log-created-by-id-field")
click_button("Save and continue")
log_id = page.current_path.scan(/\d/).join
visit("lettings-logs/#{log_id}/setup/check-answers")
@@ -95,7 +95,7 @@ RSpec.describe "Lettings Log Features" do
end
context "when visiting a subsection check answers page" do
- let(:lettings_log) { FactoryBot.create(:lettings_log, :setup_completed) }
+ let(:lettings_log) { create(:lettings_log, :setup_completed) }
it "has the correct breadcrumbs with the correct links" do
visit lettings_log_setup_check_answers_path(lettings_log)
@@ -108,7 +108,7 @@ RSpec.describe "Lettings Log Features" do
end
context "when reviewing a complete log" do
- let(:lettings_log) { FactoryBot.create(:lettings_log, :completed) }
+ let(:lettings_log) { create(:lettings_log, :completed) }
it "has the correct breadcrumbs with the correct links" do
visit review_lettings_log_path(lettings_log)
diff --git a/spec/models/form/lettings/questions/created_by_id_spec.rb b/spec/models/form/lettings/questions/created_by_id_spec.rb
index 0610e5b2a..ec3fc7a9b 100644
--- a/spec/models/form/lettings/questions/created_by_id_spec.rb
+++ b/spec/models/form/lettings/questions/created_by_id_spec.rb
@@ -41,6 +41,12 @@ RSpec.describe Form::Lettings::Questions::CreatedById, type: :model do
expect(question.derived?).to be true
end
+ def expected_option_for_users(users)
+ users.each_with_object({ "" => "Select an option" }) do |user, obj|
+ obj[user.id] = "#{user.name} (#{user.email})"
+ end
+ end
+
context "when the current user is support" do
let(:owning_org_user) { create(:user) }
let(:managing_org_user) { create(:user) }
@@ -51,17 +57,8 @@ RSpec.describe Form::Lettings::Questions::CreatedById, type: :model do
create(:lettings_log, created_by: support_user, owning_organisation: owning_org_user.organisation, managing_organisation: managing_org_user.organisation)
end
- let(:expected_answer_options) do
- {
- "" => "Select an option",
- managing_org_user.id => "#{managing_org_user.name} (#{managing_org_user.email})",
- owning_org_user.id => "#{owning_org_user.name} (#{owning_org_user.email})",
- support_user.id => "#{support_user.name} (#{support_user.email})",
- }
- end
-
it "only displays users that belong to owning and managing organisations" do
- expect(question.displayed_answer_options(lettings_log, support_user)).to eq(expected_answer_options)
+ expect(question.displayed_answer_options(lettings_log, support_user)).to eq(expected_option_for_users(managing_org_user.organisation.users + owning_org_user.organisation.users))
end
end
end
@@ -77,15 +74,8 @@ RSpec.describe Form::Lettings::Questions::CreatedById, type: :model do
let(:user_in_same_org) { create(:user, organisation: data_coordinator.organisation) }
- let(:expected_answer_options) do
- {
- "" => "Select an option",
- data_coordinator.id => "#{data_coordinator.name} (#{data_coordinator.email})",
- }
- end
-
it "only displays users that belong user's org" do
- expect(question.displayed_answer_options(lettings_log, data_coordinator)).to eq(expected_answer_options)
+ expect(question.displayed_answer_options(lettings_log, data_coordinator)).to eq(expected_option_for_users(data_coordinator.organisation.users))
end
end
end
diff --git a/spec/models/form/sales/questions/created_by_id_spec.rb b/spec/models/form/sales/questions/created_by_id_spec.rb
index fd0122cb8..29f5b38b8 100644
--- a/spec/models/form/sales/questions/created_by_id_spec.rb
+++ b/spec/models/form/sales/questions/created_by_id_spec.rb
@@ -37,6 +37,12 @@ RSpec.describe Form::Sales::Questions::CreatedById, type: :model do
expect(question.derived?).to be true
end
+ def expected_option_for_users(users)
+ users.each_with_object({ "" => "Select an option" }) do |user, obj|
+ obj[user.id] = "#{user.name} (#{user.email})"
+ end
+ end
+
context "when the current user is support" do
let(:support_user) { build(:user, :support) }
@@ -47,15 +53,9 @@ RSpec.describe Form::Sales::Questions::CreatedById, type: :model do
describe "#displayed_answer_options" do
let(:owning_org_user) { create(:user) }
let(:sales_log) { create(:sales_log, owning_organisation: owning_org_user.organisation) }
- let(:expected_answer_options) do
- {
- "" => "Select an option",
- owning_org_user.id => "#{owning_org_user.name} (#{owning_org_user.email})",
- }
- end
it "only displays users that belong to the owning organisation" do
- expect(question.displayed_answer_options(sales_log, support_user)).to eq(expected_answer_options)
+ expect(question.displayed_answer_options(sales_log, support_user)).to eq(expected_option_for_users(owning_org_user.organisation.users))
end
end
end
@@ -70,18 +70,13 @@ RSpec.describe Form::Sales::Questions::CreatedById, type: :model do
describe "#displayed_answer_options" do
let(:owning_org_user) { create(:user) }
let(:sales_log) { create(:sales_log, owning_organisation: owning_org_user.organisation) }
- let!(:user_in_same_org) { create(:user, organisation: data_coordinator.organisation) }
-
- let(:expected_answer_options) do
- {
- "" => "Select an option",
- user_in_same_org.id => "#{user_in_same_org.name} (#{user_in_same_org.email})",
- data_coordinator.id => "#{data_coordinator.name} (#{data_coordinator.email})",
- }
+
+ before do
+ create(:user, organisation: data_coordinator.organisation)
end
it "only displays users that belong user's org" do
- expect(question.displayed_answer_options(sales_log, data_coordinator)).to eq(expected_answer_options)
+ expect(question.displayed_answer_options(sales_log, data_coordinator)).to eq(expected_option_for_users(data_coordinator.organisation.users))
end
end
end
diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb
index 362a7e2a9..e52317065 100644
--- a/spec/models/organisation_spec.rb
+++ b/spec/models/organisation_spec.rb
@@ -2,39 +2,35 @@ require "rails_helper"
RSpec.describe Organisation, type: :model do
describe "#new" do
- let(:user) { FactoryBot.create(:user) }
+ let(:user) { create(:user) }
let!(:organisation) { user.organisation }
- let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: organisation) }
+ let!(:scheme) { create(:scheme, owning_organisation: organisation) }
it "has expected fields" do
expect(organisation.attribute_names).to include("name", "phone", "provider_type")
end
- it "has users" do
- expect(organisation.users.first).to eq(user)
- end
-
it "has owned_schemes" do
expect(organisation.owned_schemes.first).to eq(scheme)
end
it "validates provider_type presence" do
- expect { FactoryBot.create(:organisation, provider_type: nil) }
+ expect { create(:organisation, provider_type: nil) }
.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Provider type #{I18n.t('validations.organisation.provider_type_missing')}")
end
context "with parent/child associations", :aggregate_failures do
- let!(:child_organisation) { FactoryBot.create(:organisation, name: "DLUHC Child") }
- let!(:grandchild_organisation) { FactoryBot.create(:organisation, name: "DLUHC Grandchild") }
+ let!(:child_organisation) { create(:organisation, name: "DLUHC Child") }
+ let!(:grandchild_organisation) { create(:organisation, name: "DLUHC Grandchild") }
before do
- FactoryBot.create(
+ create(
:organisation_relationship,
child_organisation:,
parent_organisation: organisation,
)
- FactoryBot.create(
+ create(
:organisation_relationship,
child_organisation: grandchild_organisation,
parent_organisation: child_organisation,
@@ -53,17 +49,17 @@ RSpec.describe Organisation, type: :model do
end
context "with owning association", :aggregate_failures do
- let!(:child_organisation) { FactoryBot.create(:organisation, name: "DLUHC Child") }
- let!(:grandchild_organisation) { FactoryBot.create(:organisation, name: "DLUHC Grandchild") }
+ let!(:child_organisation) { create(:organisation, name: "DLUHC Child") }
+ let!(:grandchild_organisation) { create(:organisation, name: "DLUHC Grandchild") }
before do
- FactoryBot.create(
+ create(
:organisation_relationship,
child_organisation:,
parent_organisation: organisation,
)
- FactoryBot.create(
+ create(
:organisation_relationship,
child_organisation: grandchild_organisation,
parent_organisation: child_organisation,
@@ -77,17 +73,17 @@ RSpec.describe Organisation, type: :model do
end
context "with managing association", :aggregate_failures do
- let!(:child_organisation) { FactoryBot.create(:organisation, name: "DLUHC Child") }
- let!(:grandchild_organisation) { FactoryBot.create(:organisation, name: "DLUHC Grandchild") }
+ let!(:child_organisation) { create(:organisation, name: "DLUHC Child") }
+ let!(:grandchild_organisation) { create(:organisation, name: "DLUHC Grandchild") }
before do
- FactoryBot.create(
+ create(
:organisation_relationship,
child_organisation:,
parent_organisation: organisation,
)
- FactoryBot.create(
+ create(
:organisation_relationship,
child_organisation: grandchild_organisation,
parent_organisation: child_organisation,
@@ -103,8 +99,8 @@ RSpec.describe Organisation, type: :model do
context "with data protection confirmations" do
before do
- FactoryBot.create(:data_protection_confirmation, organisation:, confirmed: false, created_at: Time.utc(2018, 0o6, 0o5, 10, 36, 49))
- FactoryBot.create(:data_protection_confirmation, organisation:, created_at: Time.utc(2019, 0o6, 0o5, 10, 36, 49))
+ create(:data_protection_confirmation, organisation:, confirmed: false, created_at: Time.utc(2018, 0o6, 0o5, 10, 36, 49))
+ create(:data_protection_confirmation, organisation:, created_at: Time.utc(2019, 0o6, 0o5, 10, 36, 49))
end
it "takes the most recently created" do
@@ -118,11 +114,11 @@ RSpec.describe Organisation, type: :model do
end
before do
- FactoryBot.create(:organisation_rent_period, organisation:, rent_period: 2)
- FactoryBot.create(:organisation_rent_period, organisation:, rent_period: 3)
+ create(:organisation_rent_period, organisation:, rent_period: 2)
+ create(:organisation_rent_period, organisation:, rent_period: 3)
# Unmapped and ignored by `rent_period_labels`
- FactoryBot.create(:organisation_rent_period, organisation:, rent_period: 10)
+ create(:organisation_rent_period, organisation:, rent_period: 10)
allow(RentPeriod).to receive(:rent_period_mappings).and_return(rent_period_mappings)
end
@@ -142,9 +138,9 @@ RSpec.describe Organisation, type: :model do
end
context "with lettings logs" do
- let(:other_organisation) { FactoryBot.create(:organisation) }
+ let(:other_organisation) { create(:organisation) }
let!(:owned_lettings_log) do
- FactoryBot.create(
+ create(
:lettings_log,
:completed,
managing_organisation: other_organisation,
@@ -152,7 +148,7 @@ RSpec.describe Organisation, type: :model do
)
end
let!(:managed_lettings_log) do
- FactoryBot.create(
+ create(
:lettings_log,
created_by: user,
)
@@ -173,7 +169,7 @@ RSpec.describe Organisation, type: :model do
end
describe "paper trail" do
- let(:organisation) { FactoryBot.create(:organisation) }
+ let(:organisation) { create(:organisation) }
it "creates a record of changes to a log" do
expect { organisation.update!(name: "new test name") }.to change(organisation.versions, :count).by(1)
@@ -187,11 +183,11 @@ RSpec.describe Organisation, type: :model do
describe "delete cascade" do
context "when the organisation is deleted" do
- let!(:organisation) { FactoryBot.create(:organisation) }
- let!(:user) { FactoryBot.create(:user, :support, last_sign_in_at: Time.zone.now, organisation:) }
- let!(:scheme_to_delete) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
- let!(:log_to_delete) { FactoryBot.create(:lettings_log, owning_organisation: user.organisation) }
- let!(:sales_log_to_delete) { FactoryBot.create(:sales_log, owning_organisation: user.organisation) }
+ let!(:organisation) { create(:organisation) }
+ let!(:user) { create(:user, :support, last_sign_in_at: Time.zone.now, organisation:) }
+ let!(:scheme_to_delete) { create(:scheme, owning_organisation: user.organisation) }
+ let!(:log_to_delete) { create(:lettings_log, owning_organisation: user.organisation) }
+ let!(:sales_log_to_delete) { create(:sales_log, owning_organisation: user.organisation) }
context "when organisation is deleted" do
it "child relationships ie logs, schemes and users are deleted too - application" do
@@ -216,8 +212,8 @@ RSpec.describe Organisation, type: :model do
describe "scopes" do
before do
- FactoryBot.create(:organisation, name: "Joe Bloggs")
- FactoryBot.create(:organisation, name: "Tom Smith")
+ create(:organisation, name: "Joe Bloggs")
+ create(:organisation, name: "Tom Smith")
end
context "when searching by name" do
@@ -234,4 +230,49 @@ RSpec.describe Organisation, type: :model do
end
end
end
+
+ describe "display_organisation_attributes" do
+ let(:organisation) { create(:organisation) }
+
+ context "when new_data_protection_confirmation flag enabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
+ end
+
+ it "does not include data protection agreement" do
+ expect(organisation.display_organisation_attributes).to eq(
+ [{ editable: true, name: "Name", value: "DLUHC" },
+ { editable: true,
+ name: "Address",
+ value: "2 Marsham Street\nLondon\nSW1P 4DF" },
+ { editable: true, name: "Telephone_number", value: nil },
+ { editable: false, name: "Type of provider", value: "Local authority" },
+ { editable: false, name: "Registration number", value: "1234" },
+ { editable: false, format: :bullet, name: "Rent_periods", value: %w[All] },
+ { editable: false, name: "Owns housing stock", value: "Yes" }],
+ )
+ end
+ end
+
+ context "when new_data_protection_confirmation flag disabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
+ end
+
+ it "includes data protection agreement" do
+ expect(organisation.display_organisation_attributes).to eq(
+ [{ editable: true, name: "Name", value: "DLUHC" },
+ { editable: true,
+ name: "Address",
+ value: "2 Marsham Street\nLondon\nSW1P 4DF" },
+ { editable: true, name: "Telephone_number", value: nil },
+ { editable: false, name: "Type of provider", value: "Local authority" },
+ { editable: false, name: "Registration number", value: "1234" },
+ { editable: false, format: :bullet, name: "Rent_periods", value: %w[All] },
+ { editable: false, name: "Owns housing stock", value: "Yes" },
+ { editable: false, name: "Data protection agreement", value: "Accepted" }],
+ )
+ end
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1cdbe7cd4..edc065a4f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,10 +2,10 @@ require "rails_helper"
RSpec.describe User, type: :model do
describe "#new" do
- let(:user) { FactoryBot.create(:user, old_user_id: "3") }
- let(:other_organisation) { FactoryBot.create(:organisation) }
+ let(:user) { create(:user, old_user_id: "3") }
+ let(:other_organisation) { create(:organisation) }
let!(:owned_lettings_log) do
- FactoryBot.create(
+ create(
:lettings_log,
:completed,
managing_organisation: other_organisation,
@@ -13,7 +13,7 @@ RSpec.describe User, type: :model do
)
end
let!(:managed_lettings_log) do
- FactoryBot.create(
+ create(
:lettings_log,
created_by: user,
owning_organisation: other_organisation,
@@ -103,7 +103,7 @@ RSpec.describe User, type: :model do
end
context "when the user is a data coordinator" do
- let(:user) { FactoryBot.create(:user, :data_coordinator) }
+ let(:user) { create(:user, :data_coordinator) }
it "can assign all roles except support" do
expect(user.assignable_roles).to eq({
@@ -124,7 +124,7 @@ RSpec.describe User, type: :model do
context "and their organisation has managing agents" do
before do
- FactoryBot.create(:organisation_relationship, parent_organisation: user.organisation)
+ create(:organisation_relationship, parent_organisation: user.organisation)
end
it "can filter lettings logs by user, year, status and organisation" do
@@ -134,8 +134,8 @@ RSpec.describe User, type: :model do
end
context "when the user is a Customer Support person" do
- let(:user) { FactoryBot.create(:user, :support) }
- let!(:other_orgs_log) { FactoryBot.create(:lettings_log) }
+ let(:user) { create(:user, :support) }
+ let!(:other_orgs_log) { create(:lettings_log) }
it "has access to logs from all organisations" do
expect(user.lettings_logs.to_a).to match_array([owned_lettings_log, managed_lettings_log, other_orgs_log])
@@ -159,7 +159,7 @@ RSpec.describe User, type: :model do
end
context "when the user is in development environment" do
- let(:user) { FactoryBot.create(:user, :support) }
+ let(:user) { create(:user, :support) }
before do
allow(Rails.env).to receive(:development?).and_return(true)
@@ -171,7 +171,7 @@ RSpec.describe User, type: :model do
end
context "when the user is in review environment" do
- let(:user) { FactoryBot.create(:user, :support) }
+ let(:user) { create(:user, :support) }
before do
allow(Rails.env).to receive(:development?).and_return(false)
@@ -185,7 +185,7 @@ RSpec.describe User, type: :model do
end
describe "paper trail" do
- let(:user) { FactoryBot.create(:user) }
+ let(:user) { create(:user) }
it "creates a record of changes to a log" do
expect { user.update!(name: "new test name") }.to change(user.versions, :count).by(1)
@@ -217,13 +217,13 @@ RSpec.describe User, type: :model do
end
describe "scopes" do
- let(:organisation_1) { FactoryBot.create(:organisation, name: "A") }
- let(:organisation_2) { FactoryBot.create(:organisation, name: "B") }
- let!(:user_1) { FactoryBot.create(:user, name: "Joe Bloggs", email: "joe@example.com", organisation: organisation_1, role: "support") }
- let!(:user_3) { FactoryBot.create(:user, name: "Tom Smith", email: "tom@example.com", organisation: organisation_1, role: "data_provider") }
- let!(:user_2) { FactoryBot.create(:user, name: "Jenny Ford", email: "jenny@smith.com", organisation: organisation_1, role: "data_coordinator") }
- let!(:user_4) { FactoryBot.create(:user, name: "Greg Thomas", email: "greg@org2.com", organisation: organisation_2, role: "data_coordinator") }
- let!(:user_5) { FactoryBot.create(:user, name: "Adam Thomas", email: "adam@org2.com", organisation: organisation_2, role: "data_coordinator") }
+ let(:organisation_1) { create(:organisation, :without_dpc, name: "A") }
+ let(:organisation_2) { create(:organisation, :without_dpc, name: "B") }
+ let!(:user_1) { create(:user, name: "Joe Bloggs", email: "joe@example.com", organisation: organisation_1, role: "support") }
+ let!(:user_3) { create(:user, name: "Tom Smith", email: "tom@example.com", organisation: organisation_1, role: "data_provider") }
+ let!(:user_2) { create(:user, name: "Jenny Ford", email: "jenny@smith.com", organisation: organisation_1, role: "data_coordinator") }
+ let!(:user_4) { create(:user, name: "Greg Thomas", email: "greg@org2.com", organisation: organisation_2, role: "data_coordinator") }
+ let!(:user_5) { create(:user, name: "Adam Thomas", email: "adam@org2.com", organisation: organisation_2, role: "data_coordinator") }
context "when searching by name" do
it "returns case insensitive matching records" do
@@ -271,7 +271,7 @@ RSpec.describe User, type: :model do
let(:error_message) { "Validation failed: Password #{I18n.t('activerecord.errors.models.user.attributes.password.too_short', count: 8)}" }
it "validates password length" do
- expect { FactoryBot.create(:user, password:) }
+ expect { create(:user, password:) }
.to raise_error(ActiveRecord::RecordInvalid, error_message)
end
end
@@ -281,27 +281,27 @@ RSpec.describe User, type: :model do
let(:error_message) { "Validation failed: email #{I18n.t('activerecord.errors.models.user.attributes.email.invalid')}" }
it "validates email format" do
- expect { FactoryBot.create(:user, email: invalid_email) }
+ expect { create(:user, email: invalid_email) }
.to raise_error(ActiveRecord::RecordInvalid, error_message)
end
end
context "when the email entered has already been used" do
- let(:user) { FactoryBot.create(:user) }
+ let(:user) { create(:user) }
let(:error_message) { "Validation failed: email #{I18n.t('activerecord.errors.models.user.attributes.email.taken')}" }
it "validates email uniqueness" do
- expect { FactoryBot.create(:user, email: user.email) }
+ expect { create(:user, email: user.email) }
.to raise_error(ActiveRecord::RecordInvalid, error_message)
end
end
end
describe "delete" do
- let(:user) { FactoryBot.create(:user) }
+ let(:user) { create(:user) }
before do
- FactoryBot.create(
+ create(
:lettings_log,
:completed,
owning_organisation: user.organisation,
@@ -309,7 +309,7 @@ RSpec.describe User, type: :model do
created_by: user,
)
- FactoryBot.create(
+ create(
:sales_log,
owning_organisation: user.organisation,
created_by: user,
@@ -319,13 +319,13 @@ RSpec.describe User, type: :model do
context "when the user is deleted" do
it "owned lettings logs are not deleted as a result" do
expect { user.destroy! }
- .to change(described_class, :count).from(1).to(0)
+ .to change(described_class, :count).by(-1)
.and change(LettingsLog, :count).by(0)
end
it "owned sales logs are not deleted as a result" do
expect { user.destroy! }
- .to change(described_class, :count).from(1).to(0)
+ .to change(described_class, :count).by(-1)
.and change(SalesLog, :count).by(0)
end
end
diff --git a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb
index 7cea42f69..f901cdb7e 100644
--- a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb
+++ b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb
@@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe BulkUploadLettingsLogsController, type: :request do
- let(:user) { FactoryBot.create(:user) }
+ let(:user) { create(:user) }
let(:organisation) { user.organisation }
before do
@@ -9,6 +9,17 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do
end
describe "GET /lettings-logs/bulk-upload-logs/start" do
+ context "when data protection confirmation not signed" do
+ let(:organisation) { create(:organisation, :without_dpc) }
+ let(:user) { create(:user, organisation:) }
+
+ it "redirects to lettings index page" do
+ get "/lettings-logs/bulk-upload-logs/start", params: {}
+
+ expect(response).to redirect_to("/lettings-logs")
+ end
+ end
+
context "when not in crossover period" do
let(:expected_year) { 2022 }
diff --git a/spec/requests/bulk_upload_sales_logs_controller_spec.rb b/spec/requests/bulk_upload_sales_logs_controller_spec.rb
index 3e2aa5910..3220ff885 100644
--- a/spec/requests/bulk_upload_sales_logs_controller_spec.rb
+++ b/spec/requests/bulk_upload_sales_logs_controller_spec.rb
@@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe BulkUploadSalesLogsController, type: :request do
- let(:user) { FactoryBot.create(:user) }
+ let(:user) { create(:user) }
let(:organisation) { user.organisation }
before do
@@ -9,6 +9,17 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do
end
describe "GET /sales-logs/bulk-upload-logs/start" do
+ context "when data protection confirmation not signed" do
+ let(:organisation) { create(:organisation, :without_dpc) }
+ let(:user) { create(:user, organisation:) }
+
+ it "redirects to sales index page" do
+ get "/sales-logs/bulk-upload-logs/start", params: {}
+
+ expect(response).to redirect_to("/sales-logs")
+ end
+ end
+
context "when not in crossover period" do
let(:expected_year) { FormHandler.instance.forms["current_sales"].start_date.year }
diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb
index e8580d219..466e7522e 100644
--- a/spec/requests/organisations_controller_spec.rb
+++ b/spec/requests/organisations_controller_spec.rb
@@ -285,7 +285,7 @@ RSpec.describe OrganisationsController, type: :request do
end
it "shows the pagination count" do
- expect(page).to have_content("3 total users")
+ expect(page).to have_content("#{user.organisation.users.count} total users")
end
end
@@ -1424,7 +1424,7 @@ RSpec.describe OrganisationsController, type: :request do
it "only includes users from that organisation" do
get "/organisations/#{other_organisation.id}/users", headers:, params: {}
csv = CSV.parse(response.body)
- expect(csv.count).to eq(3)
+ expect(csv.count).to eq(other_organisation.users.count + 1)
end
end
end
@@ -1446,7 +1446,7 @@ RSpec.describe OrganisationsController, type: :request do
context "when flag not enabled" do
before do
- allow(FeatureToggle).to receive(:new_data_sharing_agreement?).and_return(false)
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
end
it "returns not found" do
@@ -1457,7 +1457,7 @@ RSpec.describe OrganisationsController, type: :request do
context "when flag enabled" do
before do
- allow(FeatureToggle).to receive(:new_data_sharing_agreement?).and_return(true)
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
end
it "returns ok" do
@@ -1469,6 +1469,8 @@ RSpec.describe OrganisationsController, type: :request do
end
describe "POST #data_sharing_agreement" do
+ let(:organisation) { create(:organisation, :without_dpc) }
+
context "when not signed in" do
it "redirects to sign in" do
post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers
@@ -1484,7 +1486,7 @@ RSpec.describe OrganisationsController, type: :request do
context "when flag not enabled" do
before do
- allow(FeatureToggle).to receive(:new_data_sharing_agreement?).and_return(false)
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
end
it "returns not found" do
@@ -1495,7 +1497,7 @@ RSpec.describe OrganisationsController, type: :request do
context "when flag enabled" do
before do
- allow(FeatureToggle).to receive(:new_data_sharing_agreement?).and_return(true)
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
end
context "when user not dpo" do
@@ -1508,39 +1510,48 @@ RSpec.describe OrganisationsController, type: :request do
end
context "when user is dpo" do
- let(:user) { create(:user, is_dpo: true) }
-
- it "returns redirects to details page" do
- post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers
+ context "when the organisation has a non-confirmed confirmation" do
+ let(:user) { create(:user, is_dpo: false) }
- expect(response).to redirect_to("/organisations/#{organisation.id}/details")
- expect(flash[:notice]).to eq("You have accepted the Data Sharing Agreement")
- expect(flash[:notification_banner_body]).to eq("Your organisation can now submit logs.")
+ it "returns not found" do
+ post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers
+ expect(response).to have_http_status(:not_found)
+ end
end
- it "creates a data sharing agreement" do
- expect(organisation.reload.data_sharing_agreement).to be_nil
+ context "when the organisation does not have a confirmation" do
+ let(:user) { create(:user, is_dpo: true, organisation:) }
+
+ it "returns redirects to details page" do
+ post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers
+
+ expect(response).to redirect_to("/organisations/#{organisation.id}/details")
+ expect(flash[:notice]).to eq("You have accepted the Data Sharing Agreement")
+ expect(flash[:notification_banner_body]).to eq("Your organisation can now submit logs.")
+ end
- post("/organisations/#{organisation.id}/data-sharing-agreement", headers:)
+ it "creates a data sharing agreement" do
+ expect(organisation.reload.data_protection_confirmation).to be_nil
- data_sharing_agreement = organisation.reload.data_sharing_agreement
+ post("/organisations/#{organisation.id}/data-sharing-agreement", headers:)
- expect(data_sharing_agreement.organisation_address).to eq(organisation.address_row)
- expect(data_sharing_agreement.organisation_name).to eq(organisation.name)
- expect(data_sharing_agreement.organisation_phone_number).to eq(organisation.phone)
- expect(data_sharing_agreement.data_protection_officer).to eq(user)
- expect(data_sharing_agreement.dpo_name).to eq(user.name)
- expect(data_sharing_agreement.dpo_email).to eq(user.email)
- end
+ data_protection_confirmation = organisation.reload.data_protection_confirmation
- context "when the user has already accepted the agreement" do
- before do
- create(:data_sharing_agreement, data_protection_officer: user, organisation: user.organisation)
+ expect(data_protection_confirmation.organisation.address_row).to eq(organisation.address_row)
+ expect(data_protection_confirmation.organisation.name).to eq(organisation.name)
+ expect(data_protection_confirmation.organisation.phone).to eq(organisation.phone)
+ expect(data_protection_confirmation.data_protection_officer).to eq(user)
end
- it "returns not found" do
- post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers
- expect(response).to have_http_status(:not_found)
+ context "when the user has already accepted the agreement" do
+ before do
+ create(:data_protection_confirmation, data_protection_officer: user, organisation: user.organisation)
+ end
+
+ it "returns not found" do
+ post "/organisations/#{organisation.id}/data-sharing-agreement", headers: headers
+ expect(response).to have_http_status(:not_found)
+ end
end
end
end
diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb
index 3328fdaf6..a97086bb2 100644
--- a/spec/requests/users_controller_spec.rb
+++ b/spec/requests/users_controller_spec.rb
@@ -363,7 +363,7 @@ RSpec.describe UsersController, type: :request do
end
context "when user is signed in as a data coordinator" do
- let(:user) { FactoryBot.create(:user, :data_coordinator, email: "coordinator@example.com") }
+ let(:user) { FactoryBot.create(:user, :data_coordinator, email: "coordinator@example.com", organisation: create(:organisation, :without_dpc)) }
let!(:other_user) { FactoryBot.create(:user, organisation: user.organisation, name: "filter name", email: "filter@example.com") }
describe "#index" do
@@ -969,7 +969,7 @@ RSpec.describe UsersController, type: :request do
end
context "when user is signed in as a support user" do
- let(:user) { FactoryBot.create(:user, :support) }
+ let(:user) { FactoryBot.create(:user, :support, organisation: create(:organisation, :without_dpc)) }
let(:other_user) { FactoryBot.create(:user, organisation: user.organisation) }
before do
@@ -979,7 +979,7 @@ RSpec.describe UsersController, type: :request do
describe "#index" do
let!(:other_user) { FactoryBot.create(:user, organisation: user.organisation, name: "User 2", email: "other@example.com") }
let!(:inactive_user) { FactoryBot.create(:user, organisation: user.organisation, active: false, name: "User 3", email: "inactive@example.com") }
- let!(:other_org_user) { FactoryBot.create(:user, name: "User 4", email: "otherorg@otherexample.com") }
+ let!(:other_org_user) { FactoryBot.create(:user, name: "User 4", email: "otherorg@otherexample.com", organisation: create(:organisation, :without_dpc)) }
before do
sign_in user
@@ -1058,7 +1058,7 @@ RSpec.describe UsersController, type: :request do
context "when our search term matches an email and a name" do
let!(:other_user) { FactoryBot.create(:user, organisation: user.organisation, name: "joe", email: "other@example.com") }
- let!(:other_org_user) { FactoryBot.create(:user, name: "User 4", email: "joe@otherexample.com") }
+ let!(:other_org_user) { FactoryBot.create(:user, name: "User 4", email: "joe@otherexample.com", organisation: create(:organisation, :without_dpc)) }
let(:search_param) { "joe" }
it "returns any results including joe" do
@@ -1090,15 +1090,19 @@ RSpec.describe UsersController, type: :request do
get "/users", headers:, params: {}
end
+ let(:byte_order_mark) { "\uFEFF" }
+
it "downloads a CSV file with headers" do
csv = CSV.parse(response.body)
- expect(csv.first.second).to eq("email")
- expect(csv.second.first).to eq(user.id.to_s)
+
+ expect(csv.first.to_csv).to eq(
+ "#{byte_order_mark}id,email,name,organisation_name,role,old_user_id,is_dpo,is_key_contact,active,sign_in_count,last_sign_in_at\n",
+ )
end
it "downloads all users" do
csv = CSV.parse(response.body)
- expect(csv.count).to eq(27)
+ expect(csv.count).to eq(User.all.count + 1) # +1 for the headers
end
it "downloads organisation names rather than ids" do
@@ -1517,7 +1521,7 @@ RSpec.describe UsersController, type: :request do
end
describe "#create" do
- let(:organisation) { FactoryBot.create(:organisation) }
+ let(:organisation) { FactoryBot.create(:organisation, :without_dpc) }
let(:email) { "new_user@example.com" }
let(:params) do
{
diff --git a/spec/services/imports/data_protection_confirmation_import_service_spec.rb b/spec/services/imports/data_protection_confirmation_import_service_spec.rb
index cb14829b4..400822462 100644
--- a/spec/services/imports/data_protection_confirmation_import_service_spec.rb
+++ b/spec/services/imports/data_protection_confirmation_import_service_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Imports::DataProtectionConfirmationImportService do
end
context "when the organisation does exist" do
- let!(:organisation) { FactoryBot.create(:organisation, old_org_id:) }
+ let!(:organisation) { create(:organisation, :without_dpc, old_org_id:) }
context "when a data protection officer with matching name does not exists for the organisation" do
it "creates a data protection officer without sign in credentials" do
@@ -41,7 +41,7 @@ RSpec.describe Imports::DataProtectionConfirmationImportService do
it "successfully create a data protection confirmation record with the expected data" do
import_service.create_data_protection_confirmations("data_protection_directory")
- confirmation = Organisation.find_by(old_org_id:).data_protection_confirmations.last
+ confirmation = Organisation.find_by(old_org_id:).data_protection_confirmation
expect(confirmation.data_protection_officer.name).to eq("John Doe")
expect(confirmation.confirmed).to be_truthy
expect(Time.zone.local_to_utc(confirmation.created_at)).to eq(Time.utc(2018, 0o6, 0o5, 10, 36, 49))
@@ -50,13 +50,13 @@ RSpec.describe Imports::DataProtectionConfirmationImportService do
context "when a data protection officer with matching name already exists for the organisation" do
let!(:data_protection_officer) do
- FactoryBot.create(:user, :data_protection_officer, name: "John Doe", organisation:)
+ create(:user, :data_protection_officer, name: "John Doe", organisation:)
end
it "successfully creates a data protection confirmation record with the expected data" do
import_service.create_data_protection_confirmations("data_protection_directory")
- confirmation = Organisation.find_by(old_org_id:).data_protection_confirmations.last
+ confirmation = Organisation.find_by(old_org_id:).data_protection_confirmation
expect(confirmation.data_protection_officer.id).to eq(data_protection_officer.id)
expect(confirmation.confirmed).to be_truthy
expect(Time.zone.local_to_utc(confirmation.created_at)).to eq(Time.utc(2018, 0o6, 0o5, 10, 36, 49))
@@ -64,7 +64,7 @@ RSpec.describe Imports::DataProtectionConfirmationImportService do
context "when the data protection record has already been imported previously" do
before do
- FactoryBot.create(
+ create(
:data_protection_confirmation,
organisation:,
data_protection_officer:,
diff --git a/spec/shared/shared_log_examples.rb b/spec/shared/shared_log_examples.rb
index f9cc59633..1a2629ba2 100644
--- a/spec/shared/shared_log_examples.rb
+++ b/spec/shared/shared_log_examples.rb
@@ -104,5 +104,54 @@ RSpec.shared_examples "shared log examples" do |log_type|
end
end
end
+
+ describe "#verify_data_protection_confirmation" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
+ end
+
+ it "is valid if the DSA is signed" do
+ log = build(log_type, :in_progress, owning_organisation: create(:organisation))
+
+ expect(log).to be_valid
+ end
+
+ it "is valid when owning_organisation nil" do
+ log = build(log_type, owning_organisation: nil)
+
+ expect(log).to be_valid
+ end
+
+ it "is not valid if the DSA is not signed" do
+ log = build(log_type, owning_organisation: create(:organisation, :without_dpc))
+
+ expect(log).to be_valid
+ end
+ end
+
+ context "when flag enabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
+ end
+
+ it "is valid if the DSA is signed" do
+ log = build(log_type, :in_progress, owning_organisation: create(:organisation))
+
+ expect(log).to be_valid
+ end
+
+ it "is valid when owning_organisation nil" do
+ log = build(log_type, owning_organisation: nil)
+
+ expect(log).to be_valid
+ end
+
+ it "is not valid if the DSA is not signed" do
+ log = build(log_type, owning_organisation: create(:organisation, :without_dpc))
+
+ expect(log).not_to be_valid
+ expect(log.errors[:owning_organisation]).to eq(["Your organisation must accept the Data Sharing Agreement before you can create any logs."])
+ end
+ end
end
# rubocop:enable RSpec/AnyInstance
diff --git a/spec/views/logs/_create_for_org_actions.html.erb_spec.rb b/spec/views/logs/_create_for_org_actions.html.erb_spec.rb
new file mode 100644
index 000000000..f554778c2
--- /dev/null
+++ b/spec/views/logs/_create_for_org_actions.html.erb_spec.rb
@@ -0,0 +1,50 @@
+require "rails_helper"
+
+RSpec.describe "logs/_create_for_org_actions.html.erb" do
+ before do
+ allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:current_page?).and_return(true)
+ assign(:organisation, user.organisation)
+ end
+
+ let(:fragment) { Capybara::Node::Simple.new(rendered) }
+
+ let(:user) { create(:user) }
+
+ context "when flag disabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
+ end
+
+ it "shows create buttons" do
+ render
+
+ expect(fragment).to have_button("Create a new lettings log for this organisation")
+ expect(fragment).to have_button("Create a new sales log for this organisation")
+ end
+ end
+
+ context "when flag enabled" do
+ before do
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true)
+ end
+
+ context "with data sharing agreement" do
+ it "does include create log buttons" do
+ render
+ expect(fragment).to have_button("Create a new lettings log for this organisation")
+ expect(fragment).to have_button("Create a new sales log for this organisation")
+ end
+ end
+
+ context "without data sharing agreement" do
+ let(:user) { create(:user, organisation: create(:organisation, :without_dpc)) }
+
+ it "does not include create log buttons" do
+ render
+ expect(fragment).not_to have_button("Create a new lettings log for this organisation")
+ expect(fragment).not_to have_button("Create a new sales log for this organisation")
+ end
+ end
+ end
+end
diff --git a/spec/views/organisations/data_sharing_agreement.html.erb_spec.rb b/spec/views/organisations/data_sharing_agreement.html.erb_spec.rb
index 430d6f459..b20cecf4d 100644
--- a/spec/views/organisations/data_sharing_agreement.html.erb_spec.rb
+++ b/spec/views/organisations/data_sharing_agreement.html.erb_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe "organisations/data_sharing_agreement.html.erb", :aggregate_failu
Timecop.freeze(Time.zone.local(2023, 1, 10))
allow(view).to receive(:current_user).and_return(user)
assign(:organisation, organisation)
- assign(:data_sharing_agreement, data_sharing_agreement)
+ assign(:data_protection_confirmation, data_protection_confirmation)
end
after do
@@ -14,10 +14,10 @@ RSpec.describe "organisations/data_sharing_agreement.html.erb", :aggregate_failu
let(:fragment) { Capybara::Node::Simple.new(rendered) }
let(:organisation) { user.organisation }
- let(:data_sharing_agreement) { nil }
+ let(:data_protection_confirmation) { nil }
context "when dpo" do
- let(:user) { create(:user, is_dpo: true) }
+ let(:user) { create(:user, is_dpo: true, organisation: create(:organisation, :without_dpc)) }
it "renders dynamic content" do
render
@@ -36,32 +36,34 @@ RSpec.describe "organisations/data_sharing_agreement.html.erb", :aggregate_failu
expect(fragment).to have_content("12.2. For #{organisation.name}: Name: #{user.name}, Postal Address: #{organisation.address_row}, E-mail address: #{user.email}, Telephone number: #{organisation.phone}")
end
- context "when accepted" do
- let(:data_sharing_agreement) do
+ context "when confirmed" do
+ let(:data_protection_confirmation) do
create(
- :data_sharing_agreement,
+ :data_protection_confirmation,
organisation:,
- signed_at: Time.zone.now - 1.day,
+ created_at: Time.zone.now - 1.day,
)
end
+ let(:dpo) { data_protection_confirmation.data_protection_officer }
+
it "renders dynamic content" do
render
# dpo name
- expect(fragment).to have_content("Name: #{data_sharing_agreement.dpo_name}")
+ expect(fragment).to have_content("Name: #{dpo.name}")
# org details
- expect(fragment).to have_content("#{data_sharing_agreement.organisation_name} of #{data_sharing_agreement.organisation_address} (“CORE Data Provider”)")
+ expect(fragment).to have_content("#{organisation.name} of #{organisation.address_row} (“CORE Data Provider”)")
# header
- expect(fragment).to have_css("h2", text: "#{data_sharing_agreement.organisation_name} and Department for Levelling Up, Housing and Communities")
+ expect(fragment).to have_css("h2", text: "#{organisation.name} and Department for Levelling Up, Housing and Communities")
# does not show action buttons
expect(fragment).not_to have_button(text: "Accept this agreement")
expect(fragment).not_to have_link(text: "Cancel", href: "/organisations/#{organisation.id}/details")
# sees signed_at date
expect(fragment).to have_content("9th day of January 2023")
# Shows DPO and org details in 12.2
- expect(fragment).to have_content("12.2. For #{data_sharing_agreement.organisation_name}: Name: #{data_sharing_agreement.dpo_name}, Postal Address: #{data_sharing_agreement.organisation_address}, E-mail address: #{data_sharing_agreement.dpo_email}, Telephone number: #{data_sharing_agreement.organisation_phone_number}")
+ expect(fragment).to have_content("12.2. For #{organisation.name}: Name: #{dpo.name}, Postal Address: #{organisation.address_row}, E-mail address: #{dpo.email}, Telephone number: #{organisation.phone}")
end
end
end
@@ -86,30 +88,32 @@ RSpec.describe "organisations/data_sharing_agreement.html.erb", :aggregate_failu
expect(fragment).to have_content("12.2. For #{organisation.name}: Name: [DPO name], Postal Address: #{organisation.address_row}, E-mail address: [DPO email], Telephone number: #{organisation.phone}")
end
- context "when accepted" do
- let(:data_sharing_agreement) do
+ context "when confirmed" do
+ let(:data_protection_confirmation) do
create(
- :data_sharing_agreement,
+ :data_protection_confirmation,
organisation:,
- signed_at: Time.zone.now - 1.day,
+ created_at: Time.zone.now - 1.day,
)
end
+ let(:dpo) { data_protection_confirmation.data_protection_officer }
+
it "renders dynamic content" do
render
# sees signed_at date
expect(fragment).to have_content("9th day of January 2023")
# dpo name placedholder
- expect(fragment).to have_content("Name: #{data_sharing_agreement.dpo_name}")
+ expect(fragment).to have_content("Name: #{dpo.name}")
# org details
- expect(fragment).to have_content("#{data_sharing_agreement.organisation_name} of #{data_sharing_agreement.organisation_address} (“CORE Data Provider”)")
+ expect(fragment).to have_content("#{organisation.name} of #{organisation.address_row} (“CORE Data Provider”)")
# header
- expect(fragment).to have_css("h2", text: "#{data_sharing_agreement.organisation_name} and Department for Levelling Up, Housing and Communities")
+ expect(fragment).to have_css("h2", text: "#{organisation.name} and Department for Levelling Up, Housing and Communities")
# does not show action buttons
expect(fragment).not_to have_button(text: "Accept this agreement")
expect(fragment).not_to have_link(text: "Cancel", href: "/organisations/#{organisation.id}/details")
# Shows filled in details in 12.2
- expect(fragment).to have_content("12.2. For #{data_sharing_agreement.organisation_name}: Name: #{data_sharing_agreement.dpo_name}, Postal Address: #{data_sharing_agreement.organisation_address}, E-mail address: #{data_sharing_agreement.dpo_email}, Telephone number: #{data_sharing_agreement.organisation_phone_number}")
+ expect(fragment).to have_content("12.2. For #{organisation.name}: Name: #{dpo.name}, Postal Address: #{organisation.address_row}, E-mail address: #{dpo.email}, Telephone number: #{organisation.phone}")
end
end
end
diff --git a/spec/views/organisations/show.html.erb_spec.rb b/spec/views/organisations/show.html.erb_spec.rb
index ea392d76d..47ec10df4 100644
--- a/spec/views/organisations/show.html.erb_spec.rb
+++ b/spec/views/organisations/show.html.erb_spec.rb
@@ -4,8 +4,7 @@ RSpec.describe "organisations/show.html.erb" do
before do
Timecop.freeze(Time.zone.local(2023, 1, 10))
allow(view).to receive(:current_user).and_return(user)
- assign(:organisation, organisation)
- organisation.update!(data_sharing_agreement:)
+ assign(:organisation, user.organisation)
end
after do
@@ -13,14 +12,14 @@ RSpec.describe "organisations/show.html.erb" do
end
let(:fragment) { Capybara::Node::Simple.new(rendered) }
- let(:organisation) { user.organisation }
- let(:data_sharing_agreement) { nil }
+ let(:organisation_without_dpc) { create(:organisation, :without_dpc) }
+ let(:organisation_with_dsa) { create(:organisation) }
context "when flag disabled" do
- let(:user) { create(:user) }
+ let(:user) { create(:user, organisation: organisation_without_dpc) }
before do
- allow(FeatureToggle).to receive(:new_data_sharing_agreement?).and_return(false)
+ allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false)
end
it "does not include data sharing agreement row" do
@@ -31,7 +30,7 @@ RSpec.describe "organisations/show.html.erb" do
end
context "when dpo" do
- let(:user) { create(:user, is_dpo: true) }
+ let(:user) { create(:user, is_dpo: true, organisation: organisation_without_dpc) }
it "includes data sharing agreement row" do
render
@@ -48,11 +47,11 @@ RSpec.describe "organisations/show.html.erb" do
it "shows link to view data sharing agreement" do
render
- expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation.id}/data-sharing-agreement")
+ expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation_without_dpc.id}/data-sharing-agreement")
end
context "when accepted" do
- let(:data_sharing_agreement) { create(:data_sharing_agreement, organisation:, signed_at: Time.zone.now - 1.day) }
+ let(:user) { create(:user, organisation: organisation_with_dsa) }
it "includes data sharing agreement row" do
render
@@ -69,13 +68,13 @@ RSpec.describe "organisations/show.html.erb" do
it "shows link to view data sharing agreement" do
render
- expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation.id}/data-sharing-agreement")
+ expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation_with_dsa.id}/data-sharing-agreement")
end
end
end
context "when support user" do
- let(:user) { create(:user, :support) }
+ let(:user) { create(:user, :support, organisation: organisation_without_dpc) }
it "includes data sharing agreement row" do
render
@@ -98,11 +97,11 @@ RSpec.describe "organisations/show.html.erb" do
it "shows link to view data sharing agreement" do
render
- expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation.id}/data-sharing-agreement")
+ expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation_without_dpc.id}/data-sharing-agreement")
end
context "when accepted" do
- let(:data_sharing_agreement) { create(:data_sharing_agreement, organisation:, signed_at: Time.zone.now - 1.day) }
+ let(:user) { create(:user, :support, organisation: organisation_with_dsa) }
it "includes data sharing agreement row" do
render
@@ -113,25 +112,25 @@ RSpec.describe "organisations/show.html.erb" do
it "shows data sharing agreement accepted with date" do
render
- expect(fragment).to have_content("Accepted 09/01/2023")
+ expect(fragment).to have_content("Accepted 10/01/2023")
end
it "shows show name of who signed the agreement" do
render
- expect(fragment).to have_content(data_sharing_agreement.dpo_name)
+ expect(fragment).to have_content(user.organisation.data_protection_confirmation.data_protection_officer.name)
end
it "shows link to view data sharing agreement" do
render
- expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation.id}/data-sharing-agreement")
+ expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation_with_dsa.id}/data-sharing-agreement")
end
end
end
context "when not dpo" do
- let(:user) { create(:user) }
+ let(:user) { create(:user, organisation: organisation_without_dpc) }
it "includes data sharing agreement row" do
render
@@ -151,13 +150,11 @@ RSpec.describe "organisations/show.html.erb" do
it "shows link to view data sharing agreement" do
render
- expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation.id}/data-sharing-agreement")
+ expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation_without_dpc.id}/data-sharing-agreement")
end
context "when accepted" do
- let(:data_sharing_agreement) do
- create(:data_sharing_agreement, organisation:, signed_at: Time.zone.now - 1.day)
- end
+ let(:user) { create(:user, organisation: organisation_with_dsa) }
it "includes data sharing agreement row" do
render
@@ -172,7 +169,7 @@ RSpec.describe "organisations/show.html.erb" do
it "shows link to view data sharing agreement" do
render
- expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation.id}/data-sharing-agreement")
+ expect(fragment).to have_link(text: "View agreement", href: "/organisations/#{organisation_with_dsa.id}/data-sharing-agreement")
end
end
end