From 1a1d51b77247e01d3475b01420430a1143347973 Mon Sep 17 00:00:00 2001 From: Adam Lee <32270711+Gubbsy@users.noreply.github.com> Date: Fri, 4 Aug 2023 12:11:33 +0100 Subject: [PATCH 01/28] Fix/app memory allocation (#1133) * Bump research memory allocation to 1GB * Bump mem to 2GB on UATT & PROD, restore research to 256MB --- manifest-production.yml | 2 +- manifest-uat.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest-production.yml b/manifest-production.yml index 4cd9d10e0..4b365b3a0 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -13,7 +13,7 @@ applications: processes: - type: web command: bundle exec rake cf:on_first_instance db:migrate && rails s -p $PORT - memory: 256M + memory: 2GB instances: 2 health-check-type: http health-check-http-endpoint: /health diff --git a/manifest-uat.yml b/manifest-uat.yml index ec8b89ef5..7276a9785 100644 --- a/manifest-uat.yml +++ b/manifest-uat.yml @@ -13,7 +13,7 @@ applications: processes: - type: web command: bundle exec rake cf:on_first_instance db:migrate && rails s -p $PORT - memory: 256M + memory: 2GB instances: 2 health-check-type: http health-check-http-endpoint: /health From b5da4f3b16859e648b01095a681face630586c4d Mon Sep 17 00:00:00 2001 From: Jack <91466216+JJD1990@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:13:33 +0100 Subject: [PATCH 02/28] reordered reconnection report data (#1132) --- app/helpers/import_helper.rb | 2 +- .../admin_portal/reconnection_report/show.html.erb | 14 ++++++-------- .../salesforce/import/import_salesforce_api.rb | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index 780d3efa7..ccb20792b 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -373,7 +373,7 @@ def populate_temporary_table_and_run_report(projects_for_reconnection) project.Project_Reference_Number__c) pop_temp_table_sql << "INSERT INTO reconnection_projects VALUES (" \ - "#{name}, #{title}, #{ref}, #{area});" + "#{name}, #{title}, #{area}, #{ref} );" end diff --git a/app/views/admin_portal/reconnection_report/show.html.erb b/app/views/admin_portal/reconnection_report/show.html.erb index 3a622f587..4d5e0c73f 100644 --- a/app/views/admin_portal/reconnection_report/show.html.erb +++ b/app/views/admin_portal/reconnection_report/show.html.erb @@ -11,9 +11,9 @@ Project Owner - Project Reference Number Project Title Project Area/Country + Project Reference Number Reconnection Date @@ -22,17 +22,15 @@ <%# This could be a nested loop, but to be explicit: %> <%# row[0] - Project Owner%> - <%# row[2] - Project Reference Number%> - <%# row[3] - Project Title%> - <%# row[1] - Project area/country%> + <%# row[1] - Project Title%> + <%# row[2] - Project area/country%> + <%# row[3] - Project Reference Number%> <%# row[4] - Project Reconnection Date%> <%=row[0]%> - <%=row[2]%> + <%=row[1]%> + <%=row[2].present? ? row[2]: 'Not specified'%> <%=row[3]%> - <%=row[1].present? ? row[1]: 'Not specified'%> <%=row[4].present? ? row[4].strftime('%d-%m-%Y').to_s : 'Not reconnected' %> - - <% end %> diff --git a/lib/apis/salesforce/import/import_salesforce_api.rb b/lib/apis/salesforce/import/import_salesforce_api.rb index 4d1e2e23f..6bbc464d0 100644 --- a/lib/apis/salesforce/import/import_salesforce_api.rb +++ b/lib/apis/salesforce/import/import_salesforce_api.rb @@ -180,7 +180,7 @@ def retrieve_existing_account_info(name, postcode, org_id) def get_projects_selected_for_reconnection query = "SELECT Owner.Name, Project_Title__c, " \ - "Project_Reference_Number__c, Region__c " \ + "Region__c, Project_Reference_Number__c " \ "FROM Case where Export_to_IMS_Portal__c = true " restforce_response = run_salesforce_query( From 2fc89d06e4e6d8bc4dd00a7f968956dec078679c Mon Sep 17 00:00:00 2001 From: Paul Trelease Date: Mon, 7 Aug 2023 14:03:50 +0100 Subject: [PATCH 03/28] Feature/manifest to increase disk for uat prod (#1134) * increased disk storage values for UAT and prod from 1 to 3GB --- manifest-production.yml | 1 + manifest-uat.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/manifest-production.yml b/manifest-production.yml index 4b365b3a0..8437d5db4 100644 --- a/manifest-production.yml +++ b/manifest-production.yml @@ -14,6 +14,7 @@ applications: - type: web command: bundle exec rake cf:on_first_instance db:migrate && rails s -p $PORT memory: 2GB + disk_quota: 3GB instances: 2 health-check-type: http health-check-http-endpoint: /health diff --git a/manifest-uat.yml b/manifest-uat.yml index 7276a9785..a00ebd841 100644 --- a/manifest-uat.yml +++ b/manifest-uat.yml @@ -14,6 +14,7 @@ applications: - type: web command: bundle exec rake cf:on_first_instance db:migrate && rails s -p $PORT memory: 2GB + disk_quota: 3GB instances: 2 health-check-type: http health-check-http-endpoint: /health From 63081996f8d078bc61347235c96a0685751afcca Mon Sep 17 00:00:00 2001 From: Jack <91466216+JJD1990@users.noreply.github.com> Date: Tue, 8 Aug 2023 08:50:37 +0100 Subject: [PATCH 04/28] Fix/org name length 255 (#1135) * validation for organisation name set to 255, spec written * error message added to en & cy yml files, validade_length/too_long method added to organisation model. * better rspec test written for organisation name validation * deleted redundant validate_length method in organisation.rb --- app/models/organisation.rb | 1 + config/locales/cy.yml | 1 + config/locales/en.yml | 1 + spec/models/organisation_spec.rb | 17 +++++++++++++++++ 4 files changed, 20 insertions(+) create mode 100644 spec/models/organisation_spec.rb diff --git a/app/models/organisation.rb b/app/models/organisation.rb index a540db638..eda2024a1 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -46,6 +46,7 @@ class Organisation < ApplicationRecord validates :custom_org_type, presence: true, if: :validate_custom_org_type? validate :validate_mission_array, if: :validate_mission? validates :name, presence: true, if: :validate_name? + validates :name, length: { maximum: 255 } validates :name, presence: true, if: :validate_address? validates :line1, presence: true, if: :validate_address? validates :townCity, presence: true, if: :validate_address? diff --git a/config/locales/cy.yml b/config/locales/cy.yml index ca78fb019..83db170c0 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -204,6 +204,7 @@ cy: not_a_number: "Mae'n rhaid i rif cwmni fod yn rhif, fel 12345678" name: blank: "Rhowch enw eich sefydliad" + too_long: "Rhaid i Enw Sefydliad fod yn 225 nod neu lai" line1: blank: "Rhowch linell gyntaf cyfeiriad eich sefydliad" townCity: diff --git a/config/locales/en.yml b/config/locales/en.yml index 7ac41400b..eec7484d5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -204,6 +204,7 @@ en-GB: not_a_number: "Company number must be a number, like 12345678" name: blank: "Enter the name of your organisation" + too_long: "Organisation name must be 255 characters or fewer" line1: blank: "Enter the first line of your organisation's address" townCity: diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb new file mode 100644 index 000000000..5a741cda0 --- /dev/null +++ b/spec/models/organisation_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe Organisation, type: :model do + let(:valid_organisation_1) { Organisation.new(name: 'A' * 255) } + let(:valid_organisation_2) { Organisation.new(name: 'A' * 100) } + let(:invalid_organisation) { Organisation.new(name: 'A' * 256) } + + it 'validates length of name to be less than or equal to 255 characters' do + expect(invalid_organisation.valid?).to be(false) + expect(invalid_organisation.errors[:name]).to include("Organisation name must be 255 characters or fewer") + end + + it 'is valid when organisation name is equal to or below 255 characters' do + expect(valid_organisation_1.valid?).to be(true) + expect(valid_organisation_2.valid?).to be(true) + end +end \ No newline at end of file From 0728d2c4c709a161bd141020de3f247852f67fe4 Mon Sep 17 00:00:00 2001 From: Jack <91466216+JJD1990@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:10:14 +0100 Subject: [PATCH 05/28] Fix/unticked mission (#1136) * conditionals added to update action in mission_controller which sets mission to empty string if no mission chosen for bugfix * created ensure_mission_params method in organisation.rb, added not to explain method * mission_controller_spec added both indirectly and directly testing the ensure_mission_params method --- .../organisation/mission_controller.rb | 18 ++++++ .../organisation/mission_controller_spec.rb | 62 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/app/controllers/organisation/mission_controller.rb b/app/controllers/organisation/mission_controller.rb index d6e4b30e0..8dd83437b 100644 --- a/app/controllers/organisation/mission_controller.rb +++ b/app/controllers/organisation/mission_controller.rb @@ -10,6 +10,8 @@ def update logger.info "Updating mission for organisation ID: #{@organisation.id}" + ensure_mission_params + @organisation.validate_mission = true @organisation.update(organisation_params) @@ -40,4 +42,20 @@ def organisation_params end + # This method ensures that if no mission is chosen by the user + # the mission array is set back to empty. + def ensure_mission_params + + if params[:organisation] + + params[:organisation][:mission] ||= [] + + else + + params[:organisation] = { mission: [] } + + end + + end + end diff --git a/spec/controllers/organisation/mission_controller_spec.rb b/spec/controllers/organisation/mission_controller_spec.rb index d9cd985a1..7c6775e6d 100644 --- a/spec/controllers/organisation/mission_controller_spec.rb +++ b/spec/controllers/organisation/mission_controller_spec.rb @@ -110,6 +110,68 @@ end + it "should successfully update if no mission params are passed" do + + put :update, params: { + organisation_id: subject.current_user.organisations.first.id, + organisation: { + mission: [] + } + } + + expect(response).to have_http_status(:redirect) + expect(response).to redirect_to(:organisation_summary) + + expect(assigns(:organisation).errors.empty?).to eq(true) + expect(assigns(:organisation) + .mission).to eq([]) + + end + + end + + # These tests specifically test the ensure_mission_params method + describe '#ensure_mission_params' do + + before do + controller.class.send(:public, :ensure_mission_params) # This makes the method available for testing as it is a private method + end + + context 'when :organisation is present' do + context 'when :mission is already set' do + it 'does not change the mission' do + params = { + organisation: { + mission: ["female_led"], + } + } + allow(controller).to receive(:params).and_return(params) + + controller.ensure_mission_params + + expect(params[:organisation][:mission]).to eq(['female_led']) + + end + + end + + end + + context 'when :mission is not set' do + it 'sets mission to an empty array' do + params = { + organisation: {} + } + allow(controller).to receive(:params).and_return(params) + + controller.ensure_mission_params + + expect(params[:organisation][:mission]).to eq([]) + + end + + end + end end From 9561121f7dc281b5e37d141809cc192ff26155c9 Mon Sep 17 00:00:00 2001 From: Eithel Anderson <48526057+etelish@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:44:11 +0100 Subject: [PATCH 06/28] added project title conditional to get main contact apps method (#1137) * added project title conditional to get main contact apps method * added in a method to migrate and move application medium over 100k --- app/helpers/admin_portal_helper.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/helpers/admin_portal_helper.rb b/app/helpers/admin_portal_helper.rb index fb49f2a36..11800eda7 100644 --- a/app/helpers/admin_portal_helper.rb +++ b/app/helpers/admin_portal_helper.rb @@ -43,6 +43,7 @@ module AdminPortalHelper PEF = 4 EOI = 5 UNKNOWN = 6 + MIGRATED_MEDIUM_OVER_100k = 7 # Creates an array of hashes for the applications and # pre-applications belonging to a main applicant. @@ -74,6 +75,16 @@ def get_main_contact_apps(org_id, user_id) type = MEDIUM end + if fa.migrated_medium_over_100k? + type = MIGRATED_MEDIUM_OVER_100k + + salesforce_api_client= SalesforceApiClient.new + + title = salesforce_api_client + .get_project_title(fa.salesforce_case_id) + .Project_Title__c + end + if fa.project.present? title = fa.project.project_title type = SMALL @@ -163,6 +174,8 @@ def move_app_to_new_user(chosen_app_hash, new_contact_id, new_org_id) move_3_to_10k(chosen_app_hash, new_contact_id, new_org_id) when MEDIUM move_10_to_250k(chosen_app_hash, new_contact_id, new_org_id) + when MIGRATED_MEDIUM_OVER_100k + move_migrated_medium_over_100k(chosen_app_hash, new_org_id) when LARGE move_large(chosen_app_hash, new_org_id) when PEF @@ -354,6 +367,16 @@ def move_large(chosen_app_hash, new_org_id) end + # uses exactly the same as move_large for migrating medium over 100k + # @param [Hash] chosen_app_hash App that we are moving: example + # {:id=>"", :ref_no=>"", :type=>1, :title=>"", salesforce_id => ""} + # @param [String] new_org_id FFE GUID for new organisation + def move_migrated_medium_over_100k(chosen_app_hash, new_org_id) + + move_large(chosen_app_hash, new_org_id) + + end + # Moves a pre_application to a new user # Amends pre_applications rows # Writes audit row of changes From 485fe9730fc6af5351d78965ea25de38f4eec7e8 Mon Sep 17 00:00:00 2001 From: Paul Trelease Date: Thu, 10 Aug 2023 15:00:09 +0100 Subject: [PATCH 07/28] Enabled session timeout (#1138) --- app/models/user.rb | 3 ++- config/initializers/devise.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 698034762..7aca2fb87 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,7 +8,8 @@ class User < ApplicationRecord :recoverable, :rememberable, :validatable, - :confirmable + :confirmable, + :timeoutable enum role: [:user, :admin] after_initialize :set_default_role, :if => :new_record? diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 54103ffa1..7bcbb6883 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -175,8 +175,8 @@ # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this - # time the user will be asked for credentials again. Default is 30 minutes. - # config.timeout_in = 30.minutes + # time the user will be asked for credentials again. + config.timeout_in = 60.minutes # ==> Configuration for :lockable # Defines which strategy will be used to lock an account. From add0a9c80fff568fcdab2d74b1389a06b80932f9 Mon Sep 17 00:00:00 2001 From: Paul Trelease Date: Fri, 11 Aug 2023 11:42:41 +0100 Subject: [PATCH 08/28] Feature/amend longer session (#1140) * increased timeout criteria to 20 hours --- config/initializers/devise.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 7bcbb6883..87f400fa0 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -176,7 +176,9 @@ # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this # time the user will be asked for credentials again. - config.timeout_in = 60.minutes + # Gone for WCAG 20 hour exception to meet level A criteria. + # https://www.w3.org/WAI/WCAG21/Understanding/timing-adjustable.html + config.timeout_in = 20.hours # ==> Configuration for :lockable # Defines which strategy will be used to lock an account. From 27f53a02ca6bb277d149bef868073aef7051a50c Mon Sep 17 00:00:00 2001 From: Eithel Anderson <48526057+etelish@users.noreply.github.com> Date: Fri, 11 Aug 2023 12:28:35 +0100 Subject: [PATCH 09/28] replaced email input field with label and override update_resource method to prevent users email being updated (#1139) --- app/controllers/user/registrations_controller.rb | 9 +++++++++ app/views/user/registrations/edit.html.erb | 7 +++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/controllers/user/registrations_controller.rb b/app/controllers/user/registrations_controller.rb index 96ed1650f..a6cd284e9 100644 --- a/app/controllers/user/registrations_controller.rb +++ b/app/controllers/user/registrations_controller.rb @@ -33,5 +33,14 @@ def create_person(resource) NotifyMailer.confirmation_instructions_copy(resource).deliver_later end + + # Override the Devise::RegistrationsController update_resource method + # Ensures the email is not provided as a param to prevent it being updated + def update_resource(resource, params) + + params.delete(:email) + + super + end end diff --git a/app/views/user/registrations/edit.html.erb b/app/views/user/registrations/edit.html.erb index cfa83dd9a..1b67bdf41 100644 --- a/app/views/user/registrations/edit.html.erb +++ b/app/views/user/registrations/edit.html.erb @@ -45,10 +45,9 @@ <%= - f.text_field :email, - autofocus: true, - autocomplete: "email", - class: "govuk-input govuk-input--width-20" + f.label :email, + @user.email, + class: "govuk-label govuk-!-margin-top-2 govuk-!-font-weight-bold" %> From 5734e941dcbe59212b1a7809a35e8f36edfb6bf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:52:07 +0100 Subject: [PATCH 10/28] Bump semver from 5.7.1 to 5.7.2 (#1120) Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index ba5efb06b..86fa6394e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7612,19 +7612,19 @@ selfsigned@^1.10.8: node-forge "^0.10.0" "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.2, semver@^7.3.5: - version "7.5.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" - integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" From a244c9b47db07697c32533676bc8b279fcd38e9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:07:28 +0100 Subject: [PATCH 11/28] Bump puma from 4.3.12 to 5.6.7 (#1141) Bumps [puma](https://github.com/puma/puma) from 4.3.12 to 5.6.7. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v4.3.12...v5.6.7) --- updated-dependencies: - dependency-name: puma dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 190c3f2e3..e3336b943 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'lograge', '~> 0.11.2' gem 'mail-notify', '~> 1.1.0 ' gem 'nilify_blanks', '~> 1.3' gem 'pg', '~> 1.1' -gem 'puma', '~> 4.3' +gem 'puma', '~> 5.6' gem "rails", "~> 7.0.0" gem 'rails-i18n', '~> 7.0.5' gem 'redis', '~> 4.1.3' diff --git a/Gemfile.lock b/Gemfile.lock index 0c71c1029..5f82a41c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -257,7 +257,7 @@ GEM method_source (~> 1.0) psych (3.3.4) public_suffix (5.0.1) - puma (4.3.12) + puma (5.6.7) nio4r (~> 2.0) racc (1.7.1) rack (2.2.7) @@ -473,7 +473,7 @@ DEPENDENCIES pg (~> 1.1) pry (~> 0.14.1) psych (< 4) - puma (~> 4.3) + puma (~> 5.6) rails (~> 7.0.0) rails-controller-testing (~> 1.0.4) rails-i18n (~> 7.0.5) From 42fda8232bb04a737a3ca39f60cbde8bae4055fa Mon Sep 17 00:00:00 2001 From: Jack <91466216+JJD1990@users.noreply.github.com> Date: Thu, 24 Aug 2023 09:39:37 +0100 Subject: [PATCH 12/28] Fix/cc no not sure (#1142) * method created in the project model to format the cash contribution secured value * dash taken away from not sure in format secured for salesforce method, dasherize is needed for other values to work * added cc build and further expect to spec for x_not_sure fix * context added to spec to test project with no cc, better formatted method description * formatting comments and line at eof --- app/models/project.rb | 23 ++- spec/models/project_spec.rb | 318 ++++++++++++++++++++---------------- 2 files changed, 195 insertions(+), 146 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index ce0f20a1d..28c2b24a3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -417,7 +417,7 @@ def to_salesforce_json json.cashContributions self.cash_contributions do |cash_contribution| json.description cash_contribution.description json.amount cash_contribution.amount - json.secured cash_contribution.secured&.dasherize + json.secured format_secured_for_salesforce(cash_contribution) json.id cash_contribution.id end json.set!('organisationSalesforceAccountId', @@ -470,4 +470,23 @@ def get_organisation_type_for_salesforce_json end -end + # Formats the secured value of a cash contribution for Salesforce. + # + # Given a cash contribution with a particular secured value, this method + # will either return the "not sure" string or a dasherized version of the + # value, depending on the original value. + # + # @param [object] cash contribution object + # @param [string] :secured for the answer/value of the 'is your cash + # contribution secured?' question + # + # @return [string] - A formatted string value. Either 'not sure' or + # a dasherized value + def format_secured_for_salesforce(cash_contribution) + if cash_contribution.secured == 'x_not_sure' + 'not sure' + else + cash_contribution.secured&.dasherize + end + end +end \ No newline at end of file diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2107d6c14..cb338d65c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1,40 +1,38 @@ require "rails_helper" RSpec.describe Project, type: :model do - describe "Project model" do - it "should serialise Salesforce JSON successfully" do - - @project = build( - :project, - id: "2c660111-ab15-4221-98e0-cf0e02748a9b", - project_title: "Test Project", start_date: "1/1/2025", - end_date: "1/10/2025", line1: "10 Downing Street", - line2: "Westminster", townCity: "London", county: "LONDON", - postcode: "SW1A 2AA", description: "A description of my project...", - difference: "The difference my project will make to...", - matter: "My project matters because...", - best_placed_description: "My organisation is best placed to...", - heritage_description: "The heritage of my project...", - involvement_description: "My project will involve a wider range of " \ - "people...", - outcome_2: true, outcome_3: false, outcome_4: true, outcome_5: false, - outcome_6: true, outcome_7: false, outcome_8: true, outcome_9: false, - outcome_2_description: "Description of outcome 2", - outcome_3_description: "", - outcome_4_description: "Description of outcome 4", - outcome_5_description: "", - outcome_6_description: "Description of outcome 6", - outcome_7_description: "", - outcome_8_description: "Description of outcome 8", - outcome_9_description: "", permission_type: 2, - permission_description: "permission description", - partnership_details: "partnership details", - declaration_reasons_description: "something" - ) + before do + @project = build( + :project, + id: "2c660111-ab15-4221-98e0-cf0e02748a9b", + project_title: "Test Project", start_date: "1/1/2025", + end_date: "1/10/2025", line1: "10 Downing Street", + line2: "Westminster", townCity: "London", county: "LONDON", + postcode: "SW1A 2AA", description: "A description of my project...", + difference: "The difference my project will make to...", + matter: "My project matters because...", + best_placed_description: "My organisation is best placed to...", + heritage_description: "The heritage of my project...", + involvement_description: "My project will involve a wider range of " \ + "people...", + outcome_2: true, outcome_3: false, outcome_4: true, outcome_5: false, + outcome_6: true, outcome_7: false, outcome_8: true, outcome_9: false, + outcome_2_description: "Description of outcome 2", + outcome_3_description: "", + outcome_4_description: "Description of outcome 4", + outcome_5_description: "", + outcome_6_description: "Description of outcome 6", + outcome_7_description: "", + outcome_8_description: "Description of outcome 8", + outcome_9_description: "", permission_type: 2, + permission_description: "permission description", + partnership_details: "partnership details", + declaration_reasons_description: "something" + ) - organisation = build( + organisation = build( :organisation, name: "Test Organisation", org_type: 5, @@ -50,120 +48,152 @@ @project.user.organisations.append(organisation) - project_salesforce_json = JSON.parse(@project.to_salesforce_json) - - # Assert metadata parameters - expect(project_salesforce_json['meta']['applicationId']) - .to eq("2c660111-ab15-4221-98e0-cf0e02748a9b") - expect(project_salesforce_json['meta']['username']) - .to eq(@project.user.email) - - # Assert main contact parameters - expect(project_salesforce_json['application']['mainContactName']) - .to eq("Joe Bloggs") - expect(project_salesforce_json['application']['mainContactDateOfBirth']) - .to eq("1980-01-01") - expect(project_salesforce_json['application']['mainContactPhone']) - .to eq("07123456789") - expect(project_salesforce_json['application']['mainContactEmail']) - .to eq(@project.user.email) - expect(project_salesforce_json['application']['mainContactAddress']['line1']) - .to eq("10 Downing Street, Westminster") - expect(project_salesforce_json['application']['mainContactAddress']['townCity']) - .to eq("London") - expect(project_salesforce_json['application']['mainContactAddress']['county']) - .to eq("LONDON") - expect(project_salesforce_json['application']['mainContactAddress']['postcode']) - .to eq("SW1A 2AA") - - # Assert organisation parameters - expect(project_salesforce_json['application']['organisationName']) - .to eq("Test Organisation") - expect(project_salesforce_json['application']['organisationType']) - .to eq("faith-based-or-church-organisation") - expect(project_salesforce_json['application']['organisationMission']) - .to eq(%w(young-people-led disability-led)) - expect(project_salesforce_json['application']['charityNumber']) - .to eq("12345") - expect(project_salesforce_json['application']['companyNumber']) - .to eq("54321") - expect(project_salesforce_json['application']['organisationAddress']['line1']) - .to eq("10 Downing Street, Westminster") - expect(project_salesforce_json['application']['organisationAddress']['townCity']) - .to eq("London") - expect(project_salesforce_json['application']['organisationAddress']['county']) - .to eq("LONDON") - expect(project_salesforce_json['application']['organisationAddress']['postcode']) - .to eq("SW1A 2AA") - - # Assert project parameters - expect(project_salesforce_json['application']['projectName']) - .to eq("Test Project") - expect(project_salesforce_json['application']['projectDateRange']['startDate']) - .to eq("2025-01-01") - expect(project_salesforce_json['application']['projectDateRange']['endDate']) - .to eq("2025-10-01") - expect(project_salesforce_json['application']['projectAddress']['line1']) - .to eq("10 Downing Street, Westminster") - expect(project_salesforce_json['application']['projectAddress']['townCity']) - .to eq("London") - expect(project_salesforce_json['application']['projectAddress']['county']) - .to eq("LONDON") - expect(project_salesforce_json['application']['projectAddress']['projectPostcode']) - .to eq("SW1A 2AA") - expect(project_salesforce_json['application']['yourIdeaProject']) - .to eq("A description of my project...") - expect(project_salesforce_json['application']['projectDifference']) - .to eq("The difference my project will make to...") - expect(project_salesforce_json['application']['projectOrgBestPlace']) - .to eq("My organisation is best placed to...") - expect(project_salesforce_json['application']['projectAvailable']) - .to eq("The heritage of my project...") - expect(project_salesforce_json['application']['projectOutcome1']) - .to eq("My project will involve a wider range of people...") - expect(project_salesforce_json['application']['projectOutcome2']) - .to eq("Description of outcome 2") - expect(project_salesforce_json['application']['projectOutcome3']) - .to eq("") - expect(project_salesforce_json['application']['projectOutcome4']) - .to eq("Description of outcome 4") - expect(project_salesforce_json['application']['projectOutcome5']) - .to eq("") - expect(project_salesforce_json['application']['projectOutcome6']) - .to eq("Description of outcome 6") - expect(project_salesforce_json['application']['projectOutcome7']) - .to eq("") - expect(project_salesforce_json['application']['projectOutcome8']) - .to eq("Description of outcome 8") - expect(project_salesforce_json['application']['projectOutcome9']) - .to eq("") - expect(project_salesforce_json['application']['projectOutcome2Checked']) - .to eq(true) - expect(project_salesforce_json['application']['projectOutcome3Checked']) - .to eq(false) - expect(project_salesforce_json['application']['projectOutcome4Checked']) - .to eq(true) - expect(project_salesforce_json['application']['projectOutcome5Checked']) - .to eq(false) - expect(project_salesforce_json['application']['projectOutcome6Checked']) - .to eq(true) - expect(project_salesforce_json['application']['projectOutcome7Checked']) - .to eq(false) - expect(project_salesforce_json['application']['projectOutcome8Checked']) - .to eq(true) - expect(project_salesforce_json['application']['projectOutcome9Checked']) - .to eq(false) - expect(project_salesforce_json['application']['projectNeedsPermission']) - .to eq("not-sure") - expect(project_salesforce_json['application']['projectNeedsPermissionDetails']) - .to eq("permission description") - expect(project_salesforce_json['application']['partnershipDetails']) - .to eq("partnership details") - expect(project_salesforce_json['application']['informationNotPubliclyAvailableRequest']) - .to eq("something") + end + + context "with a cash contribution" do + + before do + @cash_contribution = build( + :cash_contribution, + description: "Test Contribution", + amount: 1000, + secured: 'x_not_sure' + ) + + @project.cash_contributions << @cash_contribution + + @project_salesforce_json = JSON.parse(@project.to_salesforce_json) + + end + it "should serialize Salesforce JSON successfully" do + # Assert metadata parameters + expect(@project_salesforce_json['meta']['applicationId']) + .to eq("2c660111-ab15-4221-98e0-cf0e02748a9b") + expect(@project_salesforce_json['meta']['username']) + .to eq(@project.user.email) + + # Assert main contact parameters + expect(@project_salesforce_json['application']['mainContactName']) + .to eq("Joe Bloggs") + expect(@project_salesforce_json['application']['mainContactDateOfBirth']) + .to eq("1980-01-01") + expect(@project_salesforce_json['application']['mainContactPhone']) + .to eq("07123456789") + expect(@project_salesforce_json['application']['mainContactEmail']) + .to eq(@project.user.email) + expect(@project_salesforce_json['application']['mainContactAddress']['line1']) + .to eq("10 Downing Street, Westminster") + expect(@project_salesforce_json['application']['mainContactAddress']['townCity']) + .to eq("London") + expect(@project_salesforce_json['application']['mainContactAddress']['county']) + .to eq("LONDON") + expect(@project_salesforce_json['application']['mainContactAddress']['postcode']) + .to eq("SW1A 2AA") + + # Assert organisation parameters + expect(@project_salesforce_json['application']['organisationName']) + .to eq("Test Organisation") + expect(@project_salesforce_json['application']['organisationType']) + .to eq("faith-based-or-church-organisation") + expect(@project_salesforce_json['application']['organisationMission']) + .to eq(%w(young-people-led disability-led)) + expect(@project_salesforce_json['application']['charityNumber']) + .to eq("12345") + expect(@project_salesforce_json['application']['companyNumber']) + .to eq("54321") + expect(@project_salesforce_json['application']['organisationAddress']['line1']) + .to eq("10 Downing Street, Westminster") + expect(@project_salesforce_json['application']['organisationAddress']['townCity']) + .to eq("London") + expect(@project_salesforce_json['application']['organisationAddress']['county']) + .to eq("LONDON") + expect(@project_salesforce_json['application']['organisationAddress']['postcode']) + .to eq("SW1A 2AA") + + # Assert project parameters + expect(@project_salesforce_json['application']['projectName']) + .to eq("Test Project") + expect(@project_salesforce_json['application']['projectDateRange']['startDate']) + .to eq("2025-01-01") + expect(@project_salesforce_json['application']['projectDateRange']['endDate']) + .to eq("2025-10-01") + expect(@project_salesforce_json['application']['projectAddress']['line1']) + .to eq("10 Downing Street, Westminster") + expect(@project_salesforce_json['application']['projectAddress']['townCity']) + .to eq("London") + expect(@project_salesforce_json['application']['projectAddress']['county']) + .to eq("LONDON") + expect(@project_salesforce_json['application']['projectAddress']['projectPostcode']) + .to eq("SW1A 2AA") + expect(@project_salesforce_json['application']['yourIdeaProject']) + .to eq("A description of my project...") + expect(@project_salesforce_json['application']['projectDifference']) + .to eq("The difference my project will make to...") + expect(@project_salesforce_json['application']['projectOrgBestPlace']) + .to eq("My organisation is best placed to...") + expect(@project_salesforce_json['application']['projectAvailable']) + .to eq("The heritage of my project...") + expect(@project_salesforce_json['application']['projectOutcome1']) + .to eq("My project will involve a wider range of people...") + expect(@project_salesforce_json['application']['projectOutcome2']) + .to eq("Description of outcome 2") + expect(@project_salesforce_json['application']['projectOutcome3']) + .to eq("") + expect(@project_salesforce_json['application']['projectOutcome4']) + .to eq("Description of outcome 4") + expect(@project_salesforce_json['application']['projectOutcome5']) + .to eq("") + expect(@project_salesforce_json['application']['projectOutcome6']) + .to eq("Description of outcome 6") + expect(@project_salesforce_json['application']['projectOutcome7']) + .to eq("") + expect(@project_salesforce_json['application']['projectOutcome8']) + .to eq("Description of outcome 8") + expect(@project_salesforce_json['application']['projectOutcome9']) + .to eq("") + expect(@project_salesforce_json['application']['projectOutcome2Checked']) + .to eq(true) + expect(@project_salesforce_json['application']['projectOutcome3Checked']) + .to eq(false) + expect(@project_salesforce_json['application']['projectOutcome4Checked']) + .to eq(true) + expect(@project_salesforce_json['application']['projectOutcome5Checked']) + .to eq(false) + expect(@project_salesforce_json['application']['projectOutcome6Checked']) + .to eq(true) + expect(@project_salesforce_json['application']['projectOutcome7Checked']) + .to eq(false) + expect(@project_salesforce_json['application']['projectOutcome8Checked']) + .to eq(true) + expect(@project_salesforce_json['application']['projectOutcome9Checked']) + .to eq(false) + expect(@project_salesforce_json['application']['projectNeedsPermission']) + .to eq("not-sure") + expect(@project_salesforce_json['application']['projectNeedsPermissionDetails']) + .to eq("permission description") + expect(@project_salesforce_json['application']['partnershipDetails']) + .to eq("partnership details") + expect(@project_salesforce_json['application']['informationNotPubliclyAvailableRequest']) + .to eq("something") + expect(@project_salesforce_json['application']['cashContributions'][0]['description']) + .to eq("Test Contribution") + expect(@project_salesforce_json['application']['cashContributions'][0]['amount']) + .to eq(1000) + expect(@project_salesforce_json['application']['cashContributions'][0]['secured']) + .to eq('not sure') + end end - end + context "without a cash contribution" do + before do + @project_salesforce_json = JSON.parse(@project.to_salesforce_json) + end -end + it "should serialise a project without cash contributions Salesforce JSON successfully" do + expect(@project_salesforce_json['application']['cashContributions']).to eq([]) + end + end + + end +end \ No newline at end of file From 6ef4cc86d5f97123d207510b1021a9831ddc0d41 Mon Sep 17 00:00:00 2001 From: Jack <91466216+JJD1990@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:51:48 +0100 Subject: [PATCH 13/28] inital regex alteration to enforce domains in email validation (#1146) * inital regex alteration to enforce domains in email validation * add tests for legal sig regex and registration * tests written for email validation, legal sig model and user model * comments added to explain regex in devise.rb and legalsig model * deleted end of file extra lines * misunderstanding, added EOF lines back in! * eof line to user_spec * took out debugging comments in user_spec --------- Co-authored-by: eithel --- app/models/legal_signatory.rb | 4 +- config/initializers/devise.rb | 7 +- .../second_signatory_controller_spec.rb | 26 ++++++ .../users/registrations_controller_spec.rb | 1 + spec/models/legal_signatory_spec.rb | 86 +++++++++++++++++++ spec/models/user_spec.rb | 25 +++++- 6 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 spec/models/legal_signatory_spec.rb diff --git a/app/models/legal_signatory.rb b/app/models/legal_signatory.rb index 663808aab..b2b368574 100644 --- a/app/models/legal_signatory.rb +++ b/app/models/legal_signatory.rb @@ -14,8 +14,10 @@ class LegalSignatory < ApplicationRecord validates :name, length: { minimum: 1, maximum: 80 } + # the custom regex below ensures that a domain + # is present and also allows tags. validates :email_address, - format: { with: URI::MailTo::EMAIL_REGEXP } + format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i } def validate_role? validate_role == true diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 87f400fa0..62660d6a6 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -171,7 +171,12 @@ # Email regex used to validate email formats. It simply asserts that # one (and only one) @ exists in the given string. This is mainly # to give user feedback and not to assert the e-mail validity. - config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + # original email_regexp /\A[^@\s]+@[^@\s]+\z/ + + # a custom regex has now been added below, this ensures that a domain + # is present and also allows tags. + config.email_regexp =/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this diff --git a/spec/controllers/funding_application/legal_agreements/second_signatory_controller_spec.rb b/spec/controllers/funding_application/legal_agreements/second_signatory_controller_spec.rb index 840ab04ad..c4bbb46ef 100644 --- a/spec/controllers/funding_application/legal_agreements/second_signatory_controller_spec.rb +++ b/spec/controllers/funding_application/legal_agreements/second_signatory_controller_spec.rb @@ -157,6 +157,32 @@ end + it "should raise email error based on invalid email validation " \ + "when email without a domain is passed" do + + put :update, + params: { + application_id: @funding_application.id, + legal_signatory:{ + name: "John Smith", + email_address: "john@smith", + role: "Trustee" + } + } + + expect(response).to have_http_status(:success) + expect(response).to render_template(:show) + + expect(assigns(:funding_application).errors.empty?).to eq(false) + + expect(assigns(:funding_application).errors.count) + .to eq(1) + + expect(assigns(:funding_application).errors[:"legal_signatories.email_address"][0]) + .to eq("Enter a valid email address") + + end + it "should raise email error based matching email address of " \ "legal signatory 1 and legal signatory 2" do diff --git a/spec/controllers/users/registrations_controller_spec.rb b/spec/controllers/users/registrations_controller_spec.rb index 42751beed..9d2c42108 100644 --- a/spec/controllers/users/registrations_controller_spec.rb +++ b/spec/controllers/users/registrations_controller_spec.rb @@ -30,4 +30,5 @@ subject.create_person(resource) end end + end diff --git a/spec/models/legal_signatory_spec.rb b/spec/models/legal_signatory_spec.rb new file mode 100644 index 000000000..e02272938 --- /dev/null +++ b/spec/models/legal_signatory_spec.rb @@ -0,0 +1,86 @@ +require "rails_helper" + +RSpec.describe LegalSignatory, type: :model do + subject { build(:legal_signatory) } + + context "Validations" do + it "validates the length of role" do + subject.role = 'a' * 81 + expect(subject.valid?).to be_falsey + expect(subject.errors[:role]).to include("The role of the legal signatory must be fewer than 80 characters") + + subject.role = '' + expect(subject.valid?).to be_falsey + expect(subject.errors[:role]).to include("Enter the role of a legal signatory") + + subject.role = 'Valid Role' + expect(subject.valid?).to be_truthy + end + + it "validates the length of name" do + subject.name = 'a' * 81 + expect(subject.valid?).to be_falsey + expect(subject.errors[:name]).to include("The name of the legal signatory must be fewer than 80 characters") + + subject.name = '' + expect(subject.valid?).to be_falsey + expect(subject.errors[:name]).to include("Enter the name of a legal signatory") + + subject.name = 'Valid Name' + expect(subject.valid?).to be_truthy + end + end + + describe "Legal model" do + + let (:resource) { + create( + :legal_signatory, + id: 1, + email_address: 'a@f.com', + role: 'role' + ) + } + + context "when email is invalid" do + let(:invalid_emails) { ['invalid', 'invalid@', 'invalid@.com', '@invalid.com', 'invalid@invalid'] } + + it "should be invalid" do + invalid_emails.each do |email| + resource.email_address = email + expect(resource.valid?).to eq(false) + end + end + end + + context "when email is valid" do + let(:valid_emails) { ['valid@example.com', 'valid.name@example.com', 'valid.name+tag@example.co.uk', 'valid-name@example.co.uk'] } + + it "should be valid" do + valid_emails.each do |email| + resource.email_address = email + unless resource.valid? + puts "Validation failed for email #{email}" + puts resource.errors.full_messages + end + expect(resource.valid?).to eq(true) + end + end + end + + describe "Conditionally validating email_address" do + it "should validate email_address when validate_email_address is set to true" do + subject.validate_email_address = true + expect(subject.validate_email_address?).to eq(true) + end + end + + describe "Conditionally validating phone number" do + it "should validate phone number when validate_phone_number is set to true" do + subject.validate_phone_number = true + expect(subject.validate_phone_number?).to eq(true) + end + end + + end +end \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 002e853f8..44d277813 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -92,9 +92,30 @@ expect(resource.send_english_mails?).to eq(true) expect(resource.send_welsh_mails?).to eq(false) expect(resource.send_bilingual_mails?).to eq(false) + end - end - end + context "when email is invalid" do + let(:invalid_emails) { ['invalid', 'invalid@', 'invalid@.com', '@invalid.com', 'invalid@invalid'] } + + it "should be invalid" do + invalid_emails.each do |email| + resource.email = email + expect(resource.valid?).to eq(false) + end + end + end + context "when email is valid" do + let(:valid_emails) { ['valid@example.com', 'valid.name@example.com', 'valid.name+tag@example.co.uk', 'valid-name@example.co.uk'] } + + it "should be valid" do + valid_emails.each do |email| + resource.email = email + expect(resource.valid?).to eq(true) + end + end + end + + end end From 82020dcd2e68034673b28d31ee707ab88da77b1a Mon Sep 17 00:00:00 2001 From: Jack <91466216+JJD1990@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:31:47 +0100 Subject: [PATCH 14/28] Feature/create ogranisation model spec (#1148) * feat/create org spec * context for empty address fields with error tests added * add org type tests * rearranged tests in oragnisation spec, added invalid org type test * taken contexts out, used factory organisations * test org mission validation * refactored spec, configured org factory to allow test suite to run properly, added further organisation tests * all organisation tests added to file, need to refactor further * refactored organisation factory and references * organisation_spec refactored * association tests added to organisation spec * comment added about shoulda gem * added EOF lines --------- Co-authored-by: eithel --- spec/factories/organisations.rb | 68 ++++- spec/models/organisation_spec.rb | 426 ++++++++++++++++++++++++++++++- 2 files changed, 481 insertions(+), 13 deletions(-) diff --git a/spec/factories/organisations.rb b/spec/factories/organisations.rb index 6e52c882b..e9a3722d4 100644 --- a/spec/factories/organisations.rb +++ b/spec/factories/organisations.rb @@ -1,7 +1,69 @@ FactoryBot.define do - factory :organisation do |f| - + # This blank :organisation is used throughout the test suite + # best not to change it without knowing where its used. + factory :organisation do end -end + # Everything below and including this organisation model is + # used within the organisation_spec.rb. + trait :organisation_model do + id { SecureRandom.uuid } + created_at { Time.current } + updated_at { Time.current } + line1 { "123 Main Street" } + line2 { "Flat 3" } + line3 { "Third Floor" } + townCity { "Plymouth" } + county { "Devon" } + postcode { "PL1 3TT" } + org_type { 0 } + company_number { "COMP12345" } + charity_number { "CHAR12345" } + charity_number_ni { 7890 } + mission { ["black_or_minority_ethnic_led"] } + salesforce_account_id { "sf-123456789" } + custom_org_type { "CustomType" } + main_purpose_and_activities { "Main purpose and activities text" } + spend_in_last_financial_year { 1000.00 } + unrestricted_funds { 500.00 } + board_members_or_trustees { 5 } + vat_registered { true } + vat_number { "GB123456789" } + social_media_info { "Follow us on Twitter @test_org" } + end + + # A trait to allow testing of blank attributes + #that must be present. + trait :blank_organisation do + after(:build) do |org| + org.validate_name = true + org.validate_address = true + org.validate_org_type = true + end + + org_type { nil } + custom_org_type { nil } + name { nil } + line1 { nil } + townCity { nil } + county { nil } + postcode { nil } + main_purpose_and_activities { nil } + end + + trait :valid_organisation do + name { 'A' * 255 } + end + + trait :invalid_organisation do + name { 'A' * 256 } + end + + trait :invalid_mission do + mission { ["invalid_value1", "invalid_value2", "black_or_minority_ethnic_led" ] } + validate_mission {true} + end + + end + \ No newline at end of file diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index 5a741cda0..f386fc110 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -1,17 +1,423 @@ require 'rails_helper' RSpec.describe Organisation, type: :model do - let(:valid_organisation_1) { Organisation.new(name: 'A' * 255) } - let(:valid_organisation_2) { Organisation.new(name: 'A' * 100) } - let(:invalid_organisation) { Organisation.new(name: 'A' * 256) } + subject {build(:organisation)} + let(:valid_organisation) { build(:organisation, :organisation_model, :valid_organisation) } + let(:invalid_mission_organisation) { build(:organisation, :organisation_model, :invalid_organisation, :invalid_mission) } + let(:blank_organisation) { build(:organisation, :organisation_model, :blank_organisation) } + let(:not_vat_registered_org) { build(:organisation, :organisation_model, vat_registered: false, validate_vat_registered: true) } + let(:invalid_vat_registered_org) { build(:organisation, :organisation_model, vat_registered: nil, validate_vat_registered: true) } + let(:custom_org_type_blank) { build(:organisation, :organisation_model, custom_org_type: nil, validate_custom_org_type: true) } - it 'validates length of name to be less than or equal to 255 characters' do - expect(invalid_organisation.valid?).to be(false) - expect(invalid_organisation.errors[:name]).to include("Organisation name must be 255 characters or fewer") + # Set the state of the organisations to ensure any error + # messages are there to be seen in the tests. + before do + blank_organisation.valid? end - it 'is valid when organisation name is equal to or below 255 characters' do - expect(valid_organisation_1.valid?).to be(true) - expect(valid_organisation_2.valid?).to be(true) + # create a hash of attributes/fields that should have presence + # of errors. + describe "Validation of mandatory fields" do + fields_with_presence_errors = { + name: 'Enter the name of your organisation', + line1: "Enter the first line of your organisation's address", + townCity: "Enter the town or city where your organisation is located", + county: "Enter the county where your organisation is located", + postcode: "Enter the postcode where your organisation is located", + org_type: "Select the type of organisation that will be running your project" + } + + # Loop through each field to check they have an error + # and that the error matches what it should be. + fields_with_presence_errors.each do |field, message| + it "is invalid without a #{field}" do + blank_organisation[field] = nil + expect(blank_organisation.valid?).to be(false) + expect(blank_organisation.errors[field]).to include(message) + end + end + end + + # create a hash of attributes/fields that should have length limits + # with their error message. + describe "Validation of length for relevant fields" do + length_fields = { + name: [255, "Organisation name must be 255 characters or fewer"], + company_number: [20, "Company number must be 20 characters or fewer"], + charity_number: [20, "Charity number must be 20 characters or fewer. For example 1234567 in England and Wales, SC000123 in Scotland, or 10000-0 in Northern Ireland"], + vat_number: [[9, 12], "Enter the VAT number of your organisation in the correct format"] + } + + # Loop through each field to check they have an error + # and that the error matches what it should be. + length_fields.each do |field, details| + max_length, message = details + + it "validates length of #{field} to be within valid constraints" do + expect(valid_organisation.valid?).to be(true) + + if max_length.is_a?(Array) + # For VAT number, we have a range. + min_len, max_len = max_length + too_long = build(:organisation, field => 'A' * (max_len + 1)) + too_short = build(:organisation, field => 'A' * (min_len - 1)) + + if field == :vat_number + too_long.validate_vat_number = true + too_short.validate_vat_number = true + end + + expect(too_long.valid?).to be(false) + expect(too_short.valid?).to be(false) + expect(too_long.errors[field]).to include(message) + expect(too_short.errors[field]).to include(message) + + else + too_long = build(:organisation, field => 'A' * (max_length + 1)) + + if field == :company_number + too_long.validate_company_number = true + elsif field == :charity_number + too_long.validate_charity_number = true + end + + expect(too_long.valid?).to be(false) + expect(too_long.errors[field]).to include(message) + end + end + end + end + + # org_type tests + describe "validation or org_type" do + it 'has a valid org type' do + expect(valid_organisation.valid?).to be(true) + expect(blank_organisation.errors[:org_type]).to include("Select the type of organisation that will be running your project") + end + + it 'validates the presence of org_type when org_type is blank' do + expect(blank_organisation.valid?).to be(false) + expect(blank_organisation.errors[:org_type]).to include("Select the type of organisation that will be running your project") + end + + it 'validates the org_type with the correct enum' do + valid_org_type = build(:organisation, org_type: 3) + expect(valid_org_type.org_type).to eq("community_interest_company") + end + + it 'should allow organization types within the range 0 to 11' do + (0..11).each do |org_type| + valid_org = build(:organisation, org_type: org_type) + expect(valid_org.valid?).to be(true), "Expected organization type #{org_type} to be valid, but got errors: #{valid_org.errors[:org_type].join(', ')}" + end + end + + # We are testing an enum, so should recieve an ArgumentError. + it 'should raise an ArgumentError for invalid organization types' do + invalid_org_types = [-1, 12, 200, "invalid"] + invalid_org_types.each do |org_type| + expect { subject.org_type = org_type }.to raise_error(ArgumentError), "Expected an ArgumentError to be raised for org_type #{org_type.inspect}, but it wasn't." + end + end + end + + # testing custom_org_type + describe "Validation of custom_org_type" do + it 'passes validation if custom_org_type is present when validate_custom_org_type is true' do + expect(valid_organisation.valid?).to be(true) + end + + it 'fails validation if custom_org_type is blank when validate_custom_org_type is true' do + blank_organisation.validate_custom_org_type = true + expect(blank_organisation.valid?).to be(false) + expect(blank_organisation.errors[:custom_org_type]).to include("Specify your organisation type") + end + + it 'passes validation regardless of custom_org_type value when validate_custom_org_type is false' do + custom_org_type_blank.validate_custom_org_type = false + expect(custom_org_type_blank.valid?).to be(true) + end + end + + # mission tests - here we test the validate_mission_array method + describe "Validation of mission and mission_array" do + it 'validates the mission with the correct value ' do + expect(valid_organisation.mission).to eq(["black_or_minority_ethnic_led"]) + expect(invalid_mission_organisation.valid?).to be(false) + end + + it 'adds no error when mission contains only valid values' do + valid_organisation.mission = ["black_or_minority_ethnic_led", "female_led"] + valid_organisation.valid? + expect(valid_organisation.errors[:mission]).to be_empty + end + + it "adds an error when mission contains an invalid value" do + invalid_mission_organisation.valid? + expect(invalid_mission_organisation.errors[:mission]).to include("invalid_value1 is not a valid selection") + end + + it "adds multiple errors when mission contains multiple invalid values" do + invalid_mission_organisation = Organisation.new( + mission: ["invalid_value1", "invalid_value2"], + validate_mission: true + ) + invalid_mission_organisation.valid? + expect(invalid_mission_organisation.errors[:mission]).to include("invalid_value1 is not a valid selection", "invalid_value2 is not a valid selection") + end + + it 'adds no errors when mission is nil' do + expect(blank_organisation.errors[:mission]).to be_empty + end + + it 'adds no errors when mission is an empty array' do + blank_organisation.mission = [] + blank_organisation.valid? + expect(blank_organisation.errors[:mission]).to be_empty + end + end + + # More complex tests to assert the validate_length methods work + # via a loop + describe "Test the validate_length methods" do + [ + [:main_purpose_and_activities, 'activerecord.errors.models.organisation.attributes.main_purpose_and_activities.too_long'], + [:social_media_info, 'activerecord.errors.models.organisation.attributes.social_media_info.too_long'] + ].each do |attribute, translation_key| + it "validates the length of #{attribute}, must be 500 characters or fewer" do + subject.send("validate_#{attribute}=", true) + subject.send("#{attribute}=", "A " * 501) + subject.valid? + + expect(subject.errors[attribute]).to include( + I18n.t( + translation_key, + word_count: 500 + ) + ) + end + end + end + + # tests for board_members_or_trustees, main_purpose_and_activities + # spend_in_last_financial_year and unrestricted_funds + # Iterate through each set of test data for different attributes. + # Each set of test data consists of an attribute and an array of test cases. + describe "More complex validations for attributes" do + [ + { + attribute: :board_members_or_trustees, # The attribute to be tested + cases: [ + # Array of test cases, each containing a value to test and the expected error message. + { value: -1, error: "Enter an amount greater than -1" }, + { value: "Twenty One", error: "Number of board members or trustees must be a number" }, + { value: 2147483648, error: "Enter an amount less than 2147483648" }, + { value: nil, error: nil } + ] + }, + { + attribute: :main_purpose_and_activities, + cases: [ + { value: nil, error: "Enter your organisation's main purpose or activities" }, + { value: "Some Activities", error: nil } + ] + }, + { + attribute: :spend_in_last_financial_year, + cases: [ + { value: 0, error: "Enter an amount greater than 0" }, + { value: "Ninety Pound", error: "Must be a number, like 500" }, + { value: nil, error: nil }, + { value: 900000, error: nil } + ] + }, + { + attribute: :unrestricted_funds, + cases: [ + { value: 0, error: "Enter an amount greater than 0" }, + { value: "Ninety Thousand Pounds", error: "Level of unrestricted funds must be a number" }, + { value: nil, error: nil }, + { value: 900000, error: nil } + ] + } + ].each do |test_data| + attribute = test_data[:attribute] + cases = test_data[:cases] + + # Testing when the corresponding validate flag for the attribute is true + context "when validate_#{attribute} is true" do + before { subject.send("validate_#{attribute}=", true) } + + # Iterate through each case and apply the test + cases.each do |test_case| + it "handles value: #{test_case[:value]}" do + subject.send("#{attribute}=", test_case[:value]) + + # Validate the subject and compare with the expected outcome + expect(subject.valid?).to eq(test_case[:error].nil?) + + # Check for error messages if any are expected + if test_case[:error] + expect(subject.errors[attribute]).to include(test_case[:error]) + else + expect(subject.errors[attribute]).to be_empty + end + end + end + end + + describe "Conditional Validation of Attributes" do + # Testing when the corresponding validate flag for the attribute is false + context "when validate_#{attribute} is false" do + before { subject.send("validate_#{attribute}=", false) } + + cases.each do |test_case| + it "skips validation for value: #{test_case[:value]}" do + subject.send("#{attribute}=", test_case[:value]) + expect(subject.valid?).to be(true) + expect(subject.errors[attribute]).to be_empty + end + end + end + end + end + + end + + # Tests inclusion of vat_registered + describe "VAT Registered Validations" do + it 'fails validation if vat_registered is neither true or false when validate_vat_registered is true' do + expect(invalid_vat_registered_org.valid?).to be(false) + expect(invalid_vat_registered_org.errors[:vat_registered]).to include("Select an option to tell us whether your organisation is VAT registered") + end + + it 'passes validation if vat_registered is true when validate_vat_registered is true' do + expect(valid_organisation.valid?).to be(true) + end + + it 'passes validation if vat_registered is false when validate_vat_registered is true' do + expect(not_vat_registered_org.valid?).to be(true) + end + + it 'passes validation regardless of vat_registered value when validate_vat_registered is false' do + invalid_vat_registered_org.validate_vat_registered = false + expect(invalid_vat_registered_org.valid?).to be(true) + end + end + + # Tests that the validate_xyz? methods work + describe "Conditionally validating fields" do + fields_to_validate = [ + :name, + :org_type, + :custom_org_type, + :address, + :mission, + :main_purpose_and_activities, + :board_members_or_trustees, + :vat_registered, + :vat_number, + :company_number, + :charity_number, + :social_media_info, + :spend_in_last_financial_year, + :unrestricted_funds + ] + + fields_to_validate.each do |field| + it "should validate #{field} when validate_#{field} is set to true" do + subject.public_send("validate_#{field}=", true) + expect(subject.public_send("validate_#{field}?")).to eq(true) + end + end end -end \ No newline at end of file + + # Tests for Organisation associations + # We could use the 'shoulda' gem which tests associations + describe 'Associations' do + + it 'can exist without pre_applications' do + expect(valid_organisation.pre_applications).to be_empty + end + + it 'can have many pre_applications' do + pre_application1 = create(:pre_application, organisation: valid_organisation) + pre_application2 = create(:pre_application, organisation: valid_organisation) + + expect(valid_organisation.pre_applications).to include(pre_application1, pre_application2) + end + + it 'can exist without a funding_applications' do + expect(valid_organisation.funding_applications).to be_empty + end + + it 'can have many funding_applications' do + funding_application1 = create(:funding_application, organisation: valid_organisation) + funding_application2 = create(:funding_application, organisation: valid_organisation) + + expect(valid_organisation.funding_applications).to include(funding_application1, funding_application2) + end + + it 'can exist without organisations_org_types' do + expect(valid_organisation.organisations_org_types).to be_empty + end + + it 'can have many organisations_org_types' do + organisation = Organisation.create!() + + org_type_1 = OrgType.create!(id: SecureRandom.uuid, created_at: DateTime.now, updated_at: DateTime.now) + org_type_2 = OrgType.create!(id: SecureRandom.uuid, created_at: DateTime.now, updated_at: DateTime.now) + + organisations_org_type_1 = OrganisationsOrgType.create!(id: SecureRandom.uuid, organisation: organisation, org_type: org_type_1, created_at: DateTime.now, updated_at: DateTime.now) + organisations_org_type_2 = OrganisationsOrgType.create!(id: SecureRandom.uuid, organisation: organisation, org_type: org_type_2, created_at: DateTime.now, updated_at: DateTime.now) + + expect(organisation.organisations_org_types).to include(organisations_org_type_1, organisations_org_type_2) + end + + it 'can have many org_types through organisations_org_types' do + organisation = Organisation.create!() + + org_type1 = OrgType.create!() + org_type2 = OrgType.create!() + + OrganisationsOrgType.create!(organisation: organisation, org_type: org_type1) + OrganisationsOrgType.create!(organisation: organisation, org_type: org_type2) + + expect(organisation.org_types).to include(org_type1, org_type2) + end + + it 'can exist without org_types through organisations_org_types' do + expect(valid_organisation.org_types).to be_empty + end + + it 'can exist without users_organisations' do + expect(valid_organisation.users_organisations).to be_empty + end + + it 'can have many users_organisations' do + user1 = create(:user) + user2 = create(:user) + + user_org1 = create(:users_organisation, organisation: valid_organisation, user: user1) + user_org2 = create(:users_organisation, organisation: valid_organisation, user: user2) + + expect(valid_organisation.users_organisations).to include(user_org1, user_org2) + end + + + it 'can exist without users through users_organisations' do + expect(valid_organisation.users).to be_empty + end + + it 'can have many users through users_organisations' do + user1 = create(:user) + user2 = create(:user) + + create(:users_organisation, organisation: valid_organisation, user: user1) + create(:users_organisation, organisation: valid_organisation, user: user2) + + expect(valid_organisation.users).to include(user1, user2) + end + end + +end From 313be4efefec2f06c0904ffc7229a7842a450203 Mon Sep 17 00:00:00 2001 From: Vasili Kachalko Date: Tue, 26 Sep 2023 12:31:03 +0200 Subject: [PATCH 15/28] modify dockerfile to use phusion passenger image initial changes --- Dockerfile | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ dockerfile | 50 -------------------------------- 2 files changed, 84 insertions(+), 50 deletions(-) create mode 100644 Dockerfile delete mode 100644 dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..388470ce1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,84 @@ +# syntax=docker/dockerfile:1 + +FROM phusion/passenger-ruby31:latest + +ENV HOME /home/app/deploy + +CMD ["/sbin/my_init"] + +# Install common dependencies +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + --mount=type=tmpfs,target=/var/log \ + apt-get update -qq \ + && apt-get dist-upgrade -y \ + && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ + curl \ + gnupg2 \ + less \ + tzdata \ + time \ + locales \ + && update-locale LANG=C.UTF-8 LC_ALL=C.UTF-8 + +# Install NodeJS and Yarn +ARG NODE_MAJOR=16 +RUN yes | apt remove nodejs +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + --mount=type=tmpfs,target=/var/log \ + apt-get update && \ + apt-get install -y curl software-properties-common && \ + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ + echo "deb https://deb.nodesource.com/node_${NODE_MAJOR}.x $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/nodesource.list && \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends nodejs + +ARG YARN_VERSION=latest +RUN npm install -g yarn@$YARN_VERSION + +# Configure bundler +ENV RAILS_ENV=production \ + NODE_ENV=production \ + BUNDLE_JOBS=4 \ + BUNDLE_RETRY=3 \ + BUNDLE_APP_CONFIG=/home/app/.bundle \ + BUNDLE_PATH=/home/app/.bundle \ + GEM_HOME=/home/app/.bundle \ + PATH="$HOME/bin:${PATH}" \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +# Upgrade RubyGems and install the latest Bundler version +ARG BUNDLER_VERSION=2.3.11 +RUN echo "gem: --no-rdoc --no-ri >> \"$HOME/.gemrc\"" +RUN gem update --system && \ + gem install bundler:$BUNDLER_VERSION + +# Create a directory for the app code +RUN mkdir -p $HOME +WORKDIR $HOME + +# Install Ruby gems +COPY --chown=app:app Gemfile Gemfile.lock ./ +RUN bundle lock --add-platform aarch64-linux + +RUN mkdir $BUNDLE_PATH \ + && bundle config --local path "${BUNDLE_PATH}" \ + && bundle config --local without 'development test' \ + && bundle config --local clean 'true' \ + && bundle config --local no-cache 'true' \ + && bundle install --jobs=${BUNDLE_JOBS} \ + && rm -rf $BUNDLE_PATH/ruby/3.1.0/cache/* \ + && rm -rf $HOME/.bundle/cache/* + +# Install JS packages +COPY --chown=app:app package.json yarn.lock ./ +RUN yarn install --check-files + +RUN mkdir -p $HOME/tmp/pids + +COPY --chown=app:app . . + +# Precompile assets +RUN SECRET_KEY_BASE=dummyvalue bundle exec rake assets:precompile diff --git a/dockerfile b/dockerfile deleted file mode 100644 index 9b063bfa5..000000000 --- a/dockerfile +++ /dev/null @@ -1,50 +0,0 @@ -ARG RUBY_VERSION=3.1.1 - -FROM ruby:$RUBY_VERSION - -# Set Docker's working directory -WORKDIR /docker/app - -# Install fundamentals, git, npm, node and yarn -# This builds with a node v16.20.2 binary, so dev env needs to match that. (Github link https://github.com/nodesource/distributions ) -RUN apt-get update -qq && apt-get install -y build-essential apt-utils libpq-dev git \ - -y curl gnupg2 && \ - curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ - apt-get install -y nodejs && \ - npm install -g yarn - -# Copy these 2 files to working directory, check the -# dependencies and node versions sync, and generate node_modules. -COPY package.json ./ -COPY yarn.lock ./ -RUN yarn install --check-files - -# Install same bundler as developing with -RUN gem install bundler -v 2.3.11 - -# Copy generated lock to Docker's working directory and install -COPY Gemfile Gemfile.lock ./ -RUN bundle install - -ADD . /docker/app - -# The username must have a matching user on the database. -# The username must be set by a build argument -# Group will have same name as username. Home directory for user created. -ARG RAILS_RUNNING_USER -RUN useradd --user-group --system --create-home -u 1001 --no-log-init $RAILS_RUNNING_USER - -# New user owns /docker/app -RUN chown -R $RAILS_RUNNING_USER:$RAILS_RUNNING_USER /docker/app -# /docker/app can be read, written and executed from by new user -RUN chmod 700 /docker/app - -# Switch to user created above -USER $RAILS_RUNNING_USER - -# Exposes port for other containers and docker desktop -EXPOSE 3000 - -# run rails server -b 0.0.0.0 -p 3000 -ENTRYPOINT sh ./web-entrypoint.sh - From bc375a11e08dce937e07f14b0b3db2f9a455f895 Mon Sep 17 00:00:00 2001 From: Vasili Kachalko Date: Tue, 26 Sep 2023 15:33:52 +0200 Subject: [PATCH 16/28] allow to monitor both running processes --- Dockerfile | 13 ++++++++++--- docker/puma.sh | 3 +++ docker/workers.sh | 3 +++ web-entrypoint.sh | 15 --------------- 4 files changed, 16 insertions(+), 18 deletions(-) create mode 100644 docker/puma.sh create mode 100644 docker/workers.sh delete mode 100644 web-entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 388470ce1..62664b833 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,6 @@ FROM phusion/passenger-ruby31:latest ENV HOME /home/app/deploy -CMD ["/sbin/my_init"] - # Install common dependencies RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ @@ -81,4 +79,13 @@ RUN mkdir -p $HOME/tmp/pids COPY --chown=app:app . . # Precompile assets -RUN SECRET_KEY_BASE=dummyvalue bundle exec rake assets:precompile + +RUN SKIP_SALESFORCE_INIT=true SKIP_FLIPPER_INIT=true SECRET_KEY_BASE=dummyvalue bundle exec rake assets:precompile + +RUN mkdir -p /etc/my_init.d +COPY docker/puma.sh /etc/my_init.d/puma.sh +COPY docker/workers.sh /etc/my_init.d/workers.sh + + +CMD ["/sbin/my_init"] +EXPOSE 3000 diff --git a/docker/puma.sh b/docker/puma.sh new file mode 100644 index 000000000..bfc8f2149 --- /dev/null +++ b/docker/puma.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cd /home/app/deploy && exec bundle exec rails server -b 0.0.0.0 -p 3000 >>/var/log/puma.log 2>&1 diff --git a/docker/workers.sh b/docker/workers.sh new file mode 100644 index 000000000..4aea5d71f --- /dev/null +++ b/docker/workers.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cd /home/app/deploy && exec bundle exec rake jobs:work >>/var/log/workers.log 2>&1 diff --git a/web-entrypoint.sh b/web-entrypoint.sh deleted file mode 100644 index 1bfad2659..000000000 --- a/web-entrypoint.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Database migration - -echo 'prepping database' - -bin/rails db:migrate - -# Runs rails server - -echo 'running server' - -bundle exec rake assets:precompile - -bundle exec rails server -b 0.0.0.0 -p 3000 & rake jobs:work \ No newline at end of file From 09dbb01d4a6da9095f8fc7246406c87eb6ae5374 Mon Sep 17 00:00:00 2001 From: Matthew Ford Date: Tue, 26 Sep 2023 17:37:32 +0100 Subject: [PATCH 17/28] Create .tool-versions Makes managing ruby with asdf easier. Setting to 3.1.4 as that is the latest secure 3.1 patch version and what CI is using. --- .tool-versions | 1 + 1 file changed, 1 insertion(+) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..306ab3370 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.1.4 From 0cc33d6df3a41efed249192ff68496062e9490b2 Mon Sep 17 00:00:00 2001 From: Matthew Ford Date: Tue, 26 Sep 2023 17:45:26 +0100 Subject: [PATCH 18/28] Remove CF utils gem and fix asset compilation in docker remove cf specific gem, fix build errors for assets compilation - update redis url for all envs - use ENV[""] instead of fetch without default values - skip flipper and salesforce initialization during assets compilation - Fix the gemfile.lock --- Dockerfile | 1 + Gemfile | 1 - Gemfile.lock | 5 ++-- config/database.yml | 6 ++--- config/environments/civmiguat.rb | 36 ++++++++++++++-------------- config/environments/production.rb | 40 +++++++++++++++---------------- config/environments/research.rb | 37 ++++++++++++++-------------- config/environments/staging.rb | 36 ++++++++++++++-------------- config/environments/training.rb | 34 +++++++++++++------------- config/environments/uat.rb | 36 ++++++++++++++-------------- config/initializers/constants.rb | 14 +++++------ config/initializers/flipper.rb | 6 ++--- web-entrypoint.sh | 13 ++++++++++ 13 files changed, 139 insertions(+), 126 deletions(-) create mode 100644 web-entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 62664b833..a03ceceef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,6 +47,7 @@ ENV RAILS_ENV=production \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 + # Upgrade RubyGems and install the latest Bundler version ARG BUNDLER_VERSION=2.3.11 RUN echo "gem: --no-rdoc --no-ri >> \"$HOME/.gemrc\"" diff --git a/Gemfile b/Gemfile index c58d3dacb..e5b6af5f5 100644 --- a/Gemfile +++ b/Gemfile @@ -64,7 +64,6 @@ end group :production, :uat, :staging, :training, :civmiguat, :research do gem 'aws-sdk-s3', require: false gem 'remote_syslog_logger', '~> 1.0', '>= 1.0.4' - gem 'cf-app-utils' gem 'delayed_job_active_record', '~> 4.1' gem 'sentry-raven', '~> 3.1.2' end diff --git a/Gemfile.lock b/Gemfile.lock index 25a017dc0..c32616723 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -110,7 +110,6 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - cf-app-utils (0.6) childprocess (3.0.0) coderay (1.1.3) coercible (1.0.0) @@ -242,6 +241,8 @@ GEM activerecord (>= 4.0.0) activesupport (>= 4.0.0) nio4r (2.5.9) + nokogiri (1.15.2-arm64-darwin) + racc (~> 1.4) nokogiri (1.15.2-x86_64-darwin) racc (~> 1.4) nokogiri (1.15.2-x86_64-linux) @@ -448,6 +449,7 @@ GEM zeitwerk (2.6.8) PLATFORMS + arm64-darwin-23 x86_64-darwin-20 x86_64-linux @@ -459,7 +461,6 @@ DEPENDENCIES bundler (~> 2.3, >= 2.3.11) byebug capybara (>= 2.15) - cf-app-utils database_cleaner-active_record (~> 1.8) delayed_job_active_record (~> 4.1) delayed_job_web (~> 1.4) diff --git a/config/database.yml b/config/database.yml index e87e770ba..86023cc30 100644 --- a/config/database.yml +++ b/config/database.yml @@ -10,8 +10,8 @@ default: &default pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 host: <%= ENV.fetch("RAILS_PG_HOST") {"host.docker.internal"} %> - username: <%= ENV.fetch("RAILS_PG_USERNAME")%> - password: <%= ENV.fetch("RAILS_PG_PASSWORD")%> + username: <%= ENV.fetch("RAILS_PG_USERNAME") { "postgres" } %> + password: <%= ENV.fetch("RAILS_PG_PASSWORD") { "postgres" } %> development: <<: *default @@ -30,4 +30,4 @@ production: <<: *default training: - <<: *default \ No newline at end of file + <<: *default diff --git a/config/environments/civmiguat.rb b/config/environments/civmiguat.rb index fa719dd90..202390398 100644 --- a/config/environments/civmiguat.rb +++ b/config/environments/civmiguat.rb @@ -52,7 +52,7 @@ # Use a different cache store in production. config.cache_store = :redis_cache_store, { - url: CF::App::Credentials.find_by_service_label('redis')['uri'], + url: "rediss://:#{ENV["REDIS_PASSWORD"]}@#{ENV["REDIS_URL"]}:#{ENV["REDIS_PORT"]}", connect_timeout: 30, read_timeout: 0.2, write_timeout: 0.2, @@ -119,31 +119,31 @@ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session # Send emails via notify - config.action_mailer.default_url_options = { host: "https://#{JSON.parse(ENV['VCAP_APPLICATION'])['application_uris'][0]}" } + config.action_mailer.default_url_options = { host: "https://#{ENV["HOST_URI"]}" } config.action_mailer.delivery_method = :notify config.action_mailer.notify_settings = { api_key: ENV.fetch("NOTIFY_API_KEY") } - config.x.ideal_postcodes.api_key = ENV.fetch("IDEAL_POSTCODES_API_KEY") - config.x.salesforce.username = ENV.fetch("SALESFORCE_USERNAME") - config.x.salesforce.password = ENV.fetch("SALESFORCE_PASSWORD") - config.x.salesforce.security_token = ENV.fetch("SALESFORCE_SECURITY_TOKEN") - config.x.salesforce.client_id = ENV.fetch("SALESFORCE_CLIENT_ID") - config.x.salesforce.client_secret = ENV.fetch("SALESFORCE_CLIENT_SECRET") + config.x.ideal_postcodes.api_key = ENV["IDEAL_POSTCODES_API_KEY"] + config.x.salesforce.username = ENV["SALESFORCE_USERNAME"] + config.x.salesforce.password = ENV["SALESFORCE_PASSWORD"] + config.x.salesforce.security_token = ENV["SALESFORCE_SECURITY_TOKEN"] + config.x.salesforce.client_id = ENV["SALESFORCE_CLIENT_ID"] + config.x.salesforce.client_secret = ENV["SALESFORCE_CLIENT_SECRET"] config.x.salesforce.host = "test.salesforce.com" - - config.x.payment_encryption_key = ENV.fetch("PAYMENT_ENCRYPTION_KEY") - config.x.payment_encryption_salt = ENV.fetch("PAYMENT_ENCRYPTION_SALT") - - config.x.support_email_address = ENV.fetch("SUPPORT_EMAIL_ADDRESS") - config.x.reply_email_guid = ENV.fetch("REPLY_EMAIL_GUID") - config.x.no_reply_email_address = ENV.fetch("NO_REPLY_EMAIL_ADDRESS") - + + config.x.payment_encryption_key = ENV["PAYMENT_ENCRYPTION_KEY"] + config.x.payment_encryption_salt = ENV["PAYMENT_ENCRYPTION_SALT"] + + config.x.support_email_address = ENV["SUPPORT_EMAIL_ADDRESS"] + config.x.reply_email_guid = ENV["REPLY_EMAIL_GUID"] + config.x.no_reply_email_address = ENV["NO_REPLY_EMAIL_ADDRESS"] + config.lograge.enabled = true config.assets.quiet = true - config.x.consumer.username = ENV.fetch("CONSUMER_USERNAME") - config.x.consumer.password = ENV.fetch("CONSUMER_PASSWORD") + config.x.consumer.username = ENV["CONSUMER_USERNAME"] + config.x.consumer.password = ENV["CONSUMER_PASSWORD"] end \ No newline at end of file diff --git a/config/environments/production.rb b/config/environments/production.rb index 6aa83ad04..08b0896ce 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -41,7 +41,7 @@ # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). - # config.active_storage.service = :local # Rails 7 Upgrade Recommendation, we have disabled. + # config.active_storage.service = :local # Rails 7 Upgrade Recommendation, we have disabled. # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil @@ -62,7 +62,7 @@ # Use a different cache store in production. config.cache_store = :redis_cache_store, { - url: CF::App::Credentials.find_by_service_label('redis')['uri'], + url: "rediss://:#{ENV["REDIS_PASSWORD"]}@#{ENV["REDIS_URL"]}:#{ENV["REDIS_PORT"]}", connect_timeout: 30, read_timeout: 0.2, write_timeout: 0.2, @@ -132,33 +132,33 @@ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session # Send emails via notify - config.action_mailer.default_url_options = { host: "https://#{JSON.parse(ENV['VCAP_APPLICATION'])['application_uris'][0]}" } + config.action_mailer.default_url_options = { host: "https://#{ENV["HOST_URI"]}" } config.action_mailer.delivery_method = :notify config.action_mailer.notify_settings = { - api_key: ENV.fetch("NOTIFY_API_KEY") + api_key: ENV["NOTIFY_API_KEY"] } - config.x.ideal_postcodes.api_key = ENV.fetch("IDEAL_POSTCODES_API_KEY") - config.x.salesforce.username = ENV.fetch("SALESFORCE_USERNAME") - config.x.salesforce.password = ENV.fetch("SALESFORCE_PASSWORD") - config.x.salesforce.security_token = ENV.fetch("SALESFORCE_SECURITY_TOKEN") - config.x.salesforce.client_id = ENV.fetch("SALESFORCE_CLIENT_ID") - config.x.salesforce.client_secret = ENV.fetch("SALESFORCE_CLIENT_SECRET") + config.x.ideal_postcodes.api_key = ENV["IDEAL_POSTCODES_API_KEY"] + config.x.salesforce.username = ENV["SALESFORCE_USERNAME"] + config.x.salesforce.password = ENV["SALESFORCE_PASSWORD"] + config.x.salesforce.security_token = ENV["SALESFORCE_SECURITY_TOKEN"] + config.x.salesforce.client_id = ENV["SALESFORCE_CLIENT_ID"] + config.x.salesforce.client_secret = ENV["SALESFORCE_CLIENT_SECRET"] config.x.salesforce.host = "login.salesforce.com" - config.x.devise_pepper = ENV.fetch("DEVISE_PEPPER") + config.x.devise_pepper = ENV["DEVISE_PEPPER"] config.lograge.enabled = true config.assets.quiet = true - config.x.payment_encryption_key = ENV.fetch("PAYMENT_ENCRYPTION_KEY") - config.x.payment_encryption_salt = ENV.fetch("PAYMENT_ENCRYPTION_SALT") + config.x.payment_encryption_key = ENV["PAYMENT_ENCRYPTION_KEY"] + config.x.payment_encryption_salt = ENV["PAYMENT_ENCRYPTION_SALT"] - config.x.support_email_address = ENV.fetch("SUPPORT_EMAIL_ADDRESS") - config.x.reply_email_guid = ENV.fetch("REPLY_EMAIL_GUID") - config.x.no_reply_email_address = ENV.fetch("NO_REPLY_EMAIL_ADDRESS") + config.x.support_email_address = ENV["SUPPORT_EMAIL_ADDRESS"] + config.x.reply_email_guid = ENV["REPLY_EMAIL_GUID"] + config.x.no_reply_email_address = ENV["NO_REPLY_EMAIL_ADDRESS"] - config.x.delayed_job_web.username = ENV.fetch("DELAYED_JOB_WEB_USERNAME") - config.x.delayed_job_web.password = ENV.fetch("DELAYED_JOB_WEB_PASSWORD") - config.x.consumer.username = ENV.fetch("CONSUMER_USERNAME") - config.x.consumer.password = ENV.fetch("CONSUMER_PASSWORD") + config.x.delayed_job_web.username = ENV["DELAYED_JOB_WEB_USERNAME"] + config.x.delayed_job_web.password = ENV["DELAYED_JOB_WEB_PASSWORD"] + config.x.consumer.username = ENV["CONSUMER_USERNAME"] + config.x.consumer.password = ENV["CONSUMER_PASSWORD"] end diff --git a/config/environments/research.rb b/config/environments/research.rb index 161732091..afc078e3d 100644 --- a/config/environments/research.rb +++ b/config/environments/research.rb @@ -54,7 +54,7 @@ # Use a different cache store in production. config.cache_store = :redis_cache_store, { - url: CF::App::Credentials.find_by_service_label('redis')['uri'], + url: "rediss://:#{ENV["REDIS_PASSWORD"]}@#{ENV["REDIS_URL"]}:#{ENV["REDIS_PORT"]}", connect_timeout: 30, read_timeout: 0.2, write_timeout: 0.2, @@ -121,31 +121,30 @@ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session # Send emails via notify - config.action_mailer.default_url_options = { host: "https://#{JSON.parse(ENV['VCAP_APPLICATION'])['application_uris'][0]}" } + config.action_mailer.default_url_options = { host: "https://#{ENV["HOST_URI"]}" } config.action_mailer.delivery_method = :notify config.action_mailer.notify_settings = { api_key: ENV.fetch("NOTIFY_API_KEY") } - config.x.ideal_postcodes.api_key = ENV.fetch("IDEAL_POSTCODES_API_KEY") - config.x.salesforce.username = ENV.fetch("SALESFORCE_USERNAME") - config.x.salesforce.password = ENV.fetch("SALESFORCE_PASSWORD") - config.x.salesforce.security_token = ENV.fetch("SALESFORCE_SECURITY_TOKEN") - config.x.salesforce.client_id = ENV.fetch("SALESFORCE_CLIENT_ID") - config.x.salesforce.client_secret = ENV.fetch("SALESFORCE_CLIENT_SECRET") + config.x.ideal_postcodes.api_key = ENV["IDEAL_POSTCODES_API_KEY"] + config.x.salesforce.username = ENV["SALESFORCE_USERNAME"] + config.x.salesforce.password = ENV["SALESFORCE_PASSWORD"] + config.x.salesforce.security_token = ENV["SALESFORCE_SECURITY_TOKEN"] + config.x.salesforce.client_id = ENV["SALESFORCE_CLIENT_ID"] + config.x.salesforce.client_secret = ENV["SALESFORCE_CLIENT_SECRET"] config.x.salesforce.host = "test.salesforce.com" - - config.x.payment_encryption_key = ENV.fetch("PAYMENT_ENCRYPTION_KEY") - config.x.payment_encryption_salt = ENV.fetch("PAYMENT_ENCRYPTION_SALT") - - config.x.support_email_address = ENV.fetch("SUPPORT_EMAIL_ADDRESS") - config.x.reply_email_guid = ENV.fetch("REPLY_EMAIL_GUID") - config.x.no_reply_email_address = ENV.fetch("NO_REPLY_EMAIL_ADDRESS") - + + config.x.payment_encryption_key = ENV["PAYMENT_ENCRYPTION_KEY"] + config.x.payment_encryption_salt = ENV["PAYMENT_ENCRYPTION_SALT"] + + config.x.support_email_address = ENV["SUPPORT_EMAIL_ADDRESS"] + config.x.reply_email_guid = ENV["REPLY_EMAIL_GUID"] + config.x.no_reply_email_address = ENV["NO_REPLY_EMAIL_ADDRESS"] + config.lograge.enabled = true config.assets.quiet = true - config.x.consumer.username = ENV.fetch("CONSUMER_USERNAME") - config.x.consumer.password = ENV.fetch("CONSUMER_PASSWORD") - + config.x.consumer.username = ENV["CONSUMER_USERNAME"] + config.x.consumer.password = ENV["CONSUMER_PASSWORD"] end \ No newline at end of file diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 3bcf79347..db1ed853f 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -54,7 +54,7 @@ # Use a different cache store in production. config.cache_store = :redis_cache_store, { - url: CF::App::Credentials.find_by_service_label('redis')['uri'], + url: "rediss://:#{ENV["REDIS_PASSWORD"]}@#{ENV["REDIS_URL"]}:#{ENV["REDIS_PORT"]}", connect_timeout: 30, read_timeout: 0.2, write_timeout: 0.2, @@ -121,30 +121,30 @@ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session # Send emails via notify - config.action_mailer.default_url_options = { host: "https://#{JSON.parse(ENV['VCAP_APPLICATION'])['application_uris'][0]}" } + config.action_mailer.default_url_options = { host: "https://#{ENV["HOST_URI"]}" } config.action_mailer.delivery_method = :notify config.action_mailer.notify_settings = { api_key: ENV.fetch("NOTIFY_API_KEY") } - config.x.ideal_postcodes.api_key = ENV.fetch("IDEAL_POSTCODES_API_KEY") - config.x.salesforce.username = ENV.fetch("SALESFORCE_USERNAME") - config.x.salesforce.password = ENV.fetch("SALESFORCE_PASSWORD") - config.x.salesforce.security_token = ENV.fetch("SALESFORCE_SECURITY_TOKEN") - config.x.salesforce.client_id = ENV.fetch("SALESFORCE_CLIENT_ID") - config.x.salesforce.client_secret = ENV.fetch("SALESFORCE_CLIENT_SECRET") + config.x.ideal_postcodes.api_key = ENV["IDEAL_POSTCODES_API_KEY"] + config.x.salesforce.username = ENV["SALESFORCE_USERNAME"] + config.x.salesforce.password = ENV["SALESFORCE_PASSWORD"] + config.x.salesforce.security_token = ENV["SALESFORCE_SECURITY_TOKEN"] + config.x.salesforce.client_id = ENV["SALESFORCE_CLIENT_ID"] + config.x.salesforce.client_secret = ENV["SALESFORCE_CLIENT_SECRET"] config.x.salesforce.host = "test.salesforce.com" - - config.x.payment_encryption_key = ENV.fetch("PAYMENT_ENCRYPTION_KEY") - config.x.payment_encryption_salt = ENV.fetch("PAYMENT_ENCRYPTION_SALT") - - config.x.support_email_address = ENV.fetch("SUPPORT_EMAIL_ADDRESS") - config.x.reply_email_guid = ENV.fetch("REPLY_EMAIL_GUID") - config.x.no_reply_email_address = ENV.fetch("NO_REPLY_EMAIL_ADDRESS") - + + config.x.payment_encryption_key = ENV["PAYMENT_ENCRYPTION_KEY"] + config.x.payment_encryption_salt = ENV["PAYMENT_ENCRYPTION_SALT"] + + config.x.support_email_address = ENV["SUPPORT_EMAIL_ADDRESS"] + config.x.reply_email_guid = ENV["REPLY_EMAIL_GUID"] + config.x.no_reply_email_address = ENV["NO_REPLY_EMAIL_ADDRESS"] + config.lograge.enabled = true config.assets.quiet = true - config.x.consumer.username = ENV.fetch("CONSUMER_USERNAME") - config.x.consumer.password = ENV.fetch("CONSUMER_PASSWORD") + config.x.consumer.username = ENV["CONSUMER_USERNAME"] + config.x.consumer.password = ENV["CONSUMER_PASSWORD"] end diff --git a/config/environments/training.rb b/config/environments/training.rb index 50d94d1bd..910eeca73 100644 --- a/config/environments/training.rb +++ b/config/environments/training.rb @@ -130,31 +130,31 @@ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session # Send emails via notify - config.action_mailer.default_url_options = { host: "https://#{ENV.fetch("HOST_URI")}" } + config.action_mailer.default_url_options = { host: "https://#{ENV["HOST_URI"]}" } config.action_mailer.delivery_method = :notify config.action_mailer.notify_settings = { api_key: ENV.fetch("NOTIFY_API_KEY") } - config.x.ideal_postcodes.api_key = ENV.fetch("IDEAL_POSTCODES_API_KEY") - config.x.salesforce.username = ENV.fetch("SALESFORCE_USERNAME") - config.x.salesforce.password = ENV.fetch("SALESFORCE_PASSWORD") - config.x.salesforce.security_token = ENV.fetch("SALESFORCE_SECURITY_TOKEN") - config.x.salesforce.client_id = ENV.fetch("SALESFORCE_CLIENT_ID") - config.x.salesforce.client_secret = ENV.fetch("SALESFORCE_CLIENT_SECRET") + config.x.ideal_postcodes.api_key = ENV["IDEAL_POSTCODES_API_KEY"] + config.x.salesforce.username = ENV["SALESFORCE_USERNAME"] + config.x.salesforce.password = ENV["SALESFORCE_PASSWORD"] + config.x.salesforce.security_token = ENV["SALESFORCE_SECURITY_TOKEN"] + config.x.salesforce.client_id = ENV["SALESFORCE_CLIENT_ID"] + config.x.salesforce.client_secret = ENV["SALESFORCE_CLIENT_SECRET"] config.x.salesforce.host = "test.salesforce.com" - - config.x.payment_encryption_key = ENV.fetch("PAYMENT_ENCRYPTION_KEY") - config.x.payment_encryption_salt = ENV.fetch("PAYMENT_ENCRYPTION_SALT") - - config.x.support_email_address = ENV.fetch("SUPPORT_EMAIL_ADDRESS") - config.x.reply_email_guid = ENV.fetch("REPLY_EMAIL_GUID") - config.x.no_reply_email_address = ENV.fetch("NO_REPLY_EMAIL_ADDRESS") - + + config.x.payment_encryption_key = ENV["PAYMENT_ENCRYPTION_KEY"] + config.x.payment_encryption_salt = ENV["PAYMENT_ENCRYPTION_SALT"] + + config.x.support_email_address = ENV["SUPPORT_EMAIL_ADDRESS"] + config.x.reply_email_guid = ENV["REPLY_EMAIL_GUID"] + config.x.no_reply_email_address = ENV["NO_REPLY_EMAIL_ADDRESS"] + config.lograge.enabled = true config.assets.quiet = true - config.x.consumer.username = ENV.fetch("CONSUMER_USERNAME") - config.x.consumer.password = ENV.fetch("CONSUMER_PASSWORD") + config.x.consumer.username = ENV["CONSUMER_USERNAME"] + config.x.consumer.password = ENV["CONSUMER_PASSWORD"] end \ No newline at end of file diff --git a/config/environments/uat.rb b/config/environments/uat.rb index 2d2928a1e..f49683ac0 100644 --- a/config/environments/uat.rb +++ b/config/environments/uat.rb @@ -54,7 +54,7 @@ # Use a different cache store in production. config.cache_store = :redis_cache_store, { - url: CF::App::Credentials.find_by_service_label('redis')['uri'], + url: "rediss://:#{ENV["REDIS_PASSWORD"]}@#{ENV["REDIS_URL"]}:#{ENV["REDIS_PORT"]}", connect_timeout: 30, read_timeout: 0.2, write_timeout: 0.2, @@ -121,31 +121,31 @@ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session # Send emails via notify - config.action_mailer.default_url_options = { host: "https://#{JSON.parse(ENV['VCAP_APPLICATION'])['application_uris'][0]}" } + config.action_mailer.default_url_options = { host: "https://#{ENV["HOST_URI"]}" } config.action_mailer.delivery_method = :notify config.action_mailer.notify_settings = { api_key: ENV.fetch("NOTIFY_API_KEY") } - config.x.ideal_postcodes.api_key = ENV.fetch("IDEAL_POSTCODES_API_KEY") - config.x.salesforce.username = ENV.fetch("SALESFORCE_USERNAME") - config.x.salesforce.password = ENV.fetch("SALESFORCE_PASSWORD") - config.x.salesforce.security_token = ENV.fetch("SALESFORCE_SECURITY_TOKEN") - config.x.salesforce.client_id = ENV.fetch("SALESFORCE_CLIENT_ID") - config.x.salesforce.client_secret = ENV.fetch("SALESFORCE_CLIENT_SECRET") + config.x.ideal_postcodes.api_key = ENV["IDEAL_POSTCODES_API_KEY"] + config.x.salesforce.username = ENV["SALESFORCE_USERNAME"] + config.x.salesforce.password = ENV["SALESFORCE_PASSWORD"] + config.x.salesforce.security_token = ENV["SALESFORCE_SECURITY_TOKEN"] + config.x.salesforce.client_id = ENV["SALESFORCE_CLIENT_ID"] + config.x.salesforce.client_secret = ENV["SALESFORCE_CLIENT_SECRET"] config.x.salesforce.host = "test.salesforce.com" - - config.x.payment_encryption_key = ENV.fetch("PAYMENT_ENCRYPTION_KEY") - config.x.payment_encryption_salt = ENV.fetch("PAYMENT_ENCRYPTION_SALT") - - config.x.support_email_address = ENV.fetch("SUPPORT_EMAIL_ADDRESS") - config.x.reply_email_guid = ENV.fetch("REPLY_EMAIL_GUID") - config.x.no_reply_email_address = ENV.fetch("NO_REPLY_EMAIL_ADDRESS") - + + config.x.payment_encryption_key = ENV["PAYMENT_ENCRYPTION_KEY"] + config.x.payment_encryption_salt = ENV["PAYMENT_ENCRYPTION_SALT"] + + config.x.support_email_address = ENV["SUPPORT_EMAIL_ADDRESS"] + config.x.reply_email_guid = ENV["REPLY_EMAIL_GUID"] + config.x.no_reply_email_address = ENV["NO_REPLY_EMAIL_ADDRESS"] + config.lograge.enabled = true config.assets.quiet = true - config.x.consumer.username = ENV.fetch("CONSUMER_USERNAME") - config.x.consumer.password = ENV.fetch("CONSUMER_PASSWORD") + config.x.consumer.username = ENV["CONSUMER_USERNAME"] + config.x.consumer.password = ENV["CONSUMER_PASSWORD"] end \ No newline at end of file diff --git a/config/initializers/constants.rb b/config/initializers/constants.rb index 0e9936f35..33f95f29e 100644 --- a/config/initializers/constants.rb +++ b/config/initializers/constants.rb @@ -1,9 +1,9 @@ Rails.application.reloader.to_prepare do - Rails.env.test? ? \ - - SALESFORCE_URL_BASE = "" : \ - - SALESFORCE_URL_BASE = SalesforceApi::SalesforceApiClient.new.get_salesforce_url - -end \ No newline at end of file + SALESFORCE_URL_BASE = + if Rails.env.test? || ENV["SKIP_SALESFORCE_INIT"] == "true" + "" + else + SalesforceApi::SalesforceApiClient.new.get_salesforce_url + end +end diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index 76704b32e..ee74c1b77 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -1,10 +1,10 @@ -if ActiveRecord::Base.connection.table_exists? :flipper_features +if ENV["SKIP_FLIPPER_INIT"] != "true" && ActiveRecord::Base.connection.table_exists?(:flipper_features) Flipper.configure do |config| config.default do adapter = Flipper::Adapters::ActiveRecord.new Flipper.new(adapter) end - # Flipper gates toggle app features on and off. Adding the flippers + # Flipper gates toggle app features on and off. Adding the flippers # here creates a row in flipper_features. # To control toggles - see README Flipper[:registration_enabled].add @@ -27,4 +27,4 @@ Flipper[:import_existing_account_enabled].add Flipper[:disable_ffe].add end -end \ No newline at end of file +end diff --git a/web-entrypoint.sh b/web-entrypoint.sh new file mode 100644 index 000000000..1f1b81f9c --- /dev/null +++ b/web-entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Database migration + +echo 'prepping database' + +bin/rails db:migrate + +# Runs rails server + +echo 'running server' + +bundle exec rails server -b 0.0.0.0 -p 3000 & rake jobs:work From 686cc23df037f9c4e46d569d105fc3001756879e Mon Sep 17 00:00:00 2001 From: Matthew Ford Date: Tue, 26 Sep 2023 17:51:48 +0100 Subject: [PATCH 19/28] Update storage.yml Add back in the config for the other envs --- config/storage.yml | 76 +++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/config/storage.yml b/config/storage.yml index f8d95046a..d36cc59db 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -6,54 +6,48 @@ local: service: Disk root: <%= Rails.root.join("storage") %> +research: + service: S3 + access_key_id: <%= Rails.env.research? ? ENV['AWS_ACCESS_KEY'] : "" %> + secret_access_key: <%= Rails.env.research? ? ENV['AWS_SECRET_KEY'] : "" %> + region: <%= Rails.env.research? ? ENV['AWS_REGION'] : "" %> + bucket: <%= Rails.env.research? ? ENV['AWS_BUCKET'] : "" %> -# research: -# service: S3 -# access_key_id: <%= Rails.env.research? ? ENV.fetch('AWS_ACCESS_KEY') : "" %> -# secret_access_key: <%= Rails.env.research? ? ENV.fetch('AWS_SECRET_KEY') : "" %> -# region: <%= Rails.env.research? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['aws_region'] : "" %> -# bucket: <%= Rails.env.research? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['bucket_name'] : "" %> - -# # Rails tries to read this on all environments and expects a string value -# staging: -# service: S3 -# access_key_id: <%= Rails.env.staging? ? ENV.fetch('AWS_ACCESS_KEY') : "" %> -# secret_access_key: <%= Rails.env.staging? ? ENV.fetch('AWS_SECRET_KEY') : "" %> -# region: <%= Rails.env.staging? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['aws_region'] : "" %> -# bucket: <%= Rails.env.staging? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['bucket_name'] : "" %> +staging: + service: S3 + access_key_id: <%= Rails.env.staging? ? ENV['AWS_ACCESS_KEY'] : "" %> + secret_access_key: <%= Rails.env.staging? ? ENV['AWS_SECRET_KEY'] : "" %> + region: <%= Rails.env.staging? ? ENV['AWS_REGION'] : "" %> + bucket: <%= Rails.env.staging? ? ENV['AWS_BUCKET'] : "" %> -# Rails tries to read this on all environments and expects a string value training: service: S3 - access_key_id: <%= Rails.env.training? ? ENV.fetch('AWS_ACCESS_KEY') : "" %> - secret_access_key: <%= Rails.env.training? ? ENV.fetch('AWS_SECRET_KEY') : "" %> - region: <%= Rails.env.training? ? ENV.fetch('AWS_REGION') : "" %> - bucket: <%= Rails.env.training? ? ENV.fetch('AWS_BUCKET') : "" %> - -# Rails tries to read this on all environments and expects a string value -# uat: -# service: S3 -# access_key_id: <%= Rails.env.uat? ? ENV.fetch('AWS_ACCESS_KEY') : "" %> -# secret_access_key: <%= Rails.env.uat? ? ENV.fetch('AWS_SECRET_KEY') : "" %> -# region: <%= Rails.env.uat? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['aws_region'] : "" %> -# bucket: <%= Rails.env.uat? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['bucket_name'] : "" %> + access_key_id: <%= Rails.env.training? ? ENV['AWS_ACCESS_KEY'] : "" %> + secret_access_key: <%= Rails.env.training? ? ENV['AWS_SECRET_KEY'] : "" %> + region: <%= Rails.env.training? ? ENV['AWS_REGION'] : "" %> + bucket: <%= Rails.env.training? ? ENV['AWS_BUCKET'] : "" %> -# # Rails tries to read this on all environments and expects a string value -# civmiguat: -# service: S3 -# access_key_id: <%= Rails.env.civmiguat? ? ENV.fetch('AWS_ACCESS_KEY') : "" %> -# secret_access_key: <%= Rails.env.civmiguat? ? ENV.fetch('AWS_SECRET_KEY') : "" %> -# region: <%= Rails.env.civmiguat? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['aws_region'] : "" %> -# bucket: <%= Rails.env.civmiguat? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['bucket_name'] : "" %> +uat: + service: S3 + access_key_id: <%= Rails.env.uat? ? ENV['AWS_ACCESS_KEY'] : "" %> + secret_access_key: <%= Rails.env.uat? ? ENV['AWS_SECRET_KEY'] : "" %> + region: <%= Rails.env.uat? ? ENV['AWS_REGION'] : "" %> + bucket: <%= Rails.env.uat? ? ENV['AWS_BUCKET'] : "" %> -# # Rails tries to read this on all environments and expects a string value -# production: -# service: S3 -# access_key_id: <%= Rails.env.production? ? ENV.fetch('AWS_ACCESS_KEY') : "" %> -# secret_access_key: <%= Rails.env.production? ? ENV.fetch('AWS_SECRET_KEY') : "" %> -# region: <%= Rails.env.production? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['aws_region'] : "" %> -# bucket: <%= Rails.env.production? ? CF::App::Credentials.find_by_service_label('aws-s3-bucket')['bucket_name'] : "" %> +civmiguat: + service: S3 + access_key_id: <%= Rails.env.civmiguat? ? ENV['AWS_ACCESS_KEY'] : "" %> + secret_access_key: <%= Rails.env.civmiguat? ? ENV['AWS_SECRET_KEY'] : "" %> + region: <%= Rails.env.civmiguat? ? ENV['AWS_REGION'] : "" %> + bucket: <%= Rails.env.civmiguat? ? ENV['AWS_BUCKET'] : "" %> +production: + service: S3 + access_key_id: <%= Rails.env.production? ? ENV['AWS_ACCESS_KEY'] : "" %> + secret_access_key: <%= Rails.env.production? ? ENV['AWS_SECRET_KEY'] : "" %> + region: <%= Rails.env.production? ? ENV['AWS_REGION'] : "" %> + bucket: <%= Rails.env.production? ? ENV['AWS_BUCKET'] : "" %> + # Remember not to checkin your GCS keyfile to a repository # google: # service: GCS From 9028a94f611d15503c66592a9edec352911e96b7 Mon Sep 17 00:00:00 2001 From: Matthew Ford Date: Tue, 26 Sep 2023 18:06:10 +0100 Subject: [PATCH 20/28] Delete web-entrypoint.sh We are running processes under the docker folder --- web-entrypoint.sh | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 web-entrypoint.sh diff --git a/web-entrypoint.sh b/web-entrypoint.sh deleted file mode 100644 index 1f1b81f9c..000000000 --- a/web-entrypoint.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Database migration - -echo 'prepping database' - -bin/rails db:migrate - -# Runs rails server - -echo 'running server' - -bundle exec rails server -b 0.0.0.0 -p 3000 & rake jobs:work From f1e0eabc845a7fe23f40e7b630fa9bca9c06a29e Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 27 Sep 2023 00:57:12 -0300 Subject: [PATCH 21/28] Fix DB host fallback preventing tests to run --- config/database.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/database.yml b/config/database.yml index 86023cc30..c674f6f00 100644 --- a/config/database.yml +++ b/config/database.yml @@ -9,7 +9,7 @@ default: &default database: <%= ENV.fetch("RAILS_DATABASE") {"funding_frontend"} %> pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 - host: <%= ENV.fetch("RAILS_PG_HOST") {"host.docker.internal"} %> + host: <%= ENV.fetch("RAILS_PG_HOST") { "localhost" } %> username: <%= ENV.fetch("RAILS_PG_USERNAME") { "postgres" } %> password: <%= ENV.fetch("RAILS_PG_PASSWORD") { "postgres" } %> From cd0bdb31088455ba00e2b71281e4e1fc9d562610 Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 27 Sep 2023 01:01:14 -0300 Subject: [PATCH 22/28] Add workflow to deploy application to Azure container --- .github/workflows/azure-deploy-production.yml | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/workflows/azure-deploy-production.yml diff --git a/.github/workflows/azure-deploy-production.yml b/.github/workflows/azure-deploy-production.yml new file mode 100644 index 000000000..954a10c7d --- /dev/null +++ b/.github/workflows/azure-deploy-production.yml @@ -0,0 +1,130 @@ +name: Build, test & deploy to Azure Web App + +env: + AZURE_WEBAPP_NAME: funding-frontend # Name set within Azure Web App + REGISTRY: ghcr.io + +on: + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + test: + runs-on: ubuntu-latest + services: + db: + image: postgres:11-alpine + ports: ['5432:5432'] + env: + POSTGRES_DB: funding_frontend_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 15s + --health-retries 5 + redis: + image: redis + ports: ['6379:6379'] + options: --entrypoint redis-server + + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.1 + - uses: actions/setup-node@v2-beta + with: + node-version: '16' + - run: npm install -g yarn + - uses: nanasess/setup-chromedriver@master + + - name: Build and run tests + env: + DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/funding_frontend_test' + BUNDLER_VERSION: 2.3.11 + DOCKER_TLS_CERTDIR: '' + run: | + sudo apt update + sudo apt-get -yqq install postgresql postgresql-client libpq-dev xvfb unzip libcurl4 libcurl3-gnutls libcurl4-openssl-dev + gem install bundler + gem update --system && gem update bundler + yarn install + bundle install --jobs 4 --retry 3 + RAILS_ENV=test bundle exec rake db:setup + RAILS_ENV=test RAILS_DISABLE_TEST_LOG=true bundle exec rspec + + buildx: + runs-on: ubuntu-latest + needs: [test] + if: github.ref == 'refs/heads/master' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Lowercase the repository name and username + run: echo "REPOSITORY=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + + - name: Build the container image and push it to the registry + uses: docker/build-push-action@v2 + with: + push: true + tags: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }} + file: ./Dockerfile + + deploy: + runs-on: ubuntu-latest + needs: [buildx] + + permissions: + contents: none + + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Lowercase the repository name and username + run: echo "REPOSITORY=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: ${{ env.AZURE_WEBAPP_NAME }} + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + images: '${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }}' From d3e0010205e123b2b63fa65cfbbe260d2c69012f Mon Sep 17 00:00:00 2001 From: Matthew Ford Date: Wed, 27 Sep 2023 12:39:27 +0100 Subject: [PATCH 23/28] Switch Docker image to use Foreman and Ruby base image We are having issues debugging the Passenger image. Switching to another image and using a Procfile to manage the two processes. --- .dockerignore | 56 +++++-------- .node-version | 1 + Dockerfile | 185 +++++++++++++++++++++--------------------- Procfile | 2 + bin/docker-entrypoint | 7 ++ config/puma.rb | 8 +- docker/puma.sh | 3 - docker/workers.sh | 3 - 8 files changed, 130 insertions(+), 135 deletions(-) create mode 100644 .node-version create mode 100644 Procfile create mode 100755 bin/docker-entrypoint delete mode 100644 docker/puma.sh delete mode 100644 docker/workers.sh diff --git a/.dockerignore b/.dockerignore index e5c04a3a8..ca0f731c0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,14 +1,18 @@ -# Replicates gitignore - but keeps .env for local development -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile '~/.gitignore_global' +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ # Ignore bundler config. /.bundle -/db/*.sqlite3 -/db/*.sqlite3-journal +# Ignore all default key files. +/config/master.key +/config/credentials/*.key + +# Ignore all environment files. +/.env* +!/.env.example # Ignore all logfiles and tempfiles. /log/* @@ -16,34 +20,18 @@ !/log/.keep !/tmp/.keep -# Ignore uploaded files in development. +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). /storage/* !/storage/.keep +/tmp/storage/* +!/tmp/storage/.keep +# Ignore assets. +/node_modules/ +/app/assets/builds/* +!/app/assets/builds/.keep /public/assets -.byebug_history - -# Ignore master key for decrypting credentials and more. -/config/master.key - -/public/packs -/public/packs-test -/node_modules -/node_modules/.cache/ -/yarn-error.log -yarn-debug.log* -.yarn-integrity - -# Credentials - env is copied if present -# but env is never present in source control. -# so docker can only copy when running locally -.env -.dockersenv - -# Ignore Mac specific files -.DS_Store - -/package-lock.json - -# Salesforce logs -lib/apis/salesforce/log diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..cb406c60c --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +16.20.2 diff --git a/Dockerfile b/Dockerfile index a03ceceef..8a23ae7ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,92 +1,95 @@ -# syntax=docker/dockerfile:1 - -FROM phusion/passenger-ruby31:latest - -ENV HOME /home/app/deploy - -# Install common dependencies -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - --mount=type=tmpfs,target=/var/log \ - apt-get update -qq \ - && apt-get dist-upgrade -y \ - && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ - curl \ - gnupg2 \ - less \ - tzdata \ - time \ - locales \ - && update-locale LANG=C.UTF-8 LC_ALL=C.UTF-8 - -# Install NodeJS and Yarn -ARG NODE_MAJOR=16 -RUN yes | apt remove nodejs -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - --mount=type=tmpfs,target=/var/log \ - apt-get update && \ - apt-get install -y curl software-properties-common && \ - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ - echo "deb https://deb.nodesource.com/node_${NODE_MAJOR}.x $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends nodejs - -ARG YARN_VERSION=latest -RUN npm install -g yarn@$YARN_VERSION - -# Configure bundler -ENV RAILS_ENV=production \ - NODE_ENV=production \ - BUNDLE_JOBS=4 \ - BUNDLE_RETRY=3 \ - BUNDLE_APP_CONFIG=/home/app/.bundle \ - BUNDLE_PATH=/home/app/.bundle \ - GEM_HOME=/home/app/.bundle \ - PATH="$HOME/bin:${PATH}" \ - LANG=C.UTF-8 \ - LC_ALL=C.UTF-8 - - -# Upgrade RubyGems and install the latest Bundler version -ARG BUNDLER_VERSION=2.3.11 -RUN echo "gem: --no-rdoc --no-ri >> \"$HOME/.gemrc\"" -RUN gem update --system && \ - gem install bundler:$BUNDLER_VERSION - -# Create a directory for the app code -RUN mkdir -p $HOME -WORKDIR $HOME - -# Install Ruby gems -COPY --chown=app:app Gemfile Gemfile.lock ./ -RUN bundle lock --add-platform aarch64-linux - -RUN mkdir $BUNDLE_PATH \ - && bundle config --local path "${BUNDLE_PATH}" \ - && bundle config --local without 'development test' \ - && bundle config --local clean 'true' \ - && bundle config --local no-cache 'true' \ - && bundle install --jobs=${BUNDLE_JOBS} \ - && rm -rf $BUNDLE_PATH/ruby/3.1.0/cache/* \ - && rm -rf $HOME/.bundle/cache/* - -# Install JS packages -COPY --chown=app:app package.json yarn.lock ./ -RUN yarn install --check-files - -RUN mkdir -p $HOME/tmp/pids - -COPY --chown=app:app . . - -# Precompile assets - -RUN SKIP_SALESFORCE_INIT=true SKIP_FLIPPER_INIT=true SECRET_KEY_BASE=dummyvalue bundle exec rake assets:precompile - -RUN mkdir -p /etc/my_init.d -COPY docker/puma.sh /etc/my_init.d/puma.sh -COPY docker/workers.sh /etc/my_init.d/workers.sh - - -CMD ["/sbin/my_init"] +# syntax = docker/dockerfile:1 + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile +ARG RUBY_VERSION=3.1.4 +FROM ruby:$RUBY_VERSION-slim as base + +# Rails app lives here +WORKDIR /rails + +# Set production environment +ENV RAILS_ENV="production" \ + BUNDLE_WITHOUT="development:test" \ + BUNDLE_DEPLOYMENT="1" + +# Update gems and bundler +RUN gem update --system --no-document && \ + gem install -N bundler &&\ + gem install -N foreman + +# Throw-away build stages to reduce size of final image +FROM base as prebuild + +# Install packages needed to build gems and node modules +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential curl libpq-dev node-gyp pkg-config python-is-python3 + + +FROM prebuild as node + +# Install JavaScript dependencies +ARG NODE_VERSION=16.20.2 +ARG YARN_VERSION=1.22.19 +ENV PATH=/usr/local/node/bin:$PATH +RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ + npm install -g yarn@$YARN_VERSION && \ + rm -rf /tmp/node-build-master + +# Install node modules +COPY --link package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + + +FROM prebuild as build + +# Install application gems +COPY --link Gemfile Gemfile.lock ./ +RUN bundle install && \ + bundle exec bootsnap precompile --gemfile && \ + rm -rf ~/.bundle/ $BUNDLE_PATH/ruby/*/cache $BUNDLE_PATH/ruby/*/bundler/gems/*/.git + +# Copy node modules +COPY --from=node /rails/node_modules /rails/node_modules +COPY --from=node /usr/local/node /usr/local/node +ENV PATH=/usr/local/node/bin:$PATH + +# Copy application code +COPY --link . . + +# Precompile bootsnap code for faster boot times +RUN bundle exec bootsnap precompile app/ lib/ + +# Precompiling assets for production without requiring secret RAILS_MASTER_KEY +RUN SKIP_SALESFORCE_INIT=true SKIP_FLIPPER_INIT=true SECRET_KEY_BASE=DUMMY ./bin/rails assets:precompile + + +# Final stage for app image +FROM base + +# Install packages needed for deployment +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 postgresql-client ruby-foreman && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Copy built artifacts: gems, application +COPY --from=build /usr/local/bundle /usr/local/bundle +COPY --from=build /rails /rails + +# Run and own only the runtime files as a non-root user for security +RUN useradd rails --create-home --shell /bin/bash && \ + chown -R rails:rails db log storage tmp +USER rails:rails + +# Deployment options +ENV LD_PRELOAD="libjemalloc.so.2" \ + MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true" \ + RAILS_LOG_TO_STDOUT="1" \ + RAILS_SERVE_STATIC_FILES="true" + +# Entrypoint prepares the database. +ENTRYPOINT ["/rails/bin/docker-entrypoint"] + +# Start the server by default, this can be overwritten at runtime EXPOSE 3000 +CMD ["foreman", "start", "--procfile=Procfile"] diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..aa4ffcb88 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: bundle exec puma -C config/puma.rb +worker: bundle exec rake jobs:work diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100755 index 000000000..3f8d37102 --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,7 @@ +#!/bin/bash -e + +if [ "${*}" == "foreman start --procfile=Procfile" ]; then + ./bin/rails db:prepare +fi + +exec "${@}" diff --git a/config/puma.rb b/config/puma.rb index 2d3d96983..5b97cb1a6 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -17,7 +17,7 @@ environment ENV.fetch("RAILS_ENV") { "development" } # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } +# pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked web server processes. If using threads and workers together @@ -25,14 +25,14 @@ # Workers do not work on JRuby or Windows (both of which do not support # processes). # -# workers ENV.fetch("WEB_CONCURRENCY") { 2 } +workers ENV.fetch("WEB_CONCURRENCY") { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write # process behavior so workers use less memory. # -# preload_app! +preload_app! # Allow puma to be restarted by `rails restart` command. -plugin :tmp_restart +# plugin :tmp_restart diff --git a/docker/puma.sh b/docker/puma.sh deleted file mode 100644 index bfc8f2149..000000000 --- a/docker/puma.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -cd /home/app/deploy && exec bundle exec rails server -b 0.0.0.0 -p 3000 >>/var/log/puma.log 2>&1 diff --git a/docker/workers.sh b/docker/workers.sh deleted file mode 100644 index 4aea5d71f..000000000 --- a/docker/workers.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -cd /home/app/deploy && exec bundle exec rake jobs:work >>/var/log/workers.log 2>&1 From bf461ccd6d6d044519de2d7bae51ab7ea7e6234b Mon Sep 17 00:00:00 2001 From: Matthew Ford Date: Wed, 27 Sep 2023 12:43:06 +0100 Subject: [PATCH 24/28] Update Gemfile.lock --- Gemfile.lock | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 53149b244..bcdae14c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -241,6 +241,8 @@ GEM activerecord (>= 4.0.0) activesupport (>= 4.0.0) nio4r (2.5.9) + nokogiri (1.15.2-aarch64-linux) + racc (~> 1.4) nokogiri (1.15.2-arm64-darwin) racc (~> 1.4) nokogiri (1.15.2-x86_64-darwin) @@ -449,6 +451,7 @@ GEM zeitwerk (2.6.8) PLATFORMS + aarch64-linux arm64-darwin-23 x86_64-darwin-20 x86_64-linux From 6a3eff9c21b44a8fa7a793b2237421279ec4838f Mon Sep 17 00:00:00 2001 From: Matthew Ford Date: Wed, 27 Sep 2023 12:47:49 +0100 Subject: [PATCH 25/28] Set node-version to 16.20.2 --- .github/workflows/azure-deploy-production.yml | 2 +- .github/workflows/continuous-integration-workflow.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/azure-deploy-production.yml b/.github/workflows/azure-deploy-production.yml index 954a10c7d..e9c3a710f 100644 --- a/.github/workflows/azure-deploy-production.yml +++ b/.github/workflows/azure-deploy-production.yml @@ -58,7 +58,7 @@ jobs: ruby-version: 3.1.1 - uses: actions/setup-node@v2-beta with: - node-version: '16' + node-version: '16.20.2' - run: npm install -g yarn - uses: nanasess/setup-chromedriver@master diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 5a86ef69e..3b17d8008 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -36,7 +36,7 @@ jobs: - uses: actions/setup-node@v1 with: - node-version: '16.14.2' + node-version: '16.20.2' - uses: ruby/setup-ruby@v1 with: From 2e1b07d5dafc146ef0d692f9cfb057c9e23b234e Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 27 Sep 2023 09:15:35 -0300 Subject: [PATCH 26/28] Make workflow to deploy to `uat` --- .../{azure-deploy-production.yml => azure-deploy-uat.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{azure-deploy-production.yml => azure-deploy-uat.yml} (98%) diff --git a/.github/workflows/azure-deploy-production.yml b/.github/workflows/azure-deploy-uat.yml similarity index 98% rename from .github/workflows/azure-deploy-production.yml rename to .github/workflows/azure-deploy-uat.yml index e9c3a710f..d6ea48040 100644 --- a/.github/workflows/azure-deploy-production.yml +++ b/.github/workflows/azure-deploy-uat.yml @@ -1,7 +1,7 @@ name: Build, test & deploy to Azure Web App env: - AZURE_WEBAPP_NAME: funding-frontend # Name set within Azure Web App + AZURE_WEBAPP_NAME: uat REGISTRY: ghcr.io on: From d2185e4010302c00a3d623dbd57e052982c89627 Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 27 Sep 2023 01:01:14 -0300 Subject: [PATCH 27/28] Add workflow to deploy application to Azure container --- .github/workflows/azure-deploy-production.yml | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/workflows/azure-deploy-production.yml diff --git a/.github/workflows/azure-deploy-production.yml b/.github/workflows/azure-deploy-production.yml new file mode 100644 index 000000000..954a10c7d --- /dev/null +++ b/.github/workflows/azure-deploy-production.yml @@ -0,0 +1,130 @@ +name: Build, test & deploy to Azure Web App + +env: + AZURE_WEBAPP_NAME: funding-frontend # Name set within Azure Web App + REGISTRY: ghcr.io + +on: + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + test: + runs-on: ubuntu-latest + services: + db: + image: postgres:11-alpine + ports: ['5432:5432'] + env: + POSTGRES_DB: funding_frontend_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 15s + --health-retries 5 + redis: + image: redis + ports: ['6379:6379'] + options: --entrypoint redis-server + + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.1 + - uses: actions/setup-node@v2-beta + with: + node-version: '16' + - run: npm install -g yarn + - uses: nanasess/setup-chromedriver@master + + - name: Build and run tests + env: + DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/funding_frontend_test' + BUNDLER_VERSION: 2.3.11 + DOCKER_TLS_CERTDIR: '' + run: | + sudo apt update + sudo apt-get -yqq install postgresql postgresql-client libpq-dev xvfb unzip libcurl4 libcurl3-gnutls libcurl4-openssl-dev + gem install bundler + gem update --system && gem update bundler + yarn install + bundle install --jobs 4 --retry 3 + RAILS_ENV=test bundle exec rake db:setup + RAILS_ENV=test RAILS_DISABLE_TEST_LOG=true bundle exec rspec + + buildx: + runs-on: ubuntu-latest + needs: [test] + if: github.ref == 'refs/heads/master' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Lowercase the repository name and username + run: echo "REPOSITORY=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + + - name: Build the container image and push it to the registry + uses: docker/build-push-action@v2 + with: + push: true + tags: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }} + file: ./Dockerfile + + deploy: + runs-on: ubuntu-latest + needs: [buildx] + + permissions: + contents: none + + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Lowercase the repository name and username + run: echo "REPOSITORY=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: ${{ env.AZURE_WEBAPP_NAME }} + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + images: '${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }}' From c3fc326073c0ee8690dd493399dd805e7913e654 Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Tue, 3 Oct 2023 14:46:23 -0300 Subject: [PATCH 28/28] Switch from GitHub to Azure container registry --- .github/workflows/azure-deploy-uat.yml | 60 ++++++++++---------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/.github/workflows/azure-deploy-uat.yml b/.github/workflows/azure-deploy-uat.yml index d6ea48040..259dafc49 100644 --- a/.github/workflows/azure-deploy-uat.yml +++ b/.github/workflows/azure-deploy-uat.yml @@ -2,7 +2,6 @@ name: Build, test & deploy to Azure Web App env: AZURE_WEBAPP_NAME: uat - REGISTRY: ghcr.io on: workflow_dispatch: @@ -77,7 +76,7 @@ jobs: RAILS_ENV=test bundle exec rake db:setup RAILS_ENV=test RAILS_DISABLE_TEST_LOG=true bundle exec rspec - buildx: + build-and-deploy: runs-on: ubuntu-latest needs: [test] if: github.ref == 'refs/heads/master' @@ -86,45 +85,32 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + - name: Login via Azure CLI + uses: azure/login@v1 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ github.token }} + creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Lowercase the repository name and username run: echo "REPOSITORY=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} - - name: Build the container image and push it to the registry - uses: docker/build-push-action@v2 + - name: Build and push image + uses: azure/docker-login@v1 with: - push: true - tags: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }} - file: ./Dockerfile - - deploy: - runs-on: ubuntu-latest - needs: [buildx] - - permissions: - contents: none - - environment: - name: 'Production' - url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} - - steps: - - name: Lowercase the repository name and username - run: echo "REPOSITORY=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} - - - name: Deploy to Azure Web App - id: deploy-to-webapp - uses: azure/webapps-deploy@v2 + login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + - run: | + docker build . -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/${{ env.REPOSITORY }}/${{ env.AZURE_WEBAPP_NAME }}:${{ github.sha }} + docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/${{ env.REPOSITORY }}/${{ env.AZURE_WEBAPP_NAME }}:${{ github.sha }} + + - name: Deploy to Azure Container Instance + uses: azure/aci-deploy@v1 with: - app-name: ${{ env.AZURE_WEBAPP_NAME }} - publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} - images: '${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }}' + resource-group: ${{ secrets.RESOURCE_GROUP }} + dns-name-label: ${{ secrets.RESOURCE_GROUP }}${{ github.run_number }} + image: ${{ secrets.REGISTRY_LOGIN_SERVER }}/${{ env.REPOSITORY }}/${{ env.AZURE_WEBAPP_NAME }}:${{ github.sha }} + registry-login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }} + registry-username: ${{ secrets.REGISTRY_USERNAME }} + registry-password: ${{ secrets.REGISTRY_PASSWORD }} + name: ${{ env.AZURE_WEBAPP_NAME }} + location: uksouth