From eeffcdc3dda74c45cb4ff5ec87f777db2adebd7a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 17:15:30 +0000 Subject: [PATCH] Fix sample_data CLEAR=true scope, count message, and production guard - Scope CLEAR=true deletion to non-"pages" post types so hand-authored static pages survive, matching the generator which skips "pages" - Print the count and scope of posts to be deleted before destroying, and report the actual deleted count (previously always "Cleared 0") - Harden the production guard: keep the interactive prompt when a TTY is present, but abort when stdin is not a TTY or gets returns nil (EOF), unless CONFIRM_PRODUCTION=true is set explicitly. EOF is never treated as consent. - Add tests covering the deleted-count message, static page preservation, and production non-interactive/EOF abort behavior Closes #59 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CAFZmU5SJSERBVyedpsYQW --- lib/tasks/bunko/sample_data.rake | 28 ++++- test/tasks/bunko_sample_data_task_test.rb | 139 ++++++++++++++++++++++ 2 files changed, 162 insertions(+), 5 deletions(-) diff --git a/lib/tasks/bunko/sample_data.rake b/lib/tasks/bunko/sample_data.rake index 0952e98..2df7f1c 100644 --- a/lib/tasks/bunko/sample_data.rake +++ b/lib/tasks/bunko/sample_data.rake @@ -23,8 +23,21 @@ namespace :bunko do if Rails.env.production? puts "" puts "⚠️ WARNING: You're about to generate sample data in PRODUCTION" - puts " Press Ctrl+C to cancel, or Enter to continue..." - $stdin.gets + + confirmed = ENV["CONFIRM_PRODUCTION"] == "true" + + if !confirmed && $stdin.tty? + puts " Press Ctrl+C to cancel, or Enter to continue..." + # EOF (nil) is never treated as consent + confirmed = !$stdin.gets.nil? + end + + unless confirmed + puts " Cannot confirm interactively (no TTY or stdin closed)." + puts " Re-run with CONFIRM_PRODUCTION=true to proceed." + exit 1 + end + puts "" end @@ -52,11 +65,16 @@ namespace :bunko do puts " Clear existing: #{clear_existing ? "Yes" : "No"}" puts "" - # Clear existing posts if requested + # Clear existing posts if requested (static pages are preserved, matching + # the generator which manages the "pages" post type separately) if clear_existing + posts_to_clear = Post.joins(:post_type).where.not(post_types: {name: "pages"}) + clear_count = posts_to_clear.count + puts "Clearing existing posts..." - Post.destroy_all - puts "✓ Cleared #{Post.count} posts" + puts " Deleting #{clear_count} post(s) from all post types except 'pages'" + posts_to_clear.destroy_all + puts "✓ Cleared #{clear_count} post(s)" puts "" end diff --git a/test/tasks/bunko_sample_data_task_test.rb b/test/tasks/bunko_sample_data_task_test.rb index b28cc81..e629b03 100644 --- a/test/tasks/bunko_sample_data_task_test.rb +++ b/test/tasks/bunko_sample_data_task_test.rb @@ -2,6 +2,7 @@ require_relative "../test_helper" require "rake" +require "minitest/mock" class BunkoSampleDataTaskTest < Minitest::Test def setup @@ -87,6 +88,144 @@ def test_sample_data_clears_existing_when_requested ENV.delete("CLEAR") end + def test_sample_data_clear_message_reports_actual_deleted_count + 3.times do |i| + Post.create!( + post_type: @blog_type, + title: "Existing Post #{i}", + slug: "existing-post-#{i}", + content: "Test content", + status: "published", + published_at: Time.now + ) + end + + ENV["COUNT"] = "1" + ENV["CLEAR"] = "true" + + output = capture_io do + run_rake_task("bunko:sample_data") + end + + # Message must reflect the number of posts actually deleted (not the + # post-deletion count, which is always 0) + assert_match(/Deleting 3 post\(s\) from all post types except 'pages'/, output.join) + assert_match(/Cleared 3 post\(s\)/, output.join) + ensure + ENV.delete("COUNT") + ENV.delete("CLEAR") + end + + def test_sample_data_clear_preserves_static_pages + pages_type = PostType.create!(name: "pages", title: "Pages") + + Post.create!( + post_type: pages_type, + title: "About Us", + slug: "about-us", + content: "Hand-authored page content", + status: "published", + published_at: Time.now + ) + + Post.create!( + post_type: @blog_type, + title: "Old Blog Post", + slug: "old-blog-post", + content: "Test content", + status: "published", + published_at: Time.now + ) + + ENV["COUNT"] = "1" + ENV["CLEAR"] = "true" + + # Disable static page generation so the task doesn't create standard + # pages or modify the dummy app's routes file + original_allow = Bunko.configuration.allow_static_pages + Bunko.configuration.allow_static_pages = false + + capture_io do + run_rake_task("bunko:sample_data") + end + + # Static pages must survive CLEAR=true + assert Post.exists?(slug: "about-us"), "Static page should not be deleted by CLEAR=true" + # Non-page posts are cleared + refute Post.exists?(slug: "old-blog-post") + ensure + Bunko.configuration.allow_static_pages = original_allow + ENV.delete("COUNT") + ENV.delete("CLEAR") + end + + def test_sample_data_production_non_interactive_aborts_without_confirmation + ENV["COUNT"] = "1" + ENV.delete("CONFIRM_PRODUCTION") + + # $stdin is a StringIO in tests, so $stdin.tty? is false (non-interactive) + production_env = ActiveSupport::StringInquirer.new("production") + + output = Rails.stub(:env, production_env) do + capture_io do + assert_raises(SystemExit) do + run_rake_task("bunko:sample_data") + end + end + end + + assert_match(/WARNING: You're about to generate sample data in PRODUCTION/, output.join) + assert_match(/CONFIRM_PRODUCTION=true/, output.join) + assert_equal 0, Post.count, "No posts should be created when the production guard aborts" + ensure + ENV.delete("COUNT") + end + + def test_sample_data_production_non_interactive_proceeds_with_confirmation + ENV["COUNT"] = "1" + ENV["CONFIRM_PRODUCTION"] = "true" + + production_env = ActiveSupport::StringInquirer.new("production") + + Rails.stub(:env, production_env) do + capture_io do + run_rake_task("bunko:sample_data") + end + end + + assert_equal 3, Post.count # 1 post × 3 post types + ensure + ENV.delete("COUNT") + ENV.delete("CONFIRM_PRODUCTION") + end + + def test_sample_data_production_eof_on_gets_aborts + ENV["COUNT"] = "1" + ENV.delete("CONFIRM_PRODUCTION") + + # Simulate a TTY whose gets returns nil (EOF) — EOF is never consent + eof_stdin = StringIO.new("") + def eof_stdin.tty? + true + end + $stdin = eof_stdin + + production_env = ActiveSupport::StringInquirer.new("production") + + output = Rails.stub(:env, production_env) do + capture_io do + assert_raises(SystemExit) do + run_rake_task("bunko:sample_data") + end + end + end + + assert_match(/CONFIRM_PRODUCTION=true/, output.join) + assert_equal 0, Post.count + ensure + ENV.delete("COUNT") + end + def test_sample_data_preserves_existing_by_default # Create an existing post Post.create!(