diff --git a/config/initializers/sti_preload.rb b/config/initializers/sti_preload.rb
new file mode 100644
index 0000000..53e0fe0
--- /dev/null
+++ b/config/initializers/sti_preload.rb
@@ -0,0 +1,11 @@
+# In production and CI, Rails eager loads every Allocation subclass for us.
+# Everywhere else (development and local `bin/rails test`) eager loading is off,
+# so this grandchild subclass isn't registered as a descendant when
+# Scenario#ongoing_allocations builds its `type IN (...)` query and its rows get
+# filtered out. Referencing it in a to_prepare block keeps it loaded.
+# See https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#single-table-inheritance
+unless Rails.env.production? || ENV["CI"].present?
+ Rails.application.config.to_prepare do
+ Allocation::GreatestCommunityNeed
+ end
+end
diff --git a/test/controllers/allocations_controller_test.rb b/test/controllers/allocations_controller_test.rb
index 7fe24f0..111b352 100644
--- a/test/controllers/allocations_controller_test.rb
+++ b/test/controllers/allocations_controller_test.rb
@@ -50,6 +50,24 @@ class AllocationsControllerTest < ActionDispatch::IntegrationTest
assert_empty allocation.reload.preference_categories
end
+ test "renders the always-present Greatest Community Need card" do
+ # one_arlington already has the greatest_need fixture allocation.
+ get scenario_url(@scenario)
+ assert_response :success
+ assert_match Allocation::GreatestCommunityNeed::DESCRIPTION, response.body
+ end
+
+ test "rejects a duplicate Greatest Community Need allocation" do
+ # one_arlington already has the greatest_need fixture allocation.
+ assert_no_difference -> { @scenario.allocations.count } do
+ post scenario_allocations_url(@scenario), params: {
+ allocation: { type: "Allocation::GreatestCommunityNeed", percentage: 20 }
+ }
+ end
+ assert_redirected_to scenario_path(@scenario)
+ assert flash[:alert].present?
+ end
+
test "creates a one time allocation" do
assert_difference -> { @scenario.allocations.count }, 1 do
post scenario_allocations_url(@scenario), params: {
@@ -113,9 +131,17 @@ class AllocationsControllerTest < ActionDispatch::IntegrationTest
test "destroys an allocation" do
assert_difference -> { @scenario.allocations.count }, -1 do
+ delete scenario_allocation_url(@scenario, allocations(:education_grant))
+ end
+ assert_redirected_to scenario_path(@scenario)
+ end
+
+ test "cannot delete the Greatest Community Need allocation" do
+ assert_no_difference -> { @scenario.allocations.count } do
delete scenario_allocation_url(@scenario, allocations(:greatest_need))
end
assert_redirected_to scenario_path(@scenario)
+ assert flash[:alert].present?
end
test "cannot add an allocation to a scenario you do not own" do
@@ -140,7 +166,7 @@ class AllocationsControllerTest < ActionDispatch::IntegrationTest
test "admin can destroy an allocation on another user's scenario in the same org" do
sign_in_as users(:admin)
assert_difference -> { @scenario.allocations.count }, -1 do
- delete scenario_allocation_url(@scenario, allocations(:greatest_need))
+ delete scenario_allocation_url(@scenario, allocations(:education_grant))
end
assert_redirected_to scenario_path(@scenario)
end
diff --git a/test/fixtures/allocations.yml b/test/fixtures/allocations.yml
index 004ec68..d299b0d 100644
--- a/test/fixtures/allocations.yml
+++ b/test/fixtures/allocations.yml
@@ -1,7 +1,7 @@
-# greatest_need uses the free-text option fallback (no category).
+# greatest_need is the dedicated Greatest Community Need STI subclass.
greatest_need:
scenario: one_arlington
- type: Allocation::Ongoing
+ type: Allocation::GreatestCommunityNeed
option: Greatest Community Need
percentage: 30
diff --git a/test/models/allocation_test.rb b/test/models/allocation_test.rb
index 920ddd5..e38214d 100644
--- a/test/models/allocation_test.rb
+++ b/test/models/allocation_test.rb
@@ -64,4 +64,29 @@ class AllocationTest < ActiveSupport::TestCase
assert_not allocations(:greatest_need).one_time?
assert allocations(:education_grant).one_time?
end
+
+ test "greatest_community_need? is true only for the dedicated subclass" do
+ assert allocations(:greatest_need).greatest_community_need?
+ assert_instance_of Allocation::GreatestCommunityNeed, allocations(:greatest_need)
+
+ # A plain ongoing allocation that merely shares the label is NOT the subclass.
+ look_alike = @scenario.ongoing_allocations.new(percentage: 10, option: "Greatest Community Need")
+ assert_not look_alike.greatest_community_need?
+ end
+
+ test "only one Greatest Community Need allocation is allowed per scenario" do
+ # greatest_need fixture already occupies the slot in this scenario.
+ duplicate = Allocation::GreatestCommunityNeed.new(scenario: @scenario)
+ assert_not duplicate.valid?
+ assert_includes duplicate.errors[:base], "Greatest Community Need has already been added"
+
+ assert Allocation::GreatestCommunityNeed.new(scenario: scenarios(:two_boston)).valid?
+ end
+
+ test "Greatest Community Need defaults to 0% with a fixed label and needs no category or option" do
+ gcn = Allocation::GreatestCommunityNeed.new(scenario: scenarios(:two_boston))
+ assert_equal 0, gcn.percentage
+ assert_equal "Greatest Community Need", gcn.display_label
+ assert gcn.valid?
+ end
end
diff --git a/test/models/scenario_test.rb b/test/models/scenario_test.rb
index bf3f4e2..5fc66ef 100644
--- a/test/models/scenario_test.rb
+++ b/test/models/scenario_test.rb
@@ -36,4 +36,15 @@ class ScenarioTest < ActiveSupport::TestCase
scenario.destroy
end
end
+
+ test "new scenarios are created with a Greatest Community Need allocation at 0%" do
+ scenario = users(:one).scenarios.create!(
+ organization: organizations(:arlington), name: "Fresh plan", total_giving_amount: 1000)
+
+ gcn = scenario.greatest_community_need
+ assert gcn.present?
+ assert gcn.greatest_community_need?
+ assert_equal 0, gcn.percentage
+ assert_includes scenario.ongoing_allocations, gcn
+ end
end