From 9cc91ece1f5d3127fa61398f2ddf7137a6fd3d89 Mon Sep 17 00:00:00 2001 From: Mikaal Naik Date: Tue, 8 Sep 2026 16:29:06 -0400 Subject: [PATCH 1/2] Retitle two survey sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Five direct questions" describes the form's shape rather than what it asks about; "Five decisions facing the next council" says the subject. "A few more, if you have time" framed a third of the questionnaire as optional filler when it carries the trade-off questions, so it becomes "Priorities and trade-offs". The second title appears in both surveys — the resident set asks the same block — and both are renamed, since leaving them apart would put two names on one section across the pair they are meant to be read against each other. Titles are denormalized onto every question in the step, so this touches 25 rows' worth of JSON and no other field. Co-Authored-By: Claude Opus 5 (1M context) --- .../toronto_2026_candidate_questionnaire.json | 30 +++++++++---------- .../toronto_2026_city_priorities.json | 20 ++++++------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/db/seeds/elections/toronto_2026_candidate_questionnaire.json b/db/seeds/elections/toronto_2026_candidate_questionnaire.json index 72cb22d..421c270 100644 --- a/db/seeds/elections/toronto_2026_candidate_questionnaire.json +++ b/db/seeds/elections/toronto_2026_candidate_questionnaire.json @@ -30,7 +30,7 @@ { "question_id": "housing_as_of_right", "step_id": "direct-questions", - "step_title": "Five direct questions", + "step_title": "Five decisions facing the next council", "step_intro": "The five we are asking every candidate, and every resident, to answer.", "step_position": 1, "position": 0, @@ -61,7 +61,7 @@ { "question_id": "encampment_removal", "step_id": "direct-questions", - "step_title": "Five direct questions", + "step_title": "Five decisions facing the next council", "step_intro": "The five we are asking every candidate, and every resident, to answer.", "step_position": 1, "position": 1, @@ -92,7 +92,7 @@ { "question_id": "road_pricing", "step_id": "direct-questions", - "step_title": "Five direct questions", + "step_title": "Five decisions facing the next council", "step_intro": "The five we are asking every candidate, and every resident, to answer.", "step_position": 1, "position": 2, @@ -123,7 +123,7 @@ { "question_id": "infrastructure_revenue", "step_id": "direct-questions", - "step_title": "Five direct questions", + "step_title": "Five decisions facing the next council", "step_intro": "The five we are asking every candidate, and every resident, to answer.", "step_position": 1, "position": 3, @@ -154,7 +154,7 @@ { "question_id": "capital_transparency", "step_id": "direct-questions", - "step_title": "Five direct questions", + "step_title": "Five decisions facing the next council", "step_intro": "The five we are asking every candidate, and every resident, to answer.", "step_position": 1, "position": 4, @@ -853,7 +853,7 @@ { "question_id": "cycling_network", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 0, @@ -887,7 +887,7 @@ { "question_id": "protest_access", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 1, @@ -921,7 +921,7 @@ { "question_id": "technology_investment", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 2, @@ -955,7 +955,7 @@ { "question_id": "budget_gap_first", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 3, @@ -986,7 +986,7 @@ { "question_id": "business_climate", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 4, @@ -1020,7 +1020,7 @@ { "question_id": "business_attraction", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 5, @@ -1054,7 +1054,7 @@ { "question_id": "construction_capacity", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 6, @@ -1088,7 +1088,7 @@ { "question_id": "public_realm_priority", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 7, @@ -1122,7 +1122,7 @@ { "question_id": "arts_culture_support", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 8, @@ -1156,7 +1156,7 @@ { "question_id": "av_conditions", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": null, "step_position": 9, "position": 9, diff --git a/db/seeds/elections/toronto_2026_city_priorities.json b/db/seeds/elections/toronto_2026_city_priorities.json index ddcfafa..72c7ebd 100644 --- a/db/seeds/elections/toronto_2026_city_priorities.json +++ b/db/seeds/elections/toronto_2026_city_priorities.json @@ -626,7 +626,7 @@ { "question_id": "cycling_network", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 0, @@ -660,7 +660,7 @@ { "question_id": "protest_access", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 1, @@ -694,7 +694,7 @@ { "question_id": "technology_investment", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 2, @@ -728,7 +728,7 @@ { "question_id": "budget_gap_first", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 3, @@ -759,7 +759,7 @@ { "question_id": "business_climate", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 4, @@ -793,7 +793,7 @@ { "question_id": "business_attraction", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 5, @@ -827,7 +827,7 @@ { "question_id": "construction_capacity", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 6, @@ -861,7 +861,7 @@ { "question_id": "public_realm_priority", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 7, @@ -895,7 +895,7 @@ { "question_id": "arts_culture_support", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 8, @@ -929,7 +929,7 @@ { "question_id": "av_conditions", "step_id": "more-topics", - "step_title": "A few more, if you have time", + "step_title": "Priorities and trade-offs", "step_intro": "All optional. Skip any you don't have a view on.", "step_position": 7, "position": 9, From 49f17b6d15ca268433e48301ae5051f3b01e9c67 Mon Sep 17 00:00:00 2001 From: Mikaal Naik Date: Tue, 8 Sep 2026 16:54:32 -0400 Subject: [PATCH 2/2] Give each seed test its own fixture directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seed tests all wrote to one fixed path, tmp/test_candidate_responses .json, while the suite runs across 14 processes. Tests raced to overwrite the file another was about to read, so a case asserting on an off-roster candidate saw a different test's payload instead. It surfaced as a wrong assertion rather than an obvious collision, and only sometimes — three green local runs said flake, CI said otherwise. The varying assertion count across runs (4947, 4950, 4953) was the tell. Dir.mktmpdir per test, removed in teardown. Repeated writes inside one test still reuse the file, which the re-seed cases depend on. Six full parallel runs now sit at 4953 assertions. Co-Authored-By: Claude Opus 5 (1M context) --- test/tasks/elections_seed_test.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/tasks/elections_seed_test.rb b/test/tasks/elections_seed_test.rb index ace02da..33d6d63 100644 --- a/test/tasks/elections_seed_test.rb +++ b/test/tasks/elections_seed_test.rb @@ -1,5 +1,6 @@ require "test_helper" require "rake" +require "tmpdir" # The seed is how every environment other than a laptop gets the questionnaire, # so what matters is that it rebuilds from nothing and that re-running it is @@ -15,6 +16,11 @@ class ElectionsSeedTest < ActiveSupport::TestCase setup do Rails.application.load_tasks unless Rake::Task.task_defined?("elections:seed_candidate_responses") + # A directory per test. The suite runs in parallel, so a shared fixed path + # means one test overwrites the seed another is about to read — which fails + # as a wrong assertion rather than an obvious collision, and only sometimes. + @seed_dir = Dir.mktmpdir("candidate-responses") + jurisdiction = Warehouse::Jurisdiction.find_or_create_by!(slug: "toronto") do |j| j.name = "City of Toronto" j.code = "TOR-ON" @@ -44,7 +50,13 @@ class ElectionsSeedTest < ActiveSupport::TestCase ) end - def write_seed(responses, path: Rails.root.join("tmp/test_candidate_responses.json")) + teardown do + FileUtils.remove_entry(@seed_dir) if @seed_dir && File.directory?(@seed_dir) + end + + # Repeated calls within one test deliberately reuse the same file, which is + # what the re-seed cases rewrite. + def write_seed(responses, path: File.join(@seed_dir, "candidate_responses.json")) File.write(path, JSON.pretty_generate( "election_slug" => "seedtown-2026", "survey_slug" => "candidate-questionnaire",