From f7a03ba72f58076d6b4da089e00a042686d60083 Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Thu, 9 Jul 2026 12:54:24 +0100 Subject: [PATCH 01/10] Remove hardcoded id from group spec Remove the hardcoded organisation id, which I *think* but haven't confirmed is the reason for the test being flaky. --- spec/requests/groups_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb index 1adeb417d..2eb9f00e6 100644 --- a/spec/requests/groups_controller_spec.rb +++ b/spec/requests/groups_controller_spec.rb @@ -66,7 +66,7 @@ end end - let(:other_org) { create :organisation, id: 2, slug: "other-org" } + let(:other_org) { create :organisation, slug: "other-org" } let!(:other_org_trial_groups) { create_list :group, 3, organisation: other_org, status: :trial } context "when the user is not a super-admin" do @@ -128,7 +128,7 @@ let!(:other_org_active_groups) { create_list :group, 3, :active, organisation: other_org } let!(:other_org_upgrade_requested_groups) { create_list :group, 3, :upgrade_requested, organisation: other_org } - it "shows groups for organisation in query", :flaky do + it "shows groups for organisation in query" do get groups_url, params: { search: { organisation_id: other_org.id } } expect(assigns(:trial_groups)).to match_array(other_org_trial_groups) expect(assigns(:active_groups)).to match_array(other_org_active_groups) From 7d0c0cc79faabadd9e0b99a11956539c30f4b4a5 Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Thu, 9 Jul 2026 14:01:27 +0100 Subject: [PATCH 02/10] Stop making extra Forms in :form, :with_pages The Form factory trait, :with_pages builds a new form for each page which isn't used. To see the issue, open the rails console compare the number of forms before and after using the factory and trait. ```ruby Form.count f=FactoryBot.create(:form, :with_pages) Form.count ``` You will see six forms created when only one should be made. Instead we build the pages and set the form to nil. Enable logging SQL statements in the console to see how much extra work is involved without this change. ```ruby ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::LogSubscriber.attach_to :active_record ``` --- spec/factories/models/forms.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/factories/models/forms.rb b/spec/factories/models/forms.rb index 1c467ea0c..04a792d68 100644 --- a/spec/factories/models/forms.rb +++ b/spec/factories/models/forms.rb @@ -61,7 +61,7 @@ end pages do - Array.new(pages_count) { association(:page) } + Array.new(pages_count) { build(:page, form: nil) } end after(:build) do |form| @@ -80,7 +80,7 @@ trait :with_text_page do pages do - Array.new(1) { association(:page, answer_type: "text", answer_settings: { input_type: %w[single_line long_text].sample }) } + Array.new(1) { build(:page, answer_type: "text", answer_settings: { input_type: %w[single_line long_text].sample }, form: nil) } end question_section_completed { true } @@ -148,7 +148,7 @@ end pages do - Array.new(pages_count) { association(:page, :with_selection_settings) } + Array.new(pages_count) { build(:page, :with_selection_settings, form: nil) } end after(:build) do |form| From 1f9218767054f6d42765d13eb10a9c4401db5396 Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Tue, 14 Jul 2026 09:56:58 +0100 Subject: [PATCH 03/10] Strip hardcoded org primary keys from factories Remove hardcoded id which can cause deadlocks in the database when two tests run concurrently. We also updates the membership factory to build associations and share one organisation instance between user, group, and added_by. Without this, build(:membership) would compare two in-memory Organisation objects with nil ids (now that the hardcoded id=1 is gone) and fail the 'User and group must have the same organisation' validation. Sharing a user means build(:membership) touches no tables and create(:membership) no longer races two Organisation inserts against the unique slug index. --- spec/factories/models/groups.rb | 4 ++-- spec/factories/models/memberships.rb | 6 +++--- spec/factories/models/organisation_domains.rb | 2 +- spec/factories/models/users.rb | 4 ++-- spec/support/authentication_feature_helpers.rb | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/factories/models/groups.rb b/spec/factories/models/groups.rb index 6a38ffdbd..345c3a766 100644 --- a/spec/factories/models/groups.rb +++ b/spec/factories/models/groups.rb @@ -1,13 +1,13 @@ FactoryBot.define do factory :group do sequence(:name) { |n| "Group #{n}" } - organisation { association :organisation, id: 1, slug: "test-org" } + organisation { association :organisation, slug: "test-org" } creator { association :user, organisation: } status { :trial } external_id { SecureRandom.base58(8) } trait :org_has_org_admin do - organisation { association :organisation, :with_org_admin, id: 1, slug: "test-org" } + organisation { association :organisation, :with_org_admin, slug: "test-org" } end end end diff --git a/spec/factories/models/memberships.rb b/spec/factories/models/memberships.rb index c414e5388..6430f955c 100644 --- a/spec/factories/models/memberships.rb +++ b/spec/factories/models/memberships.rb @@ -1,8 +1,8 @@ FactoryBot.define do factory :membership do - user - group - added_by { association :user } + user { build :user } + group { build :group, organisation: user&.organisation } + added_by { build :user, organisation: user&.organisation } role { :editor } end end diff --git a/spec/factories/models/organisation_domains.rb b/spec/factories/models/organisation_domains.rb index 1ac40d7b5..071834ecb 100644 --- a/spec/factories/models/organisation_domains.rb +++ b/spec/factories/models/organisation_domains.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :organisation_domain do - organisation { association :organisation, id: 1, slug: "test-org" } + organisation { association :organisation, slug: "test-org" } domain { Faker::Internet.domain_name } end end diff --git a/spec/factories/models/users.rb b/spec/factories/models/users.rb index 86f5cd40c..aa2c5bb91 100644 --- a/spec/factories/models/users.rb +++ b/spec/factories/models/users.rb @@ -26,10 +26,10 @@ org_has_signed_mou end - organisation { association :organisation, id: 1, slug: "test-org" } + organisation { association :organisation, slug: "test-org" } trait :org_has_signed_mou do - organisation { association :organisation, :with_signed_mou, id: 1, slug: "test-org" } + organisation { association :organisation, :with_signed_mou, slug: "test-org" } end after(:build) do |user| diff --git a/spec/support/authentication_feature_helpers.rb b/spec/support/authentication_feature_helpers.rb index e053985d5..6d20adcf2 100644 --- a/spec/support/authentication_feature_helpers.rb +++ b/spec/support/authentication_feature_helpers.rb @@ -13,7 +13,7 @@ def login_as(user, opts = {}) end def test_org - @test_org ||= FactoryBot.create(:organisation, :with_signed_mou, id: 1, slug: "test-org") + @test_org ||= FactoryBot.create(:organisation, :with_signed_mou, slug: "test-org") end def super_admin_user From 76edaad76f7918087c41d1d5119aef36909a2376 Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Tue, 14 Jul 2026 09:58:23 +0100 Subject: [PATCH 04/10] Use shared organisations explicitly The organisation factory dedups every default-built organisation onto a single canonical test-org row, so specs could create users (via auth helpers) and groups (via the bare :group factory) and get the same organisation by accident. We want to remove the dedup part of the Organisation factory, so that we can build them without touching the database. This commit changes specs to make the sharing explicit. --- .../form/create_or_edit_a_form_spec.rb | 2 +- .../mou/upgrade_user_changes_mou_spec.rb | 2 +- ...create_a_form_prevent_double_click_spec.rb | 2 +- spec/lib/tasks/users.rake_spec.rb | 4 +-- spec/models/group_spec.rb | 28 +++++++++-------- spec/models/membership_spec.rb | 10 ++++--- spec/models/user_spec.rb | 8 ++--- spec/policies/form_policy_spec.rb | 4 +-- .../api/form_documents_controller_spec.rb | 4 +-- .../forms/unarchive_controller_spec.rb | 4 +-- spec/requests/groups_controller_spec.rb | 4 +-- spec/requests/memberships_controller_spec.rb | 4 +-- spec/requests/routes_controller_spec.rb | 6 ++-- spec/services/group_service_spec.rb | 30 ++++++++++--------- .../mailchimp/list_sync_service_spec.rb | 17 +++++++---- .../organisations_report_service_spec.rb | 13 ++++---- .../organisations/edit.html.erb_spec.rb | 2 -- spec/views/groups/index.html.erb_spec.rb | 2 +- spec/views/groups/show.html.erb_spec.rb | 8 ++--- 19 files changed, 82 insertions(+), 72 deletions(-) diff --git a/spec/features/form/create_or_edit_a_form_spec.rb b/spec/features/form/create_or_edit_a_form_spec.rb index 900ee6b94..f826e4272 100644 --- a/spec/features/form/create_or_edit_a_form_spec.rb +++ b/spec/features/form/create_or_edit_a_form_spec.rb @@ -2,7 +2,7 @@ feature "Create or edit a form", type: :feature do let(:form) { create :form, name: "Apply for a juggling license", created_at: "2024-10-08T07:31:15.762Z" } - let(:group) { create :group, name: "Group 1" } + let(:group) { create :group, name: "Group 1", organisation: standard_user.organisation } before do login_as_standard_user diff --git a/spec/features/mou/upgrade_user_changes_mou_spec.rb b/spec/features/mou/upgrade_user_changes_mou_spec.rb index 5adf9b31b..1f5ddc3cf 100644 --- a/spec/features/mou/upgrade_user_changes_mou_spec.rb +++ b/spec/features/mou/upgrade_user_changes_mou_spec.rb @@ -3,7 +3,7 @@ describe "Assign an organisation to a user with a signed MOU", type: :feature do let(:user) { create :user, name: "Test User", organisation: nil } let!(:mou_signature) { create(:mou_signature, user:, organisation: nil, created_at: Time.zone.parse("September 1, 2023")) } - let(:organisation) { create :organisation } + let(:organisation) { test_org } it "a logged in user can sign an MOU" do login_as_super_admin_user diff --git a/spec/integration/create_a_form_prevent_double_click_spec.rb b/spec/integration/create_a_form_prevent_double_click_spec.rb index 46966c13c..44bf8f774 100644 --- a/spec/integration/create_a_form_prevent_double_click_spec.rb +++ b/spec/integration/create_a_form_prevent_double_click_spec.rb @@ -5,7 +5,7 @@ let(:unwanted_form) { create :form, name: "Test form", created_at: form.created_at + 0.1 } let(:group) do - group = create :group, name: "Test group", creator: user + group = create :group, name: "Test group", creator: user, organisation: user.organisation create :membership, group:, user:, added_by: user group end diff --git a/spec/lib/tasks/users.rake_spec.rb b/spec/lib/tasks/users.rake_spec.rb index aeae16bc2..2b0630161 100644 --- a/spec/lib/tasks/users.rake_spec.rb +++ b/spec/lib/tasks/users.rake_spec.rb @@ -142,7 +142,7 @@ let(:user_in_group) { users.fifth } before do - group = create :group, creator: users.first + group = create :group, creator: users.first, organisation: user_in_group.organisation create :membership, group:, user: user_in_group, added_by: users.first end @@ -181,7 +181,7 @@ end let(:user_in_group) { users.fifth } - let(:group_with_user) { create :group, creator: users.first } + let(:group_with_user) { create :group, creator: users.first, organisation: user_in_group.organisation } before do create :membership, group: group_with_user, user: user_in_group, added_by: users.first diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 707de7e65..95f37228d 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -126,9 +126,10 @@ describe "associations" do it "destroys associated memberships" do - group = create :group - user = create :user - added_by = create :user + organisation = create :organisation + group = create :group, organisation: organisation + user = create :user, organisation: organisation + added_by = create :user, organisation: organisation create(:membership, group:, user:, added_by:) expect { group.destroy }.to change(Membership, :count).by(-1) @@ -151,11 +152,12 @@ describe "#memberships" do describe "#ordered" do it "orders the membership records by name of the associated user" do - group = create :group + organisation = create :organisation + group = create :group, organisation: organisation users = [ - create(:user, name: "Barbara User"), - create(:user, name: "Alfred User"), - create(:user, name: "Charlie User"), + create(:user, name: "Barbara User", organisation: organisation), + create(:user, name: "Alfred User", organisation: organisation), + create(:user, name: "Charlie User", organisation: organisation), ] users.each do |user| @@ -260,9 +262,10 @@ describe "scopes" do describe ".for_user" do it "returns groups that the user is a member of" do - user = create :user - group1 = create :group - group2 = create :group + organisation = create :organisation + user = create :user, organisation: organisation + group1 = create :group, organisation: organisation + group2 = create :group, organisation: organisation create :group create :membership, user:, group: group1 create :membership, user:, group: group2 @@ -278,8 +281,9 @@ end it "does not return groups that the user is not a member of" do - user = create :user - group1 = create :group + organisation = create :organisation + user = create :user, organisation: organisation + group1 = create :group, organisation: organisation create :group create :group create :membership, user:, group: group1 diff --git a/spec/models/membership_spec.rb b/spec/models/membership_spec.rb index 8edcf97d6..e1a6edde9 100644 --- a/spec/models/membership_spec.rb +++ b/spec/models/membership_spec.rb @@ -35,16 +35,18 @@ end it "is invalid if the user is already a member of the group" do - user = create :user - group = create :group + organisation = create :organisation + user = create :user, organisation: organisation + group = create :group, organisation: organisation create(:membership, user:, group:) membership = build(:membership, user:, group:) expect(membership).not_to be_valid end it "raises a DB error if the user is already a member of the group" do - user = create :user - group = create :group + organisation = create :organisation + user = create :user, organisation: organisation + group = create :group, organisation: organisation create(:membership, user:, group:) membership = build(:membership, user:, group:) expect { membership.save(validate: false) }.to raise_error(ActiveRecord::RecordNotUnique) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6a436647d..c0086f6c8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -128,7 +128,7 @@ describe "associations" do it "destroys associated memberships" do user = create(:user) - group = create(:group) + group = create(:group, organisation: user.organisation) membership = create(:membership, user:, group:) user.destroy! @@ -364,7 +364,7 @@ it "returns false when user had an organisation" do user = create(:user) - user.update!(organisation: build(:organisation)) + user.update!(organisation: create(:organisation)) expect(user).not_to be_given_organisation end end @@ -380,9 +380,9 @@ context "when the user is an organisation admin" do it "returns true" do - user = create(:organisation_admin_user) + user = create(:organisation_admin_user, organisation: create(:organisation, :with_signed_mou)) - expect(user.can_administer_org?(organisation)).to be(true) + expect(user.can_administer_org?(user.organisation)).to be(true) end end diff --git a/spec/policies/form_policy_spec.rb b/spec/policies/form_policy_spec.rb index 6227f1c3e..509a707ae 100644 --- a/spec/policies/form_policy_spec.rb +++ b/spec/policies/form_policy_spec.rb @@ -184,13 +184,13 @@ end context "when the user is a super admin" do - let(:user) { build :super_admin_user } + let(:user) { build :super_admin_user, organisation: } it { is_expected.to permit_action(:can_administer_group) } end context "when the user is an organisation admin" do - let(:user) { build :organisation_admin_user } + let(:user) { build :organisation_admin_user, organisation: } it { is_expected.to permit_action(:can_administer_group) } end diff --git a/spec/requests/api/form_documents_controller_spec.rb b/spec/requests/api/form_documents_controller_spec.rb index 62109997b..0711e0fa9 100644 --- a/spec/requests/api/form_documents_controller_spec.rb +++ b/spec/requests/api/form_documents_controller_spec.rb @@ -155,12 +155,12 @@ context "when the form exists" do let(:form) { create(:form) } let(:group) { create(:group, organisation: test_org) } - let(:group_admin) { create(:user) } + let(:group_admin) { create(:user, organisation: test_org) } before do organisation_admin_user create(:membership, user: group_admin, group:, role: :group_admin) - create(:membership, user: create(:user), group:, role: :editor) + create(:membership, user: create(:user, organisation: test_org), group:, role: :editor) group.group_forms.create!(form_id: form.id) end diff --git a/spec/requests/forms/unarchive_controller_spec.rb b/spec/requests/forms/unarchive_controller_spec.rb index 085a22c7b..064c880d5 100644 --- a/spec/requests/forms/unarchive_controller_spec.rb +++ b/spec/requests/forms/unarchive_controller_spec.rb @@ -27,7 +27,7 @@ end context "when current user does not belong to the forms group" do - let(:user) { build :user } + let(:user) { build :user, organisation: standard_user.organisation } it "is forbidden" do expect(response).to have_http_status(:forbidden) @@ -119,7 +119,7 @@ end context "when current user does not belong to the forms group" do - let(:user) { build :user } + let(:user) { build :user, organisation: standard_user.organisation } it "is forbidden" do post(unarchive_create_path(form_id: form.id), params: form_params) diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb index 2eb9f00e6..f638093fc 100644 --- a/spec/requests/groups_controller_spec.rb +++ b/spec/requests/groups_controller_spec.rb @@ -544,7 +544,7 @@ end describe "GET /delete" do - let!(:group) { create :group } + let!(:group) { create :group, organisation: test_org } context "when user is a super admin" do before do @@ -598,7 +598,7 @@ end describe "DELETE /destroy" do - let!(:group) { create :group, name: "Test Group" } + let!(:group) { create :group, name: "Test Group", organisation: test_org } context "when user is a super admin" do before do diff --git a/spec/requests/memberships_controller_spec.rb b/spec/requests/memberships_controller_spec.rb index 5ef0e95d7..15543c3d9 100644 --- a/spec/requests/memberships_controller_spec.rb +++ b/spec/requests/memberships_controller_spec.rb @@ -1,8 +1,8 @@ require "rails_helper" describe "/memberships", type: :request do - let(:user) { create(:user) } - let(:group) { create(:group) } + let(:user) { create(:user, organisation: standard_user.organisation) } + let(:group) { create(:group, organisation: standard_user.organisation) } let(:logged_in_user_role) { :group_admin } before do diff --git a/spec/requests/routes_controller_spec.rb b/spec/requests/routes_controller_spec.rb index 55dd903bd..5e1c3cb7f 100644 --- a/spec/requests/routes_controller_spec.rb +++ b/spec/requests/routes_controller_spec.rb @@ -3,7 +3,7 @@ RSpec.describe RoutesController, type: :request do let(:form) { create(:form, :with_group, group:) } let(:membership) { create :membership, group:, user: standard_user } - let(:group) { create(:group, multiple_branches_enabled: true) } + let(:group) { create(:group, multiple_branches_enabled: true, organisation: standard_user.organisation) } before do membership @@ -31,7 +31,7 @@ end context "when the multiple_branches feature is not enabled" do - let(:group) { create(:group, multiple_branches_enabled: false) } + let(:group) { create(:group, multiple_branches_enabled: false, organisation: standard_user.organisation) } it "returns a 404" do get routes_path(form.id) @@ -89,7 +89,7 @@ end context "when the multiple_branches feature is not enabled" do - let(:group) { create(:group, multiple_branches_enabled: false) } + let(:group) { create(:group, multiple_branches_enabled: false, organisation: standard_user.organisation) } it "returns a 404" do post routes_path(form.id), params: valid_params diff --git a/spec/services/group_service_spec.rb b/spec/services/group_service_spec.rb index 510552053..d142f42b1 100644 --- a/spec/services/group_service_spec.rb +++ b/spec/services/group_service_spec.rb @@ -5,16 +5,17 @@ described_class.new(group:, current_user:, host:) end - let(:group) { create :group } - let(:current_user) { create :user } + let(:organisation) { create :organisation } + let(:group) { create :group, organisation: } + let(:current_user) { create :user, organisation: } let(:host) { "example.net" } describe "#upgrade_group" do - let(:first_group_admin_user) { create :user } - let(:second_group_admin_user) { create :user } - let(:editor_user) { create :user } + let(:first_group_admin_user) { create :user, organisation: } + let(:second_group_admin_user) { create :user, organisation: } + let(:editor_user) { create :user, organisation: } let(:group) do - create(:group).tap do |group| + create(:group, organisation:).tap do |group| create(:membership, user: first_group_admin_user, group:, role: :group_admin) create(:membership, user: second_group_admin_user, group:, role: :group_admin) create(:membership, user: editor_user, group:, role: :editor) @@ -63,11 +64,11 @@ end describe "#reject_upgrade" do - let(:first_group_admin_user) { create :user } - let(:second_group_admin_user) { create :user } - let(:editor_user) { create :user } + let(:first_group_admin_user) { create :user, organisation: } + let(:second_group_admin_user) { create :user, organisation: } + let(:editor_user) { create :user, organisation: } let(:group) do - create(:group, status: :upgrade_requested).tap do |group| + create(:group, status: :upgrade_requested, organisation:).tap do |group| create(:membership, user: first_group_admin_user, group:, role: :group_admin) create(:membership, user: second_group_admin_user, group:, role: :group_admin) create(:membership, user: editor_user, group:, role: :editor) @@ -119,11 +120,12 @@ end describe "#request_upgrade" do - let!(:first_organisation_admin_user) { create :organisation_admin_user } - let!(:second_organisation_admin_user) { create :organisation_admin_user } - let(:editor_user) { create :user } + let(:organisation) { create :organisation, :with_signed_mou } + let!(:first_organisation_admin_user) { create :organisation_admin_user, organisation: } + let!(:second_organisation_admin_user) { create :organisation_admin_user, organisation: } + let(:editor_user) { create :user, organisation: } let(:group) do - create(:group).tap do |group| + create(:group, organisation:).tap do |group| create(:membership, user: editor_user, group:, role: :editor) create(:membership, user: current_user, group:, role: :group_admin) end diff --git a/spec/services/mailchimp/list_sync_service_spec.rb b/spec/services/mailchimp/list_sync_service_spec.rb index 9f2ed1100..8bfb12d76 100644 --- a/spec/services/mailchimp/list_sync_service_spec.rb +++ b/spec/services/mailchimp/list_sync_service_spec.rb @@ -22,8 +22,11 @@ let(:mou_signer_and_organisation_admin_with_access) { users_with_access.third } before do + signed_mou_org = create(:organisation) + signed_mou_org_user = create(:user, organisation: signed_mou_org, has_access: false) + create(:mou_signature, organisation: signed_mou_org, user: signed_mou_org_user) users_with_access.each do |email| - create :user, email:, has_access: true + create :user, email:, has_access: true, organisation: signed_mou_org end users_without_access.each do |email| @@ -69,17 +72,19 @@ end describe "#mou_signers" do - let(:user_with_access_and_mou) { create(:user, email: "mou_user@example.com") } - let(:user_with_access_admin) { create(:user, email: "admin_user@example.com") } - let(:user_without_access) { create(:user, email: "inactive_user@example.com", has_access: false) } - let(:user_access_and_admin_with_mou) { create(:user, email: "admin_mou_user@example.com") } - + let(:signed_mou_org) { create(:organisation) } + let(:user_with_access_and_mou) { create(:user, email: "mou_user@example.com", organisation: signed_mou_org) } + let(:user_with_access_admin) { create(:user, email: "admin_user@example.com", organisation: signed_mou_org) } + let(:user_without_access) { create(:user, email: "inactive_user@example.com", has_access: false, organisation: signed_mou_org) } + let(:user_access_and_admin_with_mou) { create(:user, email: "admin_mou_user@example.com", organisation: signed_mou_org) } let(:non_crown_org) { create(:organisation, id: 2, slug: "non-crown-org") } let(:user_that_signed_non_crown_agreement) { create(:user, organisation: non_crown_org) } let(:not_org_admin_that_signed_non_crown_agreement) { create(:user, organisation: non_crown_org) } let(:org_admin_for_non_crown_org) { create(:user, organisation: non_crown_org) } + let!(:signed_mou_org_user) { create(:user, organisation: signed_mou_org, has_access: false) } before do + create(:mou_signature, organisation: signed_mou_org, user: signed_mou_org_user) create(:mou_signature, user: user_with_access_and_mou) create(:mou_signature, user: user_access_and_admin_with_mou) create(:mou_signature, agreement_type: :non_crown, user: user_that_signed_non_crown_agreement) diff --git a/spec/services/reports/organisations_report_service_spec.rb b/spec/services/reports/organisations_report_service_spec.rb index 09db11934..1d9af260b 100644 --- a/spec/services/reports/organisations_report_service_spec.rb +++ b/spec/services/reports/organisations_report_service_spec.rb @@ -19,13 +19,12 @@ context "with organisations" do it "returns the correct rows" do - create(:organisation, name: "Ministry of tests", slug: "ministry-of-tests", organisation_domains: [ - create(:organisation_domain, domain: "ministry-of-tests.gov.uk"), - create(:organisation_domain, domain: "mot.gov.uk"), - ]) - create(:organisation, name: "Department of juggling", slug: "department-of-juggling", organisation_domains: [ - create(:organisation_domain, domain: "juggling.gov.uk"), - ]) + create(:organisation, name: "Test Org", slug: "test-org") + ministry_org = create(:organisation, name: "Ministry of tests", slug: "ministry-of-tests") + create(:organisation_domain, domain: "ministry-of-tests.gov.uk", organisation: ministry_org) + create(:organisation_domain, domain: "mot.gov.uk", organisation: ministry_org) + juggling_org = create(:organisation, name: "Department of juggling", slug: "department-of-juggling") + create(:organisation_domain, domain: "juggling.gov.uk", organisation: juggling_org) expect(service.organisation_domains_report[:rows]).to contain_exactly( [{ text: "Test Org" }, { text: "test-org" }, { text: "" }], diff --git a/spec/views/account/organisations/edit.html.erb_spec.rb b/spec/views/account/organisations/edit.html.erb_spec.rb index e8e30dccc..b9e866f79 100644 --- a/spec/views/account/organisations/edit.html.erb_spec.rb +++ b/spec/views/account/organisations/edit.html.erb_spec.rb @@ -65,9 +65,7 @@ context "when there are closed organisations" do before do - create(:organisation, slug: "test-org") create(:organisation, slug: "closed-org", closed: true) - create(:organisation, slug: "department-for-testing", name: "Department for Testing") render end diff --git a/spec/views/groups/index.html.erb_spec.rb b/spec/views/groups/index.html.erb_spec.rb index adbb1d494..f336cd5a0 100644 --- a/spec/views/groups/index.html.erb_spec.rb +++ b/spec/views/groups/index.html.erb_spec.rb @@ -108,7 +108,7 @@ end context "when the user is a super admin" do - let(:current_user) { build :super_admin_user } + let(:current_user) { create :super_admin_user } let(:search_input) { OrganisationSearchInput.new({ organisation_id: current_user.organisation_id }) } let(:organisation) { current_user.organisation } diff --git a/spec/views/groups/show.html.erb_spec.rb b/spec/views/groups/show.html.erb_spec.rb index f9b871b23..adccdf0eb 100644 --- a/spec/views/groups/show.html.erb_spec.rb +++ b/spec/views/groups/show.html.erb_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "groups/show", type: :view do let(:current_user) { create :user, :org_has_signed_mou } let(:forms) { [] } - let(:group) { create :group, name: "My Group" } + let(:group) { create :group, name: "My Group", organisation: current_user.organisation } let(:form_list_presenter) { FormListPresenter.call(forms:, group:) } @@ -150,7 +150,7 @@ end context "when the group is a trial group" do - let(:group) { create :group, :trial, name: "trial group" } + let(:group) { create :group, :trial, name: "trial group", organisation: current_user.organisation } it "renders the status of the group" do expect(rendered).to have_css ".govuk-caption-l", text: t("groups.status_caption.trial") @@ -354,7 +354,7 @@ end context "when the group has an upgrade requested" do - let(:group) { create :group, :upgrade_requested } + let(:group) { create :group, :upgrade_requested, organisation: current_user.organisation } it "have the caption trial group" do expect(rendered).to have_css ".govuk-caption-l", text: "Trial group" @@ -370,7 +370,7 @@ end context "when the group is an active group" do - let(:group) { create :group, :active, name: "Active group" } + let(:group) { create :group, :active, name: "Active group", organisation: current_user.organisation } it "renders the status of the group" do expect(rendered).to have_css ".govuk-caption-l", text: t("groups.status_caption.active") From 6edb39faece0f1ed5749ef88e188b45cb49f3a8f Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Tue, 14 Jul 2026 10:01:38 +0100 Subject: [PATCH 05/10] Remove org factory's dedup Our specs used to rely on the fact that orgs with the same slug would be identical. Now that we've removed that pattern from the specs and made it explicit, we can remove code from the factory that was ensuring that all orgs created with the same slug were identical. This caused the DB to be used, even when using build as the select would always be called. Instead, we give organisations a unique sequence-based slug. --- spec/factories/models/groups.rb | 4 ++-- spec/factories/models/organisation_domains.rb | 2 +- spec/factories/models/organisations.rb | 6 +----- spec/factories/models/users.rb | 4 ++-- spec/input_objects/forms/group_select_spec.rb | 13 ++++--------- spec/integration/organisation_factory_spec.rb | 7 ------- spec/models/organisation_spec.rb | 13 ------------- 7 files changed, 10 insertions(+), 39 deletions(-) diff --git a/spec/factories/models/groups.rb b/spec/factories/models/groups.rb index 345c3a766..2d88b8573 100644 --- a/spec/factories/models/groups.rb +++ b/spec/factories/models/groups.rb @@ -1,13 +1,13 @@ FactoryBot.define do factory :group do sequence(:name) { |n| "Group #{n}" } - organisation { association :organisation, slug: "test-org" } + organisation { association :organisation } creator { association :user, organisation: } status { :trial } external_id { SecureRandom.base58(8) } trait :org_has_org_admin do - organisation { association :organisation, :with_org_admin, slug: "test-org" } + organisation { association :organisation, :with_org_admin } end end end diff --git a/spec/factories/models/organisation_domains.rb b/spec/factories/models/organisation_domains.rb index 071834ecb..df65d54fc 100644 --- a/spec/factories/models/organisation_domains.rb +++ b/spec/factories/models/organisation_domains.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :organisation_domain do - organisation { association :organisation, slug: "test-org" } + organisation { association :organisation } domain { Faker::Internet.domain_name } end end diff --git a/spec/factories/models/organisations.rb b/spec/factories/models/organisations.rb index edf89a81f..23e9f52aa 100644 --- a/spec/factories/models/organisations.rb +++ b/spec/factories/models/organisations.rb @@ -1,15 +1,11 @@ FactoryBot.define do factory :organisation do govuk_content_id { nil } - slug { "test-org" } + sequence(:slug) { |n| "test-org-#{n}" } name { slug.titleize } abbreviation { name.split.collect(&:chr).join } internal { false } - initialize_with do - Organisation.create_with(govuk_content_id:, name:).find_or_initialize_by(slug:) - end - trait :with_signed_mou do after(:build) do |organisation| organisation.mou_signatures << (create :mou_signature_for_organisation, organisation:) diff --git a/spec/factories/models/users.rb b/spec/factories/models/users.rb index aa2c5bb91..97322ce0f 100644 --- a/spec/factories/models/users.rb +++ b/spec/factories/models/users.rb @@ -26,10 +26,10 @@ org_has_signed_mou end - organisation { association :organisation, slug: "test-org" } + organisation { association :organisation } trait :org_has_signed_mou do - organisation { association :organisation, :with_signed_mou, slug: "test-org" } + organisation { association :organisation, :with_signed_mou } end after(:build) do |user| diff --git a/spec/input_objects/forms/group_select_spec.rb b/spec/input_objects/forms/group_select_spec.rb index da2829614..e5dfb70db 100644 --- a/spec/input_objects/forms/group_select_spec.rb +++ b/spec/input_objects/forms/group_select_spec.rb @@ -31,17 +31,12 @@ end context "when the organisation admin user is logged in" do - let(:org_admin) { build(:organisation_admin_user) } - - it "returns only groups in the user's organisation" do - create_list(:group, 3) do |g| - g.organisation = group.organisation - g.save! - end - create(:group, organisation: build(:organisation)) # Group 5 + it "returns only groups in the current group's organisation" do + create_list(:group, 3, organisation: group.organisation) + create(:group, organisation: build(:organisation)) # Group in a different organisation expect(group_select.groups).not_to include(group) - expect(group_select.groups.count).to eq(4) + expect(group_select.groups.count).to eq(3) end end diff --git a/spec/integration/organisation_factory_spec.rb b/spec/integration/organisation_factory_spec.rb index 02ed18bc2..ee7683f2a 100644 --- a/spec/integration/organisation_factory_spec.rb +++ b/spec/integration/organisation_factory_spec.rb @@ -1,13 +1,6 @@ require "rails_helper" RSpec.describe "organisation factory" do - it "does not duplicate organisations with same slug" do - org1 = create :organisation, slug: "org-slug" - org2 = create :organisation, slug: "org-slug" - - expect(org1.id).to eq org2.id - end - it "does not persist the organisation" do org = build :organisation diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index 71b0ee58b..49854efdd 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -9,19 +9,6 @@ }.to raise_error ActiveRecord::RecordNotUnique end - describe "factory" do - it "does not create organisation if already exists" do - existing_organisation = create(:organisation, slug: "duplicate-org") - new_organisation = nil - - expect { - new_organisation = create(:organisation, slug: "duplicate-org") - }.not_to raise_error - - expect(new_organisation).to eq(existing_organisation) - end - end - describe "versioning", :versioning do it "enables paper trail" do expect(described_class.new).to be_versioned From cdb59ee57a409795af6ad9c4fd39bfacf39d253d Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Fri, 10 Jul 2026 12:18:42 +0100 Subject: [PATCH 06/10] Make Form:with_group trait use build strategy Using create in a factory means even factories built with 'build' or 'build_stubbed' will hit the database. --- spec/factories/models/forms.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/factories/models/forms.rb b/spec/factories/models/forms.rb index 04a792d68..a83ccd3d4 100644 --- a/spec/factories/models/forms.rb +++ b/spec/factories/models/forms.rb @@ -29,11 +29,11 @@ trait :with_group do transient do - group { nil } + group { association :group } end after(:build) do |form, evaluator| - g = evaluator.group || FactoryBot.create(:group) + g = evaluator.group form.instance_variable_set(:@associated_group, g) form.define_singleton_method(:group) { g } end From 113198514f34581148c08502ba4a20f0f0118f7d Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Fri, 10 Jul 2026 15:06:39 +0100 Subject: [PATCH 07/10] Fix User#requires_organisation? User's belong to Organisations except when they first sign up. We have validation that ensures that once a user has an organisation they move to a new organisation but can't be without one. The existing validation accessed the database column directly. This meant if you build a User and add an Organisation to it, it will be invalid. Instead, we add a check to only run the validation if there is no Organisation yet. --- app/models/user.rb | 2 +- spec/models/user_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index eeca8e662..d2692b23d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -159,6 +159,6 @@ def requires_name? end def requires_organisation? - organisation_id_was.present? || role_changed?(to: :organisation_admin) + organisation.blank? && (organisation_id_was.present? || role_changed?(to: :organisation_admin)) end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c0086f6c8..b0b4f6132 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -58,6 +58,16 @@ end end + context "when reassigning to a new, unsaved organisation" do + let(:user) { create(:user, organisation: create(:organisation)) } + + it "is valid even though the new organisation is not yet persisted" do + user.organisation = build(:organisation) + + expect(user).to be_valid + end + end + context "when updating name" do let(:user) { create :user, :with_no_name } From 39cf92d2c78eed93733c556070c5a749fce2b3fd Mon Sep 17 00:00:00 2001 From: Tom Iles Date: Mon, 13 Jul 2026 09:40:10 +0100 Subject: [PATCH 08/10] Make mou_signature and org factories use build The MOU signature factory was using create to make a user, which meant using build would still use the database. This caused us to use create in Org, too. We needed to use create to make the user valid because of the validation on User. Now we've fixed that, we can use build instead. We also change the org factory. Because we are now using build instead of create for users, the view specs failed when using a path helper that relied on the user's id. We fix that by passing build_stubbed(:user) in the specs so there is an id. --- spec/factories/models/mou_signatures.rb | 4 ++-- spec/factories/models/organisations.rb | 4 ++-- spec/views/mou_signatures/index.html.erb_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/factories/models/mou_signatures.rb b/spec/factories/models/mou_signatures.rb index 2d3dd1350..a7222cd3d 100644 --- a/spec/factories/models/mou_signatures.rb +++ b/spec/factories/models/mou_signatures.rb @@ -1,13 +1,13 @@ FactoryBot.define do factory :mou_signature do - user { create(:user) } + user { build(:user) } organisation { user.organisation } agreement_type { :crown } created_at { Time.zone.now } factory :mou_signature_for_organisation do organisation - user { create(:user, organisation:) } + user { build(:user, organisation:) } end end end diff --git a/spec/factories/models/organisations.rb b/spec/factories/models/organisations.rb index 23e9f52aa..197d57d5d 100644 --- a/spec/factories/models/organisations.rb +++ b/spec/factories/models/organisations.rb @@ -8,7 +8,7 @@ trait :with_signed_mou do after(:build) do |organisation| - organisation.mou_signatures << (create :mou_signature_for_organisation, organisation:) + organisation.mou_signatures << build(:mou_signature_for_organisation, organisation:) end end @@ -16,7 +16,7 @@ with_signed_mou after(:build) do |organisation| - organisation.users << create(:organisation_admin_user, organisation:) + organisation.users << build(:organisation_admin_user, organisation:) end end end diff --git a/spec/views/mou_signatures/index.html.erb_spec.rb b/spec/views/mou_signatures/index.html.erb_spec.rb index 623e036f5..2fb375061 100644 --- a/spec/views/mou_signatures/index.html.erb_spec.rb +++ b/spec/views/mou_signatures/index.html.erb_spec.rb @@ -3,8 +3,8 @@ describe "mou_signatures/index.html.erb" do let(:mou_signatures) do [ - build(:mou_signature, agreement_type: :crown, created_at: Time.zone.parse("October 12, 2023")), - build(:mou_signature, agreement_type: :non_crown, created_at: Time.zone.parse("October 13, 2023")), + build(:mou_signature, user: build_stubbed(:user), agreement_type: :crown, created_at: Time.zone.parse("October 12, 2023")), + build(:mou_signature, user: build_stubbed(:user), agreement_type: :non_crown, created_at: Time.zone.parse("October 13, 2023")), ] end From b9bd4dadc90cb2393b5ddb0822d85f201cd502c0 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Tue, 14 Jul 2026 16:03:32 +0100 Subject: [PATCH 09/10] Remove id from user spec We are building a user, not creating one. It doesn't need an id in this spec for the comparison to work. Co-authored-by: Sean Rankine --- spec/models/user_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b0b4f6132..5429038ee 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -424,7 +424,7 @@ context "and the user's organisation is not the same as given" do it "returns false" do user = create(:organisation_admin_user) - other_org = build(:organisation, id: 2) + other_org = build(:organisation) expect(user.is_organisations_admin?(other_org)).to be(false) end From dd277be6f87939694534842ce9f7bc0624cd03c8 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Tue, 14 Jul 2026 16:06:23 +0100 Subject: [PATCH 10/10] Remove hardcoded id from mailchimp spec The org here doesn't need a hardcoded id, which may cause issues. Co-authored-by: Sean Rankine --- spec/services/mailchimp/list_sync_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/mailchimp/list_sync_service_spec.rb b/spec/services/mailchimp/list_sync_service_spec.rb index 8bfb12d76..534b11b2c 100644 --- a/spec/services/mailchimp/list_sync_service_spec.rb +++ b/spec/services/mailchimp/list_sync_service_spec.rb @@ -77,7 +77,7 @@ let(:user_with_access_admin) { create(:user, email: "admin_user@example.com", organisation: signed_mou_org) } let(:user_without_access) { create(:user, email: "inactive_user@example.com", has_access: false, organisation: signed_mou_org) } let(:user_access_and_admin_with_mou) { create(:user, email: "admin_mou_user@example.com", organisation: signed_mou_org) } - let(:non_crown_org) { create(:organisation, id: 2, slug: "non-crown-org") } + let(:non_crown_org) { create(:organisation, slug: "non-crown-org") } let(:user_that_signed_non_crown_agreement) { create(:user, organisation: non_crown_org) } let(:not_org_admin_that_signed_non_crown_agreement) { create(:user, organisation: non_crown_org) } let(:org_admin_for_non_crown_org) { create(:user, organisation: non_crown_org) }