test_data 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/CHANGELOG.md +2 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +104 -0
  6. data/LICENSE.txt +25 -0
  7. data/README.md +35 -0
  8. data/Rakefile +11 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/example/.gitattributes +8 -0
  12. data/example/.gitignore +24 -0
  13. data/example/Gemfile +19 -0
  14. data/example/Gemfile.lock +211 -0
  15. data/example/README.md +24 -0
  16. data/example/Rakefile +6 -0
  17. data/example/app/assets/config/manifest.js +2 -0
  18. data/example/app/assets/stylesheets/application.css +15 -0
  19. data/example/app/controllers/application_controller.rb +2 -0
  20. data/example/app/controllers/boops_controller.rb +10 -0
  21. data/example/app/helpers/application_helper.rb +2 -0
  22. data/example/app/models/application_record.rb +3 -0
  23. data/example/app/models/boop.rb +2 -0
  24. data/example/app/views/boops/index.html.erb +8 -0
  25. data/example/app/views/layouts/application.html.erb +15 -0
  26. data/example/bin/bundle +114 -0
  27. data/example/bin/rails +4 -0
  28. data/example/bin/rake +4 -0
  29. data/example/bin/setup +33 -0
  30. data/example/config.ru +6 -0
  31. data/example/config/application.rb +35 -0
  32. data/example/config/boot.rb +4 -0
  33. data/example/config/credentials.yml.enc +1 -0
  34. data/example/config/database.yml +86 -0
  35. data/example/config/environment.rb +5 -0
  36. data/example/config/environments/development.rb +60 -0
  37. data/example/config/environments/production.rb +96 -0
  38. data/example/config/environments/test.rb +49 -0
  39. data/example/config/initializers/application_controller_renderer.rb +8 -0
  40. data/example/config/initializers/backtrace_silencers.rb +8 -0
  41. data/example/config/initializers/content_security_policy.rb +28 -0
  42. data/example/config/initializers/cookies_serializer.rb +5 -0
  43. data/example/config/initializers/filter_parameter_logging.rb +6 -0
  44. data/example/config/initializers/inflections.rb +16 -0
  45. data/example/config/initializers/mime_types.rb +4 -0
  46. data/example/config/initializers/permissions_policy.rb +11 -0
  47. data/example/config/initializers/wrap_parameters.rb +14 -0
  48. data/example/config/locales/en.yml +33 -0
  49. data/example/config/puma.rb +43 -0
  50. data/example/config/routes.rb +5 -0
  51. data/example/db/migrate/20210417143158_create_boops.rb +7 -0
  52. data/example/db/schema.rb +21 -0
  53. data/example/db/seeds.rb +11 -0
  54. data/example/public/404.html +67 -0
  55. data/example/public/422.html +67 -0
  56. data/example/public/500.html +66 -0
  57. data/example/public/apple-touch-icon-precomposed.png +0 -0
  58. data/example/public/apple-touch-icon.png +0 -0
  59. data/example/public/favicon.ico +0 -0
  60. data/example/public/robots.txt +1 -0
  61. data/example/test/application_system_test_case.rb +5 -0
  62. data/example/test/fixtures/boops.yml +5 -0
  63. data/example/test/integration/basic_boops_test.rb +18 -0
  64. data/example/test/integration/migrated_boops_test.rb +7 -0
  65. data/example/test/integration/parallel_boops_with_fixtures_test.rb +11 -0
  66. data/example/test/integration/parallel_boops_without_fixtures_test.rb +11 -0
  67. data/example/test/integration/updated_boops_test.rb +7 -0
  68. data/example/test/test_helper.rb +41 -0
  69. data/lib/generators/test_data/database_yaml_generator.rb +24 -0
  70. data/lib/generators/test_data/environment_file_generator.rb +29 -0
  71. data/lib/generators/test_data/webpacker_yaml_generator.rb +26 -0
  72. data/lib/test_data.rb +20 -0
  73. data/lib/test_data/active_support_ext.rb +9 -0
  74. data/lib/test_data/config.rb +52 -0
  75. data/lib/test_data/configuration_verification.rb +3 -0
  76. data/lib/test_data/configurators.rb +11 -0
  77. data/lib/test_data/configurators/database_yaml.rb +24 -0
  78. data/lib/test_data/configurators/environment_file.rb +25 -0
  79. data/lib/test_data/configurators/webpacker_yaml.rb +38 -0
  80. data/lib/test_data/detects_database_emptiness.rb +17 -0
  81. data/lib/test_data/dumps_database.rb +49 -0
  82. data/lib/test_data/error.rb +4 -0
  83. data/lib/test_data/installs_configuration.rb +9 -0
  84. data/lib/test_data/loads_database_dumps.rb +45 -0
  85. data/lib/test_data/railtie.rb +12 -0
  86. data/lib/test_data/rake.rb +104 -0
  87. data/lib/test_data/transactional_data_loader.rb +77 -0
  88. data/lib/test_data/verifies_configuration.rb +14 -0
  89. data/lib/test_data/verifies_dumps_are_loadable.rb +37 -0
  90. data/lib/test_data/version.rb +3 -0
  91. data/script/test +49 -0
  92. data/test_data.gemspec +27 -0
  93. metadata +150 -0
@@ -0,0 +1,24 @@
1
+ module TestData
2
+ module Configurators
3
+ class DatabaseYaml
4
+ def initialize
5
+ @generator = DatabaseYamlGenerator.new
6
+ @config = TestData.config
7
+ end
8
+
9
+ def verify
10
+ if @config.database_yaml.key?("test_data")
11
+ ConfigurationVerification.new(looks_good?: true)
12
+ else
13
+ ConfigurationVerification.new(problems: [
14
+ "'#{@config.database_yaml_path}' does not contain a 'test_data' section"
15
+ ])
16
+ end
17
+ end
18
+
19
+ def configure
20
+ @generator.call
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module TestData
2
+ module Configurators
3
+ class EnvironmentFile
4
+ def initialize
5
+ @generator = EnvironmentFileGenerator.new
6
+ @config = TestData.config
7
+ end
8
+
9
+ def verify
10
+ pathname = Pathname.new("#{@config.pwd}/config/environments/test_data.rb")
11
+ if pathname.readable?
12
+ ConfigurationVerification.new(looks_good?: true)
13
+ else
14
+ ConfigurationVerification.new(problems: [
15
+ "'#{pathname}' is not readable"
16
+ ])
17
+ end
18
+ end
19
+
20
+ def configure
21
+ @generator.call
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ module TestData
2
+ module Configurators
3
+ class WebpackerYaml
4
+ def initialize
5
+ @generator = WebpackerYamlGenerator.new
6
+ @config = TestData.config
7
+ end
8
+
9
+ def verify
10
+ pathname = Pathname.new("#{@config.pwd}/config/webpacker.yml")
11
+ return ConfigurationVerification.new(looks_good?: true) unless pathname.readable?
12
+ yaml = load_yaml(pathname)
13
+ if yaml.nil?
14
+ ConfigurationVerification.new(problems: [
15
+ "'#{pathname}' is not valid YAML"
16
+ ])
17
+ elsif !yaml.key?("test_data")
18
+ ConfigurationVerification.new(problems: [
19
+ "'#{pathname}' does not contain a 'test_data' section"
20
+ ])
21
+ else
22
+ ConfigurationVerification.new(looks_good?: true)
23
+ end
24
+ end
25
+
26
+ def configure
27
+ @generator.call
28
+ end
29
+
30
+ private
31
+
32
+ def load_yaml(pathname)
33
+ YAML.load_file(pathname)
34
+ rescue
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ module TestData
2
+ class DetectsDatabaseEmptiness
3
+ def initialize
4
+ @config = TestData.config
5
+ end
6
+
7
+ def empty?
8
+ result = ActiveRecord::Base.connection.execute <<~SQL
9
+ select not exists (
10
+ select from information_schema.tables
11
+ where table_name = 'ar_internal_metadata'
12
+ ) as empty
13
+ SQL
14
+ result.first["empty"]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ require "pathname"
2
+ require "fileutils"
3
+
4
+ module TestData
5
+ class DumpsDatabase
6
+ def initialize
7
+ @config = TestData.config
8
+ end
9
+
10
+ def call
11
+ dump(
12
+ type: :schema,
13
+ database_name: @config.database_name,
14
+ relative_path: @config.schema_dump_path,
15
+ full_path: @config.schema_dump_full_path
16
+ )
17
+
18
+ dump(
19
+ type: :data,
20
+ name: "test data",
21
+ database_name: @config.database_name,
22
+ relative_path: @config.data_dump_path,
23
+ full_path: @config.data_dump_full_path,
24
+ flags: @config.non_test_data_tables.map { |t| "-T #{t}" }.join(" ")
25
+ )
26
+
27
+ dump(
28
+ type: :data,
29
+ name: "non-test data",
30
+ database_name: @config.database_name,
31
+ relative_path: @config.non_test_data_dump_path,
32
+ full_path: @config.non_test_data_dump_full_path,
33
+ flags: @config.non_test_data_tables.map { |t| "-t #{t}" }.join(" ")
34
+ )
35
+ end
36
+
37
+ private
38
+
39
+ def dump(type:, database_name:, relative_path:, full_path:, name: type, flags: "")
40
+ dump_pathname = Pathname.new(full_path)
41
+ FileUtils.mkdir_p(File.dirname(dump_pathname))
42
+ if system "pg_dump #{database_name} --no-tablespaces --no-owner --inserts --#{type}-only #{flags} -f #{dump_pathname}"
43
+ puts "Dumped database '#{database_name}' #{name} to '#{relative_path}'"
44
+ else
45
+ raise "Failed while attempting to dump '#{database_name}' #{name} to '#{relative_path}'"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ module TestData
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module TestData
2
+ class InstallsConfiguration
3
+ def call
4
+ Configurators.all.each do |configurator|
5
+ configurator.configure
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ require "pathname"
2
+ require "fileutils"
3
+
4
+ module TestData
5
+ class LoadsDatabaseDumps
6
+ def initialize
7
+ @config = TestData.config
8
+ end
9
+
10
+ def call
11
+ load_dump(
12
+ name: "schema",
13
+ database_name: @config.database_name,
14
+ relative_path: @config.schema_dump_path,
15
+ full_path: @config.schema_dump_full_path
16
+ )
17
+
18
+ load_dump(
19
+ name: "test data",
20
+ database_name: @config.database_name,
21
+ relative_path: @config.data_dump_path,
22
+ full_path: @config.data_dump_full_path
23
+ )
24
+
25
+ load_dump(
26
+ name: "non-test data",
27
+ database_name: @config.database_name,
28
+ relative_path: @config.non_test_data_dump_path,
29
+ full_path: @config.non_test_data_dump_full_path
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def load_dump(name:, database_name:, relative_path:, full_path:)
36
+ dump_pathname = Pathname.new(full_path)
37
+ FileUtils.mkdir_p(File.dirname(dump_pathname))
38
+ if system "psql -q -d #{database_name} < #{dump_pathname}"
39
+ puts "Loaded #{name} from '#{relative_path}' into database '#{database_name}' "
40
+ else
41
+ raise "Failed while attempting to load #{name} from '#{relative_path}' into database '#{database_name}'"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ require "rails/railtie"
2
+ require "pathname"
3
+
4
+ module TestData
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :test_data
7
+
8
+ rake_tasks do
9
+ load Pathname.new(__dir__).join("rake.rb")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,104 @@
1
+ def wrong_env?
2
+ Rails.env != "test_data"
3
+ end
4
+
5
+ def run_in_test_data_env(task_name)
6
+ command = "RAILS_ENV=test_data #{Rails.root}/bin/rake #{task_name}"
7
+ unless system(command)
8
+ fail "An error ocurred when running: #{command}"
9
+ end
10
+ end
11
+
12
+ def create_database_or_else_blow_up_if_its_not_empty!
13
+ unless TestData::DetectsDatabaseEmptiness.new.empty?
14
+ raise TestData::Error.new("Database '#{TestData.config.database_name}' already exists and is not empty. To re-initialize it, drop it first (e.g. `rake test_data:drop_database`)")
15
+ end
16
+ rescue TestData::Error => e
17
+ raise e
18
+ rescue
19
+ # Only (anticipated) cause for raise here is DB did not exist
20
+ Rake::Task["test_data:create_database"].invoke
21
+ end
22
+
23
+ desc "Verifies test_data environment looks good"
24
+ task "test_data:verify_config" do
25
+ config = TestData::VerifiesConfiguration.new.call
26
+ unless config.looks_good?
27
+ puts "The test_data gem is not configured correctly. Try 'rake test_data:install'?\n"
28
+ config.problems.each do |problem|
29
+ puts " - #{problem}"
30
+ end
31
+ fail
32
+ end
33
+ end
34
+
35
+ desc "Install default configuration files and snippets"
36
+ task "test_data:configure" do
37
+ TestData::InstallsConfiguration.new.call
38
+ end
39
+
40
+ desc "Initialize test_data's interactive database"
41
+ task "test_data:initialize" => ["test_data:verify_config", :environment] do
42
+ next run_in_test_data_env("test_data:initialize") if wrong_env?
43
+
44
+ create_database_or_else_blow_up_if_its_not_empty!
45
+ if TestData::VerifiesDumpsAreLoadable.new.call(quiet: true)
46
+ Rake::Task["test_data:load"].invoke
47
+ else
48
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord::Base.schema_format, ENV["SCHEMA"], "test_data")
49
+ ActiveRecord::Tasks::DatabaseTasks.load_seed
50
+ end
51
+ end
52
+
53
+ desc "Initialize test_data Rails environment & configure database"
54
+ task "test_data:install" => ["test_data:configure", "test_data:initialize"]
55
+
56
+ desc "Dumps the interactive test_data database"
57
+ task "test_data:dump" => "test_data:verify_config" do
58
+ TestData::DumpsDatabase.new.call
59
+ end
60
+
61
+ desc "Dumps the interactive test_data database"
62
+ task "test_data:load" => ["test_data:verify_config", :environment] do
63
+ next run_in_test_data_env("test_data:load") if wrong_env?
64
+
65
+ create_database_or_else_blow_up_if_its_not_empty!
66
+
67
+ unless TestData::VerifiesDumpsAreLoadable.new.call
68
+ fail "Cannot load schema & data into database '#{TestData.config.database_name}'"
69
+ end
70
+
71
+ TestData::LoadsDatabaseDumps.new.call
72
+
73
+ if ActiveRecord::Base.connection.migration_context.needs_migration?
74
+ warn "Warning: There are pending migrations for database '#{TestData.config.database_name}'. To run them, run:\n\n RAILS_ENV=test_data bin/rake db:migrate\n\n"
75
+ end
76
+ end
77
+
78
+ desc "Creates the test_data interactive database"
79
+ task "test_data:create_database" => :environment do
80
+ if TestData::Configurators::DatabaseYaml.new.verify.looks_good?
81
+ ActiveRecord::Tasks::DatabaseTasks.create_current("test_data")
82
+ else
83
+ warn "Warning: 'test_data' section not defined in config/database.yml - Skipping"
84
+ end
85
+ end
86
+
87
+ # Add the test_data env to Rails' default rake db:create task
88
+ Rake::Task["db:create"].enhance do |task|
89
+ Rake::Task["test_data:create_database"].invoke
90
+ end
91
+
92
+ desc "Drops the test_data interactive database"
93
+ task "test_data:drop_database" => :environment do
94
+ if TestData::Configurators::DatabaseYaml.new.verify.looks_good?
95
+ ActiveRecord::Tasks::DatabaseTasks.drop_current("test_data")
96
+ else
97
+ warn "Warning: 'test_data' section not defined in config/database.yml - Skipping"
98
+ end
99
+ end
100
+
101
+ # Add the test_data env to Rails' default rake db:create task
102
+ Rake::Task["db:drop"].enhance do |task|
103
+ Rake::Task["test_data:drop_database"].invoke
104
+ end
@@ -0,0 +1,77 @@
1
+ require "fileutils"
2
+
3
+ module TestData
4
+ def self.load_data_dump
5
+ @transactional_data_loader ||= TransactionalDataLoader.new
6
+ @transactional_data_loader.load_data_dump
7
+ end
8
+
9
+ def self.rollback(to: :after_data_load)
10
+ raise Error.new("rollback called before load_data_dump") unless @transactional_data_loader.present?
11
+ @transactional_data_loader.rollback(to: to)
12
+ end
13
+
14
+ class TransactionalDataLoader
15
+ SavePoint = Struct.new(:name, :transaction, keyword_init: true)
16
+
17
+ def initialize
18
+ @config = TestData.config
19
+ @save_points = []
20
+ @dump_count = 0
21
+ end
22
+
23
+ def load_data_dump
24
+ create_save_point(:before_data_load) unless save_point?(:before_data_load)
25
+ unless save_point?(:after_data_load)
26
+ execute_data_dump
27
+ @dump_count += 1
28
+ create_save_point(:after_data_load)
29
+ end
30
+ end
31
+
32
+ def rollback(to:)
33
+ return unless save_point?(to)
34
+ rollback_save_point(to)
35
+ end
36
+
37
+ private
38
+
39
+ def execute_data_dump
40
+ search_path = execute("show search_path").first["search_path"]
41
+ execute(File.read(@config.data_dump_full_path))
42
+ execute <<~SQL
43
+ select pg_catalog.set_config('search_path', '#{search_path}', false)
44
+ SQL
45
+ end
46
+
47
+ def save_point?(name)
48
+ purge_closed_save_points!
49
+ @save_points.any? { |sp| sp.name == name }
50
+ end
51
+
52
+ def create_save_point(name)
53
+ save_point = SavePoint.new(
54
+ name: name,
55
+ transaction: ActiveRecord::Base.connection.begin_transaction(joinable: false, _lazy: false)
56
+ )
57
+ @save_points << save_point
58
+ end
59
+
60
+ def rollback_save_point(name)
61
+ if (save_point = @save_points.find { |sp| sp.name == name }) && save_point.transaction.open?
62
+ save_point.transaction.rollback
63
+ end
64
+ purge_closed_save_points!
65
+ end
66
+
67
+ def purge_closed_save_points!
68
+ @save_points = @save_points.select { |save_point|
69
+ save_point.transaction.open?
70
+ }
71
+ end
72
+
73
+ def execute(sql)
74
+ ActiveRecord::Base.connection.execute(sql)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,14 @@
1
+ module TestData
2
+ class VerifiesConfiguration
3
+ def call
4
+ problems = Configurators.all.flat_map { |configurator|
5
+ configurator.verify.problems
6
+ }.compact
7
+
8
+ ConfigurationVerification.new(
9
+ looks_good?: problems.none?,
10
+ problems: problems
11
+ )
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ module TestData
2
+ class VerifiesDumpsAreLoadable
3
+ def initialize
4
+ @config = TestData.config
5
+ @detects_database_emptiness = DetectsDatabaseEmptiness.new
6
+ end
7
+
8
+ def call(quiet: false)
9
+ schema_dump_looks_good = Pathname.new(@config.schema_dump_full_path).readable?
10
+ if !quiet && !schema_dump_looks_good
11
+ warn "Warning: Database schema dump '#{@config.schema_dump_path}' not readable"
12
+ end
13
+
14
+ data_dump_looks_good = Pathname.new(@config.data_dump_full_path).readable?
15
+ if !quiet && !data_dump_looks_good
16
+ warn "Warning: Database data dump '#{@config.data_dump_path}' not readable"
17
+ end
18
+
19
+ non_test_data_dump_looks_good = Pathname.new(@config.non_test_data_dump_full_path).readable?
20
+ if !quiet && !non_test_data_dump_looks_good
21
+ warn "Warning: Database non-test data dump '#{@config.non_test_data_dump_path}' not readable"
22
+ end
23
+
24
+ database_empty = @detects_database_emptiness.empty?
25
+ unless quiet || database_empty
26
+ warn "Warning: Database '#{@config.database_name}' is not empty"
27
+ end
28
+
29
+ [
30
+ schema_dump_looks_good,
31
+ data_dump_looks_good,
32
+ non_test_data_dump_looks_good,
33
+ database_empty
34
+ ].all?
35
+ end
36
+ end
37
+ end