db_migration.rb 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. # require all database migrations so we can test them without manual require
  3. Rails.root.join('db/migrate').children.each do |migration|
  4. require migration.to_s
  5. end
  6. module DbMigrationHelper
  7. # Provides a helper method to execute a migration for the current class.
  8. # Make sure to define type: :db_migration in your RSpec.describe call.
  9. #
  10. # @param [Symbol] direction the migration should take (:up or :down)
  11. # @yield [instance] Yields the created instance of the
  12. # migration to allow expectations or other changes to it
  13. #
  14. # @example
  15. # migrate
  16. #
  17. # @example
  18. # migrate(:down)
  19. #
  20. # @return [nil]
  21. def migrate(direction = :up)
  22. instance = described_class.new
  23. yield(instance) if block_given?
  24. instance.suppress_messages do
  25. instance.migrate(direction)
  26. end
  27. end
  28. # Provides a helper method to remove foreign_keys if exist.
  29. # Make sure to define type: :db_migration in your RSpec.describe call
  30. # and add `self.use_transactional_tests = false` to your context.
  31. #
  32. # ATTENTION: We do not use the same arguments as the internally
  33. # used methods since giving a table name
  34. # as a second argument as e.g.
  35. # `remove_foreign_key(:online_notifications, :users)`
  36. # doesn't remove the index at first execution on at least MySQL
  37. #
  38. # @param [Symbol] from_table the name of the table with the foreign_key column
  39. # @param [Symbol] column the name of the foreign_key column
  40. #
  41. # @example
  42. # without_foreign_key(:online_notifications, column: :user_id)
  43. #
  44. # @return [nil]
  45. def without_foreign_key(from_table, column:)
  46. suppress_messages do
  47. break if !foreign_key_exists?(from_table, column: column)
  48. remove_foreign_key(from_table, column: column)
  49. end
  50. end
  51. # Helper method for setting up specs on DB migrations that add columns.
  52. # Make sure to define type: :db_migration in your RSpec.describe call
  53. # and add `self.use_transactional_tests = false` to your context.
  54. #
  55. # @param [Symbol] from_table the name of the table with the indexed column
  56. # @param [Symbol] name(s) of indexed column(s)
  57. #
  58. # @example
  59. # without_column(:online_notifications, column: :user_id)
  60. #
  61. # @return [nil]
  62. def without_column(from_table, column:)
  63. suppress_messages do
  64. Array(column).each do |elem|
  65. next if !column_exists?(from_table, elem)
  66. remove_column(from_table, elem)
  67. end
  68. end
  69. end
  70. # Helper method for setting up specs on DB migrations that add indices.
  71. # Make sure to define type: :db_migration in your RSpec.describe call
  72. # and add `self.use_transactional_tests = false` to your context.
  73. #
  74. # @param [Symbol] from_table the name of the table with the indexed column
  75. # @param [Symbol] name(s) of indexed column(s)
  76. #
  77. # @example
  78. # without_index(:online_notifications, column: :user_id)
  79. #
  80. # @return [nil]
  81. def without_index(from_table, column:)
  82. suppress_messages do
  83. break if !index_exists?(from_table, column)
  84. remove_index(from_table, column: column)
  85. end
  86. end
  87. # Enables the usage of `ActiveRecord::Migration` methods.
  88. #
  89. # @see ActiveRecord::Migration
  90. #
  91. # @example
  92. # remove_foreign_key(:online_notifications, :users)
  93. #
  94. # @return [nil]
  95. def method_missing(method, ...)
  96. ActiveRecord::Migration.send(method, ...)
  97. rescue NoMethodError
  98. super
  99. end
  100. # Enables the usage of `ActiveRecord::Migration` methods.
  101. #
  102. # @see ActiveRecord::Migration
  103. #
  104. # @example
  105. # remove_foreign_key(:online_notifications, :users)
  106. #
  107. # @return [nil]
  108. def respond_to_missing?(...)
  109. true
  110. end
  111. # Provides a helper method to check if migration class adds a foreign key.
  112. # Make sure to define type: :db_migration in your RSpec.describe call
  113. # and add `self.use_transactional_tests = false` to your context.
  114. #
  115. # @param [Symbol] from_table the name of the table with the foreign_key column
  116. # @param [Symbol] column the name of the foreign_key column
  117. #
  118. # @example
  119. # adds_foreign_key(:online_notifications, column: :user_id)
  120. #
  121. # @return [nil]
  122. def adds_foreign_key(from_table, column:)
  123. without_foreign_key(from_table, column: column)
  124. suppress_messages do
  125. expect do
  126. migrate
  127. end.to change {
  128. foreign_key_exists?(from_table, column: column)
  129. }
  130. end
  131. end
  132. def self.included(base)
  133. # Execute in RSpec class context
  134. base.class_exec do
  135. # This method simulates a system that is is already initialized
  136. # aka `Setting.exists?(name: 'system_init_done')`
  137. # It's possible to simulate a not yet initialized system by adding the
  138. # meta tag `system_init_done` to `false` to the needing example:
  139. #
  140. # @example
  141. # it 'does stuff in an unitialized system', system_init_done: false do
  142. #
  143. before do |example|
  144. initialized = example.metadata.fetch(:system_init_done, true)
  145. system_init_done(initialized)
  146. end
  147. end
  148. end
  149. end
  150. RSpec.configure do |config|
  151. config.include DbMigrationHelper, type: :db_migration
  152. end