diff --git a/app/components/primary_navigation_component.rb b/app/components/primary_navigation_component.rb index 0389d30b3..e766a83ce 100644 --- a/app/components/primary_navigation_component.rb +++ b/app/components/primary_navigation_component.rb @@ -3,6 +3,7 @@ class PrimaryNavigationComponent < ViewComponent::Base def initialize(items:) @items = items + Rails.env.production? ? @items = @items.reject { |nav_item| nav_item.text.include?("Supported housing") } : @items super end diff --git a/app/components/search_component.rb b/app/components/search_component.rb index eefdc5309..b08c1039a 100644 --- a/app/components/search_component.rb +++ b/app/components/search_component.rb @@ -13,6 +13,8 @@ class SearchComponent < ViewComponent::Base request.path elsif request.path.include?("organisations") && request.path.include?("logs") request.path + elsif request.path.include?("organisations") && request.path.include?("supported-housing") + request.path elsif request.path.include?("users") user_path(current_user) elsif request.path.include?("organisations") diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index d57f9b215..7a92e5811 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -16,6 +16,14 @@ class OrganisationsController < ApplicationController @total_count = all_organisations.size end + def schemes + all_schemes = Scheme.where(organisation: @organisation) + + @pagy, @schemes = pagy(filtered_collection(all_schemes, search_term)) + @searched = search_term.presence + @total_count = all_schemes.size + end + def show redirect_to details_organisation_path(@organisation) end diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb new file mode 100644 index 000000000..83bff5542 --- /dev/null +++ b/app/controllers/schemes_controller.rb @@ -0,0 +1,26 @@ +class SchemesController < ApplicationController + include Pagy::Backend + include Modules::SearchFilter + + before_action :authenticate_user! + before_action :authenticate_scope! + + def index + redirect_to supported_housing_organisation_path(current_user.organisation) unless current_user.support? + all_schemes = Scheme.all + + @pagy, @schemes = pagy(filtered_collection(all_schemes, search_term)) + @searched = search_term.presence + @total_count = all_schemes.size + end + +private + + def search_term + params["search"] + end + + def authenticate_scope! + head :unauthorized and return unless current_user.data_coordinator? || current_user.support? + end +end diff --git a/app/helpers/navigation_items_helper.rb b/app/helpers/navigation_items_helper.rb index 1ebf17d11..c0b55c674 100644 --- a/app/helpers/navigation_items_helper.rb +++ b/app/helpers/navigation_items_helper.rb @@ -7,6 +7,14 @@ module NavigationItemsHelper NavigationItem.new("Organisations", organisations_path, organisations_current?(path)), NavigationItem.new("Users", "/users", users_current?(path)), NavigationItem.new("Logs", case_logs_path, logs_current?(path)), + NavigationItem.new("Supported housing", "/supported-housing", supported_housing_current?(path)), + ] + elsif current_user.data_coordinator? + [ + NavigationItem.new("Logs", case_logs_path, logs_current?(path)), + NavigationItem.new("Supported housing", "/supported-housing", subnav_supported_housing_path?(path)), + NavigationItem.new("Users", users_organisation_path(current_user.organisation), subnav_users_path?(path)), + NavigationItem.new("About your organisation", "/organisations/#{current_user.organisation.id}", subnav_details_path?(path)), ] else [ @@ -20,6 +28,7 @@ module NavigationItemsHelper def secondary_items(path, current_organisation_id) [ NavigationItem.new("Logs", "/organisations/#{current_organisation_id}/logs", subnav_logs_path?(path)), + NavigationItem.new("Supported housing", "/organisations/#{current_organisation_id}/supported-housing", subnav_supported_housing_path?(path)), NavigationItem.new("Users", "/organisations/#{current_organisation_id}/users", subnav_users_path?(path)), NavigationItem.new("About this organisation", "/organisations/#{current_organisation_id}", subnav_details_path?(path)), ] @@ -35,8 +44,16 @@ private path == "/users" end + def supported_housing_current?(path) + path == "/supported-housing" + end + def organisations_current?(path) - path == "/organisations" || subnav_users_path?(path) || subnav_logs_path?(path) || subnav_details_path?(path) + path == "/organisations" || subnav_users_path?(path) || subnav_logs_path?(path) || subnav_details_path?(path) || subnav_supported_housing_path?(path) + end + + def subnav_supported_housing_path?(path) + path.include?("/organisations") && path.include?("/supported-housing") end def subnav_users_path?(path) diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 6f04c037a..0319befa4 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -4,6 +4,7 @@ class Organisation < ApplicationRecord has_many :managed_case_logs, class_name: "CaseLog", foreign_key: "managing_organisation_id" has_many :data_protection_confirmations has_many :organisation_rent_periods + has_many :schemes scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by, ->(param) { search_by_name(param) } diff --git a/app/models/scheme.rb b/app/models/scheme.rb new file mode 100644 index 000000000..7f6ec337e --- /dev/null +++ b/app/models/scheme.rb @@ -0,0 +1,7 @@ +class Scheme < ApplicationRecord + belongs_to :organisation + + scope :search_by_code, ->(code) { where("code ILIKE ?", "%#{code}%") } + scope :search_by_service_name, ->(name) { where("service_name ILIKE ?", "%#{name}%") } + scope :search_by, ->(param) { search_by_code(param).or(search_by_service_name(param)) } +end diff --git a/app/views/organisations/schemes.html.erb b/app/views/organisations/schemes.html.erb new file mode 100644 index 000000000..b28d9c306 --- /dev/null +++ b/app/views/organisations/schemes.html.erb @@ -0,0 +1,22 @@ +<% item_label = format_label(@pagy.count, "scheme") %> +<% title = format_title(@searched, "Supported housing services", current_user, item_label, @pagy.count, @organisation.name) %> + +<% content_for :title, title %> + +<%= render partial: "organisations/headings", locals: current_user.support? ? { main: @organisation.name, sub: nil } : { main: "Supported housing services", sub: current_user.organisation.name } %> + +<% if current_user.support? %> + <%= render SubNavigationComponent.new( + items: secondary_items(request.path, @organisation.id), + ) %> +<% end %> + +

Supported housing services

+ +<%= render SearchComponent.new(current_user:, search_label: "Search by service name or code", value: @searched) %> + +
+ +<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %> + +<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %> diff --git a/app/views/schemes/_scheme_list.html.erb b/app/views/schemes/_scheme_list.html.erb new file mode 100644 index 000000000..002a215b4 --- /dev/null +++ b/app/views/schemes/_scheme_list.html.erb @@ -0,0 +1,39 @@ +
+ <%= govuk_table do |table| %> + <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> + + <% if searched.present? %> + <%= pagy.count %> <%= item_label %> found matching ‘<%= searched %>’ of <%= total_count %> total schemes. <%= govuk_link_to("Clear search", request.path) %> + <% else %> + <%= pagy.count %> total schemes. + <% end %> + + <% end %> + <%= table.head do |head| %> + <%= head.row do |row| %> + <% row.cell(header: true, text: "Code", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Service", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Managing agent", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Created", html_attributes: { + scope: "col", + }) %> + <% end %> + <% end %> + <% @schemes.each do |scheme| %> + <%= table.body do |body| %> + <%= body.row do |row| %> + <% row.cell(text: scheme.code) %> + <% row.cell(text: scheme.service_name) %> + <% row.cell(text: scheme.organisation.name) %> + <% row.cell(text: scheme.created_at.to_formatted_s(:govuk_date)) %> + <% end %> + <% end %> + <% end %> + <% end %> +
diff --git a/app/views/schemes/index.html.erb b/app/views/schemes/index.html.erb new file mode 100644 index 000000000..b96e7bdc3 --- /dev/null +++ b/app/views/schemes/index.html.erb @@ -0,0 +1,16 @@ +<% item_label = format_label(@pagy.count, "scheme") %> +<% title = format_title(@searched, "Supported housing services", current_user, item_label, @pagy.count, nil) %> + +<% content_for :title, title %> + +<%= render partial: "organisations/headings", locals: current_user.support? ? { main: "Supported housing services", sub: nil } : { main: "Supported housing services", sub: current_user.organisation.name } %> + +

Supported housing services

+ +<%= render SearchComponent.new(current_user:, search_label: "Search by service name or code", value: @searched) %> + +
+ +<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %> + +<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %> diff --git a/config/routes.rb b/config/routes.rb index 4f7bc91e5..c7b3df36e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -35,6 +35,8 @@ Rails.application.routes.draw do get "edit/password", to: "users#edit_password" end + resources :schemes, path: "/supported-housing", only: [:index] + resources :users do member do get "deactivate", to: "users#deactivate" @@ -48,6 +50,7 @@ Rails.application.routes.draw do get "users", to: "organisations#users" get "users/invite", to: "users/account#new" get "logs", to: "organisations#logs" + get "supported-housing", to: "organisations#schemes" end end diff --git a/db/migrate/20220608144156_create_schemes.rb b/db/migrate/20220608144156_create_schemes.rb new file mode 100644 index 000000000..2f96bc29f --- /dev/null +++ b/db/migrate/20220608144156_create_schemes.rb @@ -0,0 +1,11 @@ +class CreateSchemes < ActiveRecord::Migration[7.0] + def change + create_table :schemes do |t| + t.string :code + t.string :service_name + t.references :organisation, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4cca6211e..cd132897d 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: 2022_06_06_082639) do +ActiveRecord::Schema[7.0].define(version: 2022_06_08_144156) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -192,9 +192,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_06_06_082639) do t.integer "joint" t.bigint "created_by_id" t.integer "illness_type_0" + t.integer "retirement_value_check" t.integer "tshortfall_known" t.integer "shelteredaccom" - t.integer "retirement_value_check" t.integer "pregnancy_value_check" t.index ["created_by_id"], name: "index_case_logs_on_created_by_id" t.index ["managing_organisation_id"], name: "index_case_logs_on_managing_organisation_id" @@ -232,7 +232,7 @@ ActiveRecord::Schema[7.0].define(version: 2022_06_06_082639) do create_table "logs_exports", force: :cascade do |t| t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" } - t.datetime "started_at", precision: nil, null: false + t.datetime "started_at", null: false t.integer "base_number", default: 1, null: false t.integer "increment_number", default: 1, null: false t.boolean "empty_export", default: false, null: false @@ -277,6 +277,15 @@ ActiveRecord::Schema[7.0].define(version: 2022_06_06_082639) do t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true end + create_table "schemes", force: :cascade do |t| + t.string "code" + t.string "service_name" + t.bigint "organisation_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["organisation_id"], name: "index_schemes_on_organisation_id" + end + create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -330,4 +339,5 @@ ActiveRecord::Schema[7.0].define(version: 2022_06_06_082639) do t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" end + add_foreign_key "schemes", "organisations" end diff --git a/db/seeds.rb b/db/seeds.rb index 54ffb843a..356e020b4 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -54,6 +54,45 @@ unless Rails.env.test? pp "Seeded 3 dummy users" end + if Rails.env.development? + dummy_org = Organisation.find_or_create_by!( + name: "FooBar LTD", + address_line1: "Higher Kingston", + address_line2: "Yeovil", + postcode: "BA21 4AT", + holds_own_stock: false, + other_stock_owners: "None", + managing_agents: "None", + provider_type: "LA", + ) + + pp "Seeded dummy FooBar LTD organisation" + end + + if Rails.env.development? && Scheme.count.zero? + Scheme.create!( + code: "S878", + service_name: "Beulahside Care", + organisation: org, + created_at: Time.zone.now, + ) + + Scheme.create!( + code: "S312", + service_name: "Abdullahview Point", + organisation: org, + created_at: Time.zone.now, + ) + + Scheme.create!( + code: "7XYZ", + service_name: "Caspermouth Center", + organisation: dummy_org, + created_at: Time.zone.now, + ) + end + + pp "Seeded 3 dummy schemes" if LaRentRange.count.zero? Dir.glob("config/rent_range_data/*.csv").each do |path| start_year = File.basename(path, ".csv") diff --git a/spec/components/primary_navigation_component_spec.rb b/spec/components/primary_navigation_component_spec.rb index 0de1c58d1..9a3c91d09 100644 --- a/spec/components/primary_navigation_component_spec.rb +++ b/spec/components/primary_navigation_component_spec.rb @@ -6,6 +6,7 @@ RSpec.describe PrimaryNavigationComponent, type: :component do NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true), NavigationItemsHelper::NavigationItem.new("Users", "/users", false), NavigationItemsHelper::NavigationItem.new("Logs ", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), ] end @@ -24,6 +25,7 @@ RSpec.describe PrimaryNavigationComponent, type: :component do expect(navigation_panel).to be_highlighted_item(items[0], "/something-else") expect(navigation_panel).not_to be_highlighted_item(items[1], "/something-else") expect(navigation_panel).not_to be_highlighted_item(items[2], "/something-else") + expect(navigation_panel).not_to be_highlighted_item(items[3], "/something-else") end end @@ -34,6 +36,18 @@ RSpec.describe PrimaryNavigationComponent, type: :component do expect(result.text).to include("Organisations") expect(result.text).to include("Users") expect(result.text).to include("Logs") + expect(result.text).to include("Supported housing") + end + end + + context "when production environment" do + before do + allow(Rails.env).to receive(:production?).and_return(true) + end + + it "doesn't render supported housing" do + result = render_inline(described_class.new(items:)) + expect(result.text).not_to include("Supported housing") end end end diff --git a/spec/factories/scheme.rb b/spec/factories/scheme.rb new file mode 100644 index 000000000..8d15032fe --- /dev/null +++ b/spec/factories/scheme.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :scheme do + code { Faker::Name.initials(number: 4) } + service_name { Faker::Name.name_with_middle } + organisation + created_at { Time.zone.now } + end +end diff --git a/spec/features/schemes_spec.rb b/spec/features/schemes_spec.rb new file mode 100644 index 000000000..b1fe131fe --- /dev/null +++ b/spec/features/schemes_spec.rb @@ -0,0 +1,78 @@ +require "rails_helper" + +RSpec.describe "Supported housing scheme Features" do + context "when viewing list of schemes" do + context "when I am signed as a support user in there are schemes in the database" do + let(:user) { FactoryBot.create(:user, :support, last_sign_in_at: Time.zone.now) } + let!(:schemes) { FactoryBot.create_list(:scheme, 5) } + let!(:scheme_to_search) { FactoryBot.create(:scheme) } + let(:notify_client) { instance_double(Notifications::Client) } + let(:confirmation_token) { "MCDH5y6Km-U7CFPgAMVS" } + let(:devise_notify_mailer) { DeviseNotifyMailer.new } + let(:otp) { "999111" } + + before do + allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) + allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) + allow(Devise).to receive(:friendly_token).and_return(confirmation_token) + allow(notify_client).to receive(:send_email).and_return(true) + allow(SecureRandom).to receive(:random_number).and_return(otp) + visit("/logs") + fill_in("user[email]", with: user.email) + fill_in("user[password]", with: user.password) + click_button("Sign in") + fill_in("code", with: otp) + click_button("Submit") + end + + it "displays the link to the supported housing" do + expect(page).to have_link("Supported housing", href: "/supported-housing") + end + + context "when I click Supported housing" do + before do + click_link "Supported housing", href: "/supported-housing" + end + + it "shows list of schemes" do + schemes.each do |scheme| + expect(page).to have_content(scheme.code) + end + end + + context "when I search for a specific scheme" do + it "there is a search bar with a message and search button for schemes" do + expect(page).to have_field("search") + expect(page).to have_content("Search by service name or code") + expect(page).to have_button("Search") + end + + context "when I fill in search information and press the search button" do + before do + fill_in("search", with: scheme_to_search.code) + click_button("Search") + end + + it "displays scheme matching the scheme code" do + expect(page).to have_content(scheme_to_search.code) + end + + context "when I want to clear results" do + it "there is link to clear the search results" do + expect(page).to have_link("Clear search") + end + + it "displays all schemes after I clear the search results" do + click_link("Clear search") + expect(page).to have_content(scheme_to_search.code) + schemes.each do |scheme| + expect(page).to have_content(scheme.code) + end + end + end + end + end + end + end + end +end diff --git a/spec/helpers/navigation_items_helper_spec.rb b/spec/helpers/navigation_items_helper_spec.rb index 1bcf23f1a..8aa02eb82 100644 --- a/spec/helpers/navigation_items_helper_spec.rb +++ b/spec/helpers/navigation_items_helper_spec.rb @@ -8,10 +8,26 @@ RSpec.describe NavigationItemsHelper do describe "#primary items" do context "when the user is a data coordinator" do + context "when the user is on the logs page" do + let(:expected_navigation_items) do + [ + NavigationItemsHelper::NavigationItem.new("Logs", "/logs", true), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), + NavigationItemsHelper::NavigationItem.new("Users", users_path, false), + NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false), + ] + end + + it "returns navigation items with the users item set as current" do + expect(primary_items("/logs", current_user)).to eq(expected_navigation_items) + end + end + context "when the user is on the users page" do let(:expected_navigation_items) do [ NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), NavigationItemsHelper::NavigationItem.new("Users", users_path, true), NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false), ] @@ -26,6 +42,7 @@ RSpec.describe NavigationItemsHelper do let(:expected_navigation_items) do [ NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), NavigationItemsHelper::NavigationItem.new("Users", users_path, false), NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, true), ] @@ -40,6 +57,7 @@ RSpec.describe NavigationItemsHelper do let(:expected_navigation_items) do [ NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false), NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false), ] @@ -54,12 +72,28 @@ RSpec.describe NavigationItemsHelper do context "when the user is a support user" do let(:current_user) { FactoryBot.create(:user, :support) } + context "when the user is on the logs page" do + let(:expected_navigation_items) do + [ + NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false), + NavigationItemsHelper::NavigationItem.new("Users", "/users", false), + NavigationItemsHelper::NavigationItem.new("Logs", "/logs", true), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), + ] + end + + it "returns navigation items with the users item set as current" do + expect(primary_items("/logs", current_user)).to eq(expected_navigation_items) + end + end + context "when the user is on the users page" do let(:expected_navigation_items) do [ NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false), NavigationItemsHelper::NavigationItem.new("Users", "/users", true), NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), ] end @@ -74,6 +108,7 @@ RSpec.describe NavigationItemsHelper do NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false), NavigationItemsHelper::NavigationItem.new("Users", "/users", false), NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), ] end @@ -82,6 +117,21 @@ RSpec.describe NavigationItemsHelper do end end + context "when the user is on the supported housing page" do + let(:expected_navigation_items) do + [ + NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false), + NavigationItemsHelper::NavigationItem.new("Users", "/users", false), + NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", true), + ] + end + + it "returns navigation items with the users item set as current" do + expect(primary_items("/supported-housing", current_user)).to eq(expected_navigation_items) + end + end + context "when the user is on the specific organisation's page" do context "when the user is on organisation logs page" do let(:required_sub_path) { "logs" } @@ -90,12 +140,14 @@ RSpec.describe NavigationItemsHelper do NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true), NavigationItemsHelper::NavigationItem.new("Users", "/users", false), NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), ] end let(:expected_secondary_navigation_items) do [ NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", true), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", false), NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false), NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", false), ] @@ -114,12 +166,14 @@ RSpec.describe NavigationItemsHelper do NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true), NavigationItemsHelper::NavigationItem.new("Users", "/users", false), NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), ] end let(:expected_secondary_navigation_items) do [ NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", false), NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", true), NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", false), ] @@ -131,6 +185,32 @@ RSpec.describe NavigationItemsHelper do end end + context "when the user is on organisation schemes page" do + let(:required_sub_path) { "supported-housing" } + let(:expected_navigation_items) do + [ + NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true), + NavigationItemsHelper::NavigationItem.new("Users", "/users", false), + NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), + ] + end + + let(:expected_secondary_navigation_items) do + [ + NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", true), + NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false), + NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", false), + ] + end + + it "returns navigation items with the schemes item set as current" do + expect(primary_items("/organisations/#{current_user.organisation.id}/#{required_sub_path}", current_user)).to eq(expected_navigation_items) + expect(secondary_items("/organisations/#{current_user.organisation.id}/#{required_sub_path}", current_user.organisation.id)).to eq(expected_secondary_navigation_items) + end + end + context "when the user is on organisation details page" do let(:required_sub_path) { "details" } let(:expected_navigation_items) do @@ -138,12 +218,14 @@ RSpec.describe NavigationItemsHelper do NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true), NavigationItemsHelper::NavigationItem.new("Users", "/users", false), NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false), ] end let(:expected_secondary_navigation_items) do [ NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", false), + NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", false), NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false), NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", true), ] diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index a524e1e85..17b8ec200 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -4,6 +4,7 @@ RSpec.describe Organisation, type: :model do describe "#new" do let(:user) { FactoryBot.create(:user) } let!(:organisation) { user.organisation } + let!(:scheme) { FactoryBot.create(:scheme, organisation:) } it "has expected fields" do expect(organisation.attribute_names).to include("name", "phone", "provider_type") @@ -13,6 +14,10 @@ RSpec.describe Organisation, type: :model do expect(organisation.users.first).to eq(user) end + it "has schemes" do + expect(organisation.schemes.first).to eq(scheme) + end + it "validates provider_type presence" do expect { FactoryBot.create(:organisation, provider_type: nil) } .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Provider type can’t be blank") diff --git a/spec/models/scheme_spec.rb b/spec/models/scheme_spec.rb new file mode 100644 index 000000000..bbeec6746 --- /dev/null +++ b/spec/models/scheme_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Scheme, type: :model do + describe "#new" do + let(:scheme) { FactoryBot.create(:scheme) } + + it "belongs to an organisation" do + expect(scheme.organisation).to be_a(Organisation) + end + + describe "scopes" do + let!(:scheme_1) { FactoryBot.create(:scheme) } + let!(:scheme_2) { FactoryBot.create(:scheme) } + + context "when searching by code" do + it "returns case insensitive matching records" do + expect(described_class.search_by_code(scheme_1.code.upcase).count).to eq(1) + expect(described_class.search_by_code(scheme_1.code.downcase).count).to eq(1) + expect(described_class.search_by_code(scheme_1.code.downcase).first.code).to eq(scheme_1.code) + expect(described_class.search_by_code(scheme_2.code.upcase).count).to eq(1) + expect(described_class.search_by_code(scheme_2.code.downcase).count).to eq(1) + expect(described_class.search_by_code(scheme_2.code.downcase).first.code).to eq(scheme_2.code) + end + end + + context "when searching by service name" do + it "returns case insensitive matching records" do + expect(described_class.search_by_service_name(scheme_1.service_name.upcase).count).to eq(1) + expect(described_class.search_by_service_name(scheme_1.service_name.downcase).count).to eq(1) + expect(described_class.search_by_service_name(scheme_1.service_name.downcase).first.service_name).to eq(scheme_1.service_name) + expect(described_class.search_by_service_name(scheme_2.service_name.upcase).count).to eq(1) + expect(described_class.search_by_service_name(scheme_2.service_name.downcase).count).to eq(1) + expect(described_class.search_by_service_name(scheme_2.service_name.downcase).first.service_name).to eq(scheme_2.service_name) + end + end + + context "when searching by all searchable field" do + it "returns case insensitive matching records" do + expect(described_class.search_by(scheme_1.code.upcase).count).to eq(1) + expect(described_class.search_by(scheme_1.code.downcase).count).to eq(1) + expect(described_class.search_by(scheme_1.code.downcase).first.code).to eq(scheme_1.code) + expect(described_class.search_by_service_name(scheme_2.service_name.upcase).count).to eq(1) + expect(described_class.search_by_service_name(scheme_2.service_name.downcase).count).to eq(1) + expect(described_class.search_by_service_name(scheme_2.service_name.downcase).first.service_name).to eq(scheme_2.service_name) + end + end + end + end +end diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 8470d50ef..cd976453d 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -30,10 +30,141 @@ RSpec.describe OrganisationsController, type: :request do get "/organisations", headers: headers, params: {} expect(response).to redirect_to("/account/sign-in") end + + it "does not let you see supported housing list" do + get "/organisations/#{organisation.id}/supported-housing", headers: headers, params: {} + expect(response).to redirect_to("/account/sign-in") + end end end context "when user is signed in" do + describe "#schemes" do + context "when support user" do + let(:user) { FactoryBot.create(:user, :support) } + let!(:schemes) { FactoryBot.create_list(:scheme, 5) } + let!(:same_org_scheme) { FactoryBot.create(:scheme, organisation: user.organisation) } + + before do + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in user + get "/organisations/#{organisation.id}/supported-housing", headers:, params: {} + end + + it "has page heading" do + expect(page).to have_content("Supported housing services") + end + + it "shows a search bar" do + expect(page).to have_field("search", type: "search") + end + + it "has hidden accebility field with description" do + expected_field = "

Supported housing services

" + expect(CGI.unescape_html(response.body)).to include(expected_field) + end + + it "shows only schemes belonging to the same organisation" do + expect(page).to have_content(same_org_scheme.code) + schemes.each do |scheme| + expect(page).not_to have_content(scheme.code) + end + end + + context "when searching" do + let!(:searched_scheme) { FactoryBot.create(:scheme, code: "CODE321", organisation: user.organisation) } + let(:search_param) { "CODE321" } + + before do + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + get "/organisations/#{organisation.id}/supported-housing?search=#{search_param}" + end + + it "returns matching results" do + expect(page).to have_content(searched_scheme.code) + schemes.each do |scheme| + expect(page).not_to have_content(scheme.code) + end + end + + it "updates the table caption" do + expect(page).to have_content("1 scheme found matching ‘#{search_param}’") + end + + it "has search in the title" do + expect(page).to have_title("#{user.organisation.name} (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + end + end + end + + context "when data coordinator user" do + let(:user) { FactoryBot.create(:user, :data_coordinator) } + let!(:schemes) { FactoryBot.create_list(:scheme, 5) } + let!(:same_org_scheme) { FactoryBot.create(:scheme, organisation: user.organisation) } + + before do + sign_in user + get "/organisations/#{organisation.id}/supported-housing", headers:, params: {} + end + + it "has page heading" do + expect(page).to have_content("Supported housing services") + end + + it "shows a search bar" do + expect(page).to have_field("search", type: "search") + end + + it "has hidden accebility field with description" do + expected_field = "

Supported housing services

" + expect(CGI.unescape_html(response.body)).to include(expected_field) + end + + it "shows only schemes belonging to the same organisation" do + expect(page).to have_content(same_org_scheme.code) + schemes.each do |scheme| + expect(page).not_to have_content(scheme.code) + end + end + + context "with schemes that are not in scope for the user, i.e. that they do not belong to" do + let!(:unauthorised_organisation) { FactoryBot.create(:organisation) } + + before do + get "/organisations/#{unauthorised_organisation.id}/supported-housing", headers:, params: {} + end + + it "returns not found 404 from org details route" do + expect(response).to have_http_status(:not_found) + end + end + + context "when searching" do + let!(:searched_scheme) { FactoryBot.create(:scheme, code: "CODE321", organisation: user.organisation) } + let(:search_param) { "CODE321" } + + before do + get "/organisations/#{organisation.id}/supported-housing?search=#{search_param}" + end + + it "returns matching results" do + expect(page).to have_content(searched_scheme.code) + schemes.each do |scheme| + expect(page).not_to have_content(scheme.code) + end + end + + it "updates the table caption" do + expect(page).to have_content("1 scheme found matching ‘#{search_param}’") + end + + it "has search in the title" do + expect(page).to have_title("Supported housing services (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + end + end + end + end + describe "#show" do context "with an organisation that the user belongs to" do before do diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb new file mode 100644 index 000000000..13e612c46 --- /dev/null +++ b/spec/requests/schemes_controller_spec.rb @@ -0,0 +1,163 @@ +require "rails_helper" + +RSpec.describe SchemesController, type: :request do + let(:organisation) { user.organisation } + let(:headers) { { "Accept" => "text/html" } } + let(:page) { Capybara::Node::Simple.new(response.body) } + let(:user) { FactoryBot.create(:user, :support) } + let!(:schemes) { FactoryBot.create_list(:scheme, 5) } + + describe "#index" do + context "when not signed in" do + it "redirects to the sign in page" do + get "/supported-housing" + expect(response).to redirect_to("/account/sign-in") + end + end + + context "when signed in as a data provider user" do + let(:user) { FactoryBot.create(:user) } + + before do + sign_in user + get "/supported-housing" + end + + it "returns 401 unauthorized" do + request + expect(response).to have_http_status(:unauthorized) + end + end + + context "when signed in as a data coordinator user" do + let(:user) { FactoryBot.create(:user, :data_coordinator) } + + before do + sign_in user + get "/supported-housing" + end + + it "redirects to the organisation schemes path" do + follow_redirect! + expect(path).to match("/organisations/#{user.organisation.id}/supported-housing") + end + end + + context "when signed in as a support user" do + before do + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in user + get "/supported-housing" + end + + it "has page heading" do + expect(page).to have_content("Supported housing services") + end + + it "shows all schemes" do + schemes.each do |scheme| + expect(page).to have_content(scheme.code) + end + end + + it "shows a search bar" do + expect(page).to have_field("search", type: "search") + end + + it "has correct title" do + expect(page).to have_title("Supported housing services - Submit social housing lettings and sales data (CORE) - GOV.UK") + end + + it "shows the total organisations count" do + expect(CGI.unescape_html(response.body)).to match("#{schemes.count} total schemes.") + end + + it "has hidden accebility field with description" do + expected_field = "

Supported housing services

" + expect(CGI.unescape_html(response.body)).to include(expected_field) + end + + context "when paginating over 20 results" do + let(:total_schemes_count) { Scheme.count } + + before do + FactoryBot.create_list(:scheme, 20) + end + + context "when on the first page" do + before do + get "/supported-housing" + end + + it "shows the total schemes count" do + expect(CGI.unescape_html(response.body)).to match("#{total_schemes_count} total schemes.") + end + + it "shows which schemes are being shown on the current page" do + expect(CGI.unescape_html(response.body)).to match("Showing 1 to 20 of #{total_schemes_count} schemes") + end + + it "has correct page 1 of 2 title" do + expect(page).to have_title("Supported housing services (page 1 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK") + end + + it "has pagination links" do + expect(page).to have_content("Previous") + expect(page).not_to have_link("Previous") + expect(page).to have_content("Next") + expect(page).to have_link("Next") + end + end + + context "when on the second page" do + before do + get "/supported-housing?page=2" + end + + it "shows the total schemes count" do + expect(CGI.unescape_html(response.body)).to match("#{total_schemes_count} total schemes.") + end + + it "has pagination links" do + expect(page).to have_content("Previous") + expect(page).to have_link("Previous") + expect(page).to have_content("Next") + expect(page).not_to have_link("Next") + end + + it "shows which schemes are being shown on the current page" do + expect(CGI.unescape_html(response.body)).to match("Showing 21 to 25 of #{total_schemes_count} schemes") + end + + it "has correct page 1 of 2 title" do + expect(page).to have_title("Supported housing services (page 2 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK") + end + end + end + + context "when searching" do + let!(:searched_scheme) { FactoryBot.create(:scheme, code: "CODE321") } + let(:search_param) { "CODE321" } + + before do + get "/supported-housing?search=#{search_param}" + end + + it "returns matching results" do + expect(page).to have_content(searched_scheme.code) + schemes.each do |scheme| + expect(page).not_to have_content(scheme.code) + end + end + + it "updates the table caption" do + expect(page).to have_content("1 scheme found matching ‘#{search_param}’") + end + + it "has search in the title" do + expect(page).to have_title("Supported housing services (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + end + end + end + end +end