diff --git a/doc/plans/2026-08-29-maintainer-backlog-triage.md b/doc/plans/2026-08-29-maintainer-backlog-triage.md index 57c5be2..0db5742 100644 --- a/doc/plans/2026-08-29-maintainer-backlog-triage.md +++ b/doc/plans/2026-08-29-maintainer-backlog-triage.md @@ -191,25 +191,31 @@ reviving the proposed public hook surface. ## Current replacement stack -### Model-aware fixture generation (PR 1) +### Model-aware fixture generation (PR 1, [#116](https://github.com/rdy/fixture_builder/pull/116)) -In progress from current `master`: use loaded Active Record models by their -configured table names, emit Rails-native `_fixture.model_class` metadata for -model-backed YAML, and preserve raw SQL fallback for tables without an eligible -model. The implementation keeps Rails fixture loading authoritative: explicit +Completed from current `master`: loaded Active Record models are resolved by +their configured table names, model-backed YAML emits Rails-native +`_fixture.model_class` metadata, and tables without an eligible model retain the +raw SQL fallback. Rails fixture loading remains authoritative: explicit fixture-class mappings take precedence and unresolved metadata may use conventional inference. Its regression coverage is organized by final ownership in `test/fixture_builder_test.rb`, `test/configuration_test.rb`, `test/configuration/manifest_test.rb`, `test/fixtures_path_test.rb`, `test/builder/`, and `test/model_resolver/`. -### Ephemeral test-model migration (PR 2) - -Planned as the dependent follow-up: migrate remaining ordinary fixture-builder -tests to models whose schemas are declared by their owning test cases. Retain -manual setup only for raw SQL, constant/autoload timing, alternate pools, schema -errors, namespaced table-name boundaries, and STI. Keep the metadata-aware and -metadata-free legacy fixture inputs as separate compatibility paths. +### Ephemeral test-model migration (PR 2, [#117](https://github.com/rdy/fixture_builder/pull/117)) + +Completed as the dependent follow-up: the remaining ordinary fixture-builder +tests now use models whose schemas are declared by their owning test cases, and +the shared persistent test database helper is removed. The former shared +`MagicalCreature` schema now belongs inline to the owning integration case in +`test/fixture_builder_test.rb`, manifest lifecycle case in +`test/configuration/manifest_test.rb`, and serialization case in +`test/builder/serialization_test.rb`; `test/configuration_test.rb` remains the +model-free configuration contract case. Manual setup remains only at the +intentional raw SQL, constant/autoload timing, alternate pool, schema-error, +namespaced table-name, and STI boundaries. Metadata-aware and metadata-free +legacy fixture inputs remain separate compatibility paths. ## Execution order diff --git a/test/builder/generated_fixture_ownership_test.rb b/test/builder/generated_fixture_ownership_test.rb index 5f6b262..a3b87d4 100644 --- a/test/builder/generated_fixture_ownership_test.rb +++ b/test/builder/generated_fixture_ownership_test.rb @@ -3,23 +3,24 @@ require_relative "../test_helper" require "tmpdir" -# standard:disable Rails/ApplicationRecord -class OwnershipRecord < ActiveRecord::Base -end -# standard:enable Rails/ApplicationRecord - module BuilderTests class GeneratedFixtureOwnershipTest < Test::Unit::TestCase MARKER = "# Generated by fixture_builder. Do not edit.\n" + with_model :PopulatedRecord do + table { |table| table.string :name } + end + + with_model :EmptyRecord do + table + end + + with_model :DroppedRecord do + table { |table| table.string :name } + end + def setup @directory = Dir.mktmpdir("fixture-builder-ownership") - # with_table captures this adapter when the suite loads, so reset the - # in-memory database without replacing its connection pool. - connection = ActiveRecord::Base.connection - connection.tables.each { |table| connection.drop_table(table) } - connection.create_table(:ownership_records) { |table| table.string :name } - OwnershipRecord.reset_column_information end def teardown @@ -27,38 +28,37 @@ def teardown end def test_marks_populated_and_empty_output - generate { OwnershipRecord.create!(name: "Gandalf") } + generate { PopulatedRecord.create!(name: "Gandalf") } - populated = fixture_path("ownership_records") - empty = fixture_path("empty_records") + populated = fixture_path(PopulatedRecord.table_name) + empty = fixture_path(EmptyRecord.table_name) assert_equal MARKER, File.open(populated, &:readline) assert_equal MARKER, File.open(empty, &:readline) assert_equal "Gandalf", YAML.safe_load_file(populated).dig("gandalf", "name") - assert_equal({}, YAML.safe_load_file(empty)) + assert_equal({"_fixture" => {"model_class" => EmptyRecord.name}}, YAML.safe_load_file(empty)) - OwnershipRecord.delete_all - ActiveRecord::FixtureSet.create_fixtures(@directory, "ownership_records") - assert_equal "Gandalf", OwnershipRecord.find_by!(name: "Gandalf").name + PopulatedRecord.delete_all + ActiveRecord::FixtureSet.create_fixtures(@directory, PopulatedRecord.table_name) + assert_equal "Gandalf", PopulatedRecord.find_by!(name: "Gandalf").name end def test_removes_marked_fixture_after_table_is_dropped - ActiveRecord::Base.connection.create_table(:dropped_records) { |table| table.string :name } - generate(tables: %w[ownership_records empty_records dropped_records]) - stale = fixture_path("dropped_records") + generate(tables: [PopulatedRecord.table_name, EmptyRecord.table_name, DroppedRecord.table_name]) + stale = fixture_path(DroppedRecord.table_name) assert_path_exist stale - ActiveRecord::Base.connection.drop_table(:dropped_records) + ActiveRecord::Base.connection.drop_table(DroppedRecord.table_name) - generate { OwnershipRecord.create!(name: "Current") } + generate { PopulatedRecord.create!(name: "Current") } assert_path_not_exist stale - assert_path_exist fixture_path("ownership_records") + assert_path_exist fixture_path(PopulatedRecord.table_name) end def test_preserves_unmarked_orphans orphan = fixture_path("unknown_records") File.binwrite(orphan, "unknown: true\n") - generate { OwnershipRecord.create!(name: "Current") } + generate { PopulatedRecord.create!(name: "Current") } assert_equal "unknown: true\n", File.binread(orphan) end @@ -74,7 +74,7 @@ def test_requires_exact_first_line_marker [path, contents] end - generate { OwnershipRecord.create!(name: "Current") } + generate { PopulatedRecord.create!(name: "Current") } paths.each_value { |path, contents| assert_equal contents, File.read(path) } end @@ -83,16 +83,16 @@ def test_removes_edited_marked_orphans stale = fixture_path("edited_records") File.write(stale, MARKER + "human_edit: true\n") - generate { OwnershipRecord.create!(name: "Current") } + generate { PopulatedRecord.create!(name: "Current") } assert_path_not_exist stale end def test_warns_before_overwriting_unmarked_current_output - path = fixture_path("ownership_records") + path = fixture_path(PopulatedRecord.table_name) File.write(path, "old: true\n") - _output, warning = capture_output { generate { OwnershipRecord.create!(name: "Current") } } + _output, warning = capture_output { generate { PopulatedRecord.create!(name: "Current") } } assert_include warning, path assert_include warning, "replacing an unmarked fixture" @@ -101,18 +101,18 @@ def test_warns_before_overwriting_unmarked_current_output end def test_overwrites_marked_output_without_warning - path = fixture_path("ownership_records") + path = fixture_path(PopulatedRecord.table_name) File.write(path, MARKER + "edited: true\n") - _output, warning = capture_output { generate { OwnershipRecord.create!(name: "Current") } } + _output, warning = capture_output { generate { PopulatedRecord.create!(name: "Current") } } assert_empty warning assert_equal "Current", YAML.safe_load_file(path).dig("current", "name") end def test_preserves_unmarked_omitted_empty_output - marked = fixture_path("empty_records") - unmarked = fixture_path("ownership_records") + marked = fixture_path(EmptyRecord.table_name) + unmarked = fixture_path(PopulatedRecord.table_name) File.write(marked, MARKER + "{}\n") File.write(unmarked, "hand_authored: true\n") @@ -129,7 +129,7 @@ def test_removes_marked_output_for_newly_skipped_table File.write(marked, MARKER + "{}\n") File.write(unmarked, "hand_authored: true\n") - generate { OwnershipRecord.create!(name: "Current") } + generate { PopulatedRecord.create!(name: "Current") } assert_path_not_exist marked assert_path_exist unmarked @@ -140,10 +140,10 @@ def test_removes_marked_output_for_newly_skipped_table def generate( write_empty_files: true, legacy_marker_migration: false, - tables: %w[ownership_records empty_records], + tables: nil, &block ) - ActiveRecord::Base.connection.create_table(:empty_records) unless ActiveRecord::Base.connection.table_exists?(:empty_records) + tables ||= [PopulatedRecord.table_name, EmptyRecord.table_name] configuration = FixtureBuilder::Configuration.new configuration.fixture_directory = @directory configuration.write_empty_files = write_empty_files diff --git a/test/builder/serialization_test.rb b/test/builder/serialization_test.rb index deaed3a..a6e43b0 100644 --- a/test/builder/serialization_test.rb +++ b/test/builder/serialization_test.rb @@ -5,12 +5,25 @@ # standard:disable Rails/ApplicationRecord module BuilderTests class SerializationTest < Test::Unit::TestCase - include TestDatabase prepend IsolatedFixtureFilesystem - def setup - super - create_and_blow_away_old_db + with_model :MagicalCreature do + table do |table| + table.string :name + table.string :species + table.string :powers + table.json :wizard_data + table.date :born_on + table.boolean :deleted, default: false, null: false + end + + model do + validates_presence_of :name, :species + serialize :powers, type: Array + default_scope -> { where(deleted: false) } + attribute :virtual, ActiveRecord::Type::Integer.new + attribute :wizard_data, WizardDataType.new + end end def test_serialization diff --git a/test/configuration/generated_file_marker_test.rb b/test/configuration/generated_file_marker_test.rb index a57cc20..8ab792f 100644 --- a/test/configuration/generated_file_marker_test.rb +++ b/test/configuration/generated_file_marker_test.rb @@ -3,15 +3,14 @@ require_relative "../test_helper" require "tmpdir" -# standard:disable Rails/ApplicationRecord -class MarkerMigrationRecord < ActiveRecord::Base -end -# standard:enable Rails/ApplicationRecord - module ConfigurationTests class GeneratedFileMarkerTest < Test::Unit::TestCase MARKER = "# Generated by fixture_builder. Do not edit.\n" + with_model :MarkerRecord do + table { |table| table.string :name } + end + def setup @directory = Dir.mktmpdir("fixture-builder-marker-migration") @fixtures = File.join(@directory, "fixtures") @@ -19,10 +18,6 @@ def setup @source = File.join(@directory, "source.rb") FileUtils.mkdir_p(@fixtures) File.write(@source, "source\n") - connection = ActiveRecord::Base.connection - connection.tables.each { |table| connection.drop_table(table) } - connection.create_table(:marker_migration_records) { |table| table.string :name } - MarkerMigrationRecord.reset_column_information end def teardown @@ -40,7 +35,7 @@ def test_migrates_compatible_v1_without_overwrite_warning _output, warning = capture_output do configuration.factory do builds += 1 - MarkerMigrationRecord.create!(name: "Migrated") + MarkerRecord.create!(name: "Migrated") end end @@ -57,14 +52,14 @@ def test_persists_adoption_after_success config = configuration config.factory do builds += 1 - MarkerMigrationRecord.create!(name: "Migrated") + MarkerRecord.create!(name: "Migrated") end manifest = YAML.safe_load_file(@manifest) assert_equal 1, manifest.fetch("version") assert_equal true, manifest.fetch("generated_file_marker") assert_equal Digest::SHA256.file(fixture_path).hexdigest, - manifest.fetch("fixtures").fetch("marker_migration_records.yml") + manifest.fetch("fixtures").fetch("#{MarkerRecord.table_name}.yml") config.factory { builds += 1 } assert_equal 1, builds @@ -72,11 +67,11 @@ def test_persists_adoption_after_success def test_warns_on_unmarked_collision_after_adoption config = configuration - config.factory { MarkerMigrationRecord.create!(name: "First") } + config.factory { MarkerRecord.create!(name: "First") } File.write(fixture_path, "unmarked: true\n") _output, warning = capture_output do - config.factory { MarkerMigrationRecord.create!(name: "Second") } + config.factory { MarkerRecord.create!(name: "Second") } end assert_include warning, "replacing an unmarked fixture" @@ -97,7 +92,7 @@ def test_does_not_suppress_for_unrecognized_manifests(manifest) manifest ? File.write(@manifest, manifest.to_yaml) : FileUtils.rm_f(@manifest) _output, warning = capture_output do - configuration.factory { MarkerMigrationRecord.create!(name: "Current") } + configuration.factory { MarkerRecord.create!(name: "Current") } end assert_include warning, "replacing an unmarked fixture" @@ -111,14 +106,14 @@ def test_does_not_adopt_when_after_build_raises config.after_build = proc { raise "after build failure" } assert_raise(RuntimeError) do - config.factory { MarkerMigrationRecord.create!(name: "Failed") } + config.factory { MarkerRecord.create!(name: "Failed") } end assert_path_not_exist @manifest File.write(fixture_path, "still_unmarked: true\n") retry_config = configuration _output, warning = capture_output do - retry_config.factory { MarkerMigrationRecord.create!(name: "Retried") } + retry_config.factory { MarkerRecord.create!(name: "Retried") } end assert_include warning, "replacing an unmarked fixture" end @@ -134,7 +129,7 @@ def test_does_not_adopt_when_output_write_fails config.factory do FileUtils.rm_f(output_path) File.chmod(0o500, fixture_directory) - MarkerMigrationRecord.create!(name: "Failed") + MarkerRecord.create!(name: "Failed") end end @@ -150,13 +145,13 @@ def test_rechecks_adoption_under_generation_lock config.define_singleton_method(:rebuild_fixtures_preflight?) do adopted = YAML.safe_load_file(fixture_builder_file) adopted["generated_file_marker"] = true - adopted["fixtures"]["marker_migration_records.yml"] = "stale" + adopted["fixtures"]["#{MarkerRecord.table_name}.yml"] = "stale" File.write(fixture_builder_file, adopted.to_yaml) true end _output, warning = capture_output do - config.factory { MarkerMigrationRecord.create!(name: "Locked") } + config.factory { MarkerRecord.create!(name: "Locked") } end assert_include warning, "replacing an unmarked fixture" @@ -169,12 +164,12 @@ def configuration config.fixture_directory = @fixtures config.fixture_builder_file = @manifest config.files_to_check = [@source] - config.skip_tables = ActiveRecord::Base.connection.tables - ["marker_migration_records"] + config.skip_tables = ActiveRecord::Base.connection.tables - [MarkerRecord.table_name] config.write_empty_files = false end end - def fixture_path(table_name = "marker_migration_records") + def fixture_path(table_name = MarkerRecord.table_name) File.join(@fixtures, "#{table_name}.yml") end diff --git a/test/configuration/manifest_test.rb b/test/configuration/manifest_test.rb index 842cf50..b5ef8c0 100644 --- a/test/configuration/manifest_test.rb +++ b/test/configuration/manifest_test.rb @@ -5,12 +5,25 @@ # standard:disable Rails/ApplicationRecord module ConfigurationTests class ManifestTest < Test::Unit::TestCase - include TestDatabase prepend IsolatedFixtureFilesystem - def setup - super - create_and_blow_away_old_db + with_model :MagicalCreature do + table do |table| + table.string :name + table.string :species + table.string :powers + table.json :wizard_data + table.date :born_on + table.boolean :deleted, default: false, null: false + end + + model do + validates_presence_of :name, :species + serialize :powers, type: Array + default_scope -> { where(deleted: false) } + attribute :virtual, ActiveRecord::Type::Integer.new + attribute :wizard_data, WizardDataType.new + end end def test_malformed_manifest_raises_without_running_factory diff --git a/test/fixture_builder_test.rb b/test/fixture_builder_test.rb index ab974c7..ad439d3 100644 --- a/test/fixture_builder_test.rb +++ b/test/fixture_builder_test.rb @@ -4,12 +4,25 @@ # standard:disable Rails/ApplicationRecord class FixtureBuilderTest < Test::Unit::TestCase - include TestDatabase prepend IsolatedFixtureFilesystem - def setup - super - create_and_blow_away_old_db + with_model :MagicalCreature do + table do |table| + table.string :name + table.string :species + table.string :powers + table.json :wizard_data + table.date :born_on + table.boolean :deleted, default: false, null: false + end + + model do + validates_presence_of :name, :species + serialize :powers, type: Array + default_scope -> { where(deleted: false) } + attribute :virtual, ActiveRecord::Type::Integer.new + attribute :wizard_data, WizardDataType.new + end end def test_configure @@ -97,6 +110,7 @@ def test_custom_json_attribute_type_round_trips_through_fixtures end generated_fixture = YAML.safe_load_file(fixture_path("#{MagicalCreature.table_name}.yml")) + assert_equal({"model_class" => MagicalCreature.name}, generated_fixture.fetch("_fixture")) assert_equal( {"level" => 99, "title" => "The Grey", "allies" => %w[Frodo Aragorn]}, generated_fixture.dig("gandalf", "wizard_data") diff --git a/test/legacy_fixture_mode/conventional_model_metadata_test.rb b/test/legacy_fixture_mode/conventional_model_metadata_test.rb new file mode 100644 index 0000000..84f6dde --- /dev/null +++ b/test/legacy_fixture_mode/conventional_model_metadata_test.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require_relative "../test_helper" +require "tmpdir" + +# standard:disable Rails/ApplicationRecord +class ConventionalModelMetadataTest < Test::Unit::TestCase + prepend IsolatedFixtureFilesystem + + with_table :conventional_legacy_creatures do |table| + table.string :name, null: false + end + + with_model :ConventionalLegacyCreature do + table + + model do + self.table_name = "conventional_legacy_creatures" + end + end + + def test_metadata_free_conventional_legacy_fixture_is_regenerated_with_native_metadata + table_name = "conventional_legacy_creatures" + model = ConventionalLegacyCreature + input_path = test_path("legacy_fixtures/#{table_name}.yml") + + Dir.mktmpdir("fixture-builder-legacy-convention") do |directory| + output_directory = File.join(directory, "output") + manifest_path = File.join(directory, "manifest.yml") + FileUtils.mkdir_p(output_directory) + + input_fixture = YAML.safe_load_file(input_path) + assert_not_include input_fixture, "_fixture" + + factory_records = nil + FixtureBuilder.instance_variable_set(:@configuration, nil) + FixtureBuilder.configure do |fbuilder| + fbuilder.files_to_check = [] + fbuilder.fixture_directory = output_directory + fbuilder.fixture_builder_file = manifest_path + fbuilder.legacy_fixtures = [input_path] + fbuilder.skip_tables = ActiveRecord::Base.connection.tables - [table_name] + fbuilder.factory { factory_records = model.order(:name).pluck(:name) } + end + + assert_equal ["Legacy creature"], factory_records + + output_path = File.join(output_directory, "#{table_name}.yml") + output_fixture = YAML.safe_load_file(output_path) + assert_equal({"model_class" => model.name}, output_fixture.fetch("_fixture")) + assert_equal "Legacy creature", output_fixture.fetch("legacy_creature").fetch("name") + + model.delete_all + ActiveRecord::FixtureSet.reset_cache + ActiveRecord::FixtureSet.create_fixtures(output_directory, table_name) + assert_equal ["Legacy creature"], model.order(:name).pluck(:name) + end + end +end +# standard:enable Rails/ApplicationRecord diff --git a/test/legacy_fixture_mode_fixture_generation_test.rb b/test/legacy_fixture_mode_fixture_generation_test.rb index 029193e..639d445 100644 --- a/test/legacy_fixture_mode_fixture_generation_test.rb +++ b/test/legacy_fixture_mode_fixture_generation_test.rb @@ -1,27 +1,44 @@ # frozen_string_literal: true require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) +require "tmpdir" +# standard:disable Rails/ApplicationRecord class LegacyFixtureModeFixtureGenerationTest < Test::Unit::TestCase - include TestDatabase + prepend IsolatedFixtureFilesystem + + with_model :MagicalCreature do + table do |table| + table.string :name + table.string :species + table.string :powers + table.json :wizard_data + table.date :born_on + table.boolean :deleted, default: false, null: false + end + + model do + validates_presence_of :name, :species + serialize :powers, type: Array + default_scope -> { where(deleted: false) } + end + end + + # standard:enable Rails/ApplicationRecord def setup - create_and_blow_away_old_db + ActiveRecord::FixtureSet.reset_cache force_fixture_generation FixtureBuilder.configure do |fbuilder| - fbuilder.legacy_fixtures = Dir[test_path("legacy_fixtures/*.yml"), test_path("other_legacy_fixture_set/*.yml")] + fbuilder.legacy_fixtures = Dir[test_path("legacy_fixtures/magical_creatures.yml"), test_path("other_legacy_fixture_set/*.yml")] fbuilder.factory do MagicalCreature.create(name: "frank", species: "unicorn") MagicalCreature.create(name: "loch ness monster", species: "sea creature", deleted: true) end end - @@magical_creatures = YAML.load(File.open(test_path("fixtures/magical_creatures.yml"))) - end - - def teardown - FixtureBuilder.send(:remove_instance_variable, :@configuration) + @@magical_creatures = YAML.load(File.open(fixture_path("#{MagicalCreature.table_name}.yml"))) end def test_legacy_fixtures_created diff --git a/test/legacy_fixture_mode_test.rb b/test/legacy_fixture_mode_test.rb index 543e5a3..3301f36 100644 --- a/test/legacy_fixture_mode_test.rb +++ b/test/legacy_fixture_mode_test.rb @@ -2,21 +2,37 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) +# standard:disable Rails/ApplicationRecord class LegacyFixtureModeTest < Test::Unit::TestCase - include TestDatabase + prepend IsolatedFixtureFilesystem - def setup - create_and_blow_away_old_db - force_fixture_generation + with_model :MagicalCreature do + table do |table| + table.string :name + table.string :species + table.string :powers + table.json :wizard_data + table.date :born_on + table.boolean :deleted, default: false, null: false + end + + model do + validates_presence_of :name, :species + serialize :powers, type: Array + default_scope -> { where(deleted: false) } + end end - def teardown - FixtureBuilder.send(:remove_instance_variable, :@configuration) + # standard:enable Rails/ApplicationRecord + + def setup + ActiveRecord::FixtureSet.reset_cache + force_fixture_generation end def test_load_legacy_fixtures FixtureBuilder.configure do |fbuilder| - fbuilder.legacy_fixtures = Dir[test_path("legacy_fixtures/*.yml"), test_path("other_legacy_fixture_set/*.yml")] + fbuilder.legacy_fixtures = Dir[test_path("legacy_fixtures/magical_creatures.yml"), test_path("other_legacy_fixture_set/*.yml")] fbuilder.factory do end end @@ -35,7 +51,7 @@ def test_generate_new_fixtures_without_legacy def test_new_and_old_fixtures FixtureBuilder.configure do |fbuilder| - fbuilder.legacy_fixtures = Dir[test_path("legacy_fixtures/*.yml"), test_path("other_legacy_fixture_set/*.yml")] + fbuilder.legacy_fixtures = Dir[test_path("legacy_fixtures/magical_creatures.yml"), test_path("other_legacy_fixture_set/*.yml")] fbuilder.factory do MagicalCreature.create name: "Barry", species: "Party Guy" end diff --git a/test/legacy_fixtures/conventional_legacy_creatures.yml b/test/legacy_fixtures/conventional_legacy_creatures.yml new file mode 100644 index 0000000..11bafde --- /dev/null +++ b/test/legacy_fixtures/conventional_legacy_creatures.yml @@ -0,0 +1,2 @@ +legacy_creature: + name: Legacy creature diff --git a/test/legacy_fixtures/magical_creatures.yml b/test/legacy_fixtures/magical_creatures.yml index d981c56..1b6f97e 100644 --- a/test/legacy_fixtures/magical_creatures.yml +++ b/test/legacy_fixtures/magical_creatures.yml @@ -1,3 +1,6 @@ +_fixture: + model_class: MagicalCreature + bigfoot: name: bigfoot diff --git a/test/model_resolver/ambiguous_model_error_test.rb b/test/model_resolver/ambiguous_model_error_test.rb index 565962c..e8441bf 100644 --- a/test/model_resolver/ambiguous_model_error_test.rb +++ b/test/model_resolver/ambiguous_model_error_test.rb @@ -3,7 +3,7 @@ require_relative "../test_helper" module ModelResolverTests - class ErrorTest < Test::Unit::TestCase + class AmbiguousModelErrorTest < Test::Unit::TestCase prepend IsolatedFixtureFilesystem def test_ambiguous_model_error_exposes_its_table_name_and_models diff --git a/test/namer_test.rb b/test/namer_test.rb index 1b9fd47..adfe387 100644 --- a/test/namer_test.rb +++ b/test/namer_test.rb @@ -22,8 +22,28 @@ def self.[](attribute) def self.model_class = NamerTestModel end +# standard:disable Rails/ApplicationRecord class NamerTest < Test::Unit::TestCase - include TestDatabase + prepend IsolatedFixtureFilesystem + + with_model :MagicalCreature do + table do |table| + table.string :name + table.string :species + table.string :powers + table.json :wizard_data + table.date :born_on + table.boolean :deleted, default: false, null: false + end + + model do + validates_presence_of :name, :species + serialize :powers, type: Array + default_scope -> { where(deleted: false) } + end + end + + # standard:enable Rails/ApplicationRecord def setup configuration = FixtureBuilder::Configuration.new @@ -64,10 +84,9 @@ def test_name_not_unique_across_tables end def test_populate_custom_names_from_current_fixture_sets - create_and_blow_away_old_db fixture_sets = ActiveRecord::FixtureSet.create_fixtures( test_path("legacy_fixtures"), - MagicalCreature.table_name + "magical_creatures" ) assert_equal [ActiveRecord::FixtureSet], fixture_sets.map(&:class).uniq diff --git a/test/support/test_database.rb b/test/support/test_database.rb deleted file mode 100644 index c5b0eab..0000000 --- a/test/support/test_database.rb +++ /dev/null @@ -1,98 +0,0 @@ -# frozen_string_literal: true - -# Shared test database used by the FixtureBuilder tests. -# -# Test classes include this module and call +create_and_blow_away_old_db+ from -# their own +setup+ or from individual tests, exactly as they did when this -# lived as a top-level helper method. -# -# Alongside the `magical_creatures` table the tests share, the schema carries -# the tables that exist only to exercise FixtureBuilder's database-generated -# column handling. -# -# Ownership of the schema and of the fixture files it causes FixtureBuilder to -# write live together: including this module registers a teardown that removes -# those fixture files, so every test class that builds the shared schema cleans -# up after itself rather than depending on another class running later. -module TestDatabase - CONFIGURATION = {"adapter" => "sqlite3", "database" => ":memory:"}.freeze - - GENERATED_CREATURES_TABLE = "generated_creatures" - GENERATED_COLUMN_RECORDS_TABLE = "generated_column_records" - RELOCATED_CREATURES_TABLE = "relocated_creatures" - CREATURE_ARCHIVE_TABLE = "creature_archive" - - # The only tables created solely to exercise generated columns, and therefore - # the only fixture files a run is allowed to delete. `magical_creatures` is - # user-authored fixture data and is deliberately absent. - GENERATED_TEST_TABLES = [ - GENERATED_CREATURES_TABLE, - GENERATED_COLUMN_RECORDS_TABLE, - RELOCATED_CREATURES_TABLE, - CREATURE_ARCHIVE_TABLE - ].freeze - - def self.included(base) - base.teardown :clean_up_generated_fixture_files - end - - def create_and_blow_away_old_db - ActiveRecord::Base.configurations = {"test" => CONFIGURATION} - connection = ActiveRecord::Base.connection - connection.tables.each { |table| connection.drop_table(table) } - connection.schema_cache.clear! - ActiveRecord::FixtureSet.reset_cache - connection.create_table(:magical_creatures, force: true) do |t| - t.column :name, :string - t.column :species, :string - t.column :powers, :string - t.column :wizard_data, :json - t.column :born_on, :date - t.column :deleted, :boolean, default: false, null: false - end - - # Inferable as the `GeneratedCreature` model, so the model-backed extraction - # path sees a database-generated column. - create_generated_column_table(GENERATED_CREATURES_TABLE) - - # No inferable model, so the raw-query extraction path sees a - # database-generated column. - create_generated_column_table(GENERATED_COLUMN_RECORDS_TABLE) - - # The table FixtureBuilder iterates (`relocated_creatures`) alongside the - # differently named table `RelocatedCreature` actually reads. - # - # The two tables expose deliberately incompatible schemas: the iterated - # table's only writable column is `unrelated` and its `name` is - # database-generated, while the model's table has a writable `name`. Reading - # generated columns from the iterated table instead of the model's table - # therefore strips `name` from the fixture. - connection.create_table(RELOCATED_CREATURES_TABLE, force: true) do |t| - t.string :unrelated - t.virtual :name, type: :string, as: "upper(unrelated)", stored: true - end - connection.create_table(CREATURE_ARCHIVE_TABLE, force: true) do |t| - t.string :name, null: false - end - - RelocatedCreature.reset_column_information - end - - # Creates a table with a writable `name` column and a stored - # database-generated `name_length` column. - def create_generated_column_table(table_name) - ActiveRecord::Base.connection.create_table(table_name, force: true) do |t| - t.string :name, null: false - t.virtual :name_length, type: :integer, as: "length(name)", stored: true - end - end - - # FixtureBuilder writes a fixture file for every table it iterates, so runs - # leave behind output for the generated-column tables above. Delete exactly - # those files, never the fixture directory or fixtures the repository owns. - def clean_up_generated_fixture_files - GENERATED_TEST_TABLES.each do |table_name| - FileUtils.rm_f(test_path("fixtures/#{table_name}.yml")) - end - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb index ee3f13c..049ec98 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -91,7 +91,6 @@ def create_fixtures(*table_names, &block) require "sqlite3" require "fixture_builder" -require_relative "support/test_database" ActiveRecord::Base.configurations = {"test" => {"adapter" => "sqlite3", "database" => ":memory:"}} ActiveRecord::Base.establish_connection(:test) @@ -147,37 +146,13 @@ def wizard_data(attributes) end end -# standard:disable Rails/ApplicationRecord -class RelocatedCreature < ActiveRecord::Base - self.table_name = "creature_archive" -end - -class MagicalCreature < ActiveRecord::Base - validates_presence_of :name, :species - serialize :powers, type: Array - default_scope -> { where(deleted: false) } - attribute :virtual, ActiveRecord::Type::Integer.new - attribute :wizard_data, WizardDataType.new -end -# standard:enable Rails/ApplicationRecord - def force_fixture_generation - FileUtils.rm_f(current_fixture_builder_file) - reset_fixture_builder_configuration if isolated_fixture_filesystem? + FileUtils.rm_f(fixture_builder_file) + reset_fixture_builder_configuration end def force_fixture_generation_due_to_differing_file_hashes - FileUtils.mkdir_p(File.dirname(current_fixture_builder_file)) - File.write(current_fixture_builder_file, "blah blah blah") - reset_fixture_builder_configuration if isolated_fixture_filesystem? -end - -def current_fixture_builder_file - return fixture_builder_file if isolated_fixture_filesystem? - - File.expand_path("../tmp/fixture_builder.yml", __dir__) -end - -def isolated_fixture_filesystem? - respond_to?(:fixture_builder_file, true) + FileUtils.mkdir_p(File.dirname(fixture_builder_file)) + File.write(fixture_builder_file, "blah blah blah") + reset_fixture_builder_configuration end