Repository: rails/rails
File: railties/test/application/configuration_test.rb (Ruby)

Code before:
      remove_from_config '.*config\.load_defaults.*\n'

      app_file "config/initializers/new_framework_defaults_7_0.rb", <<-RUBY
        Rails.application.config.active_record.run_commit_callbacks_on_first_saved_instances_in_transaction = false
      RUBY

      app "development"

      assert_equal false, ActiveRecord::Base.run_commit_callbacks_on_first_saved_instances_in_transaction
    end

    test "Rails.application.config.active_record.use_legacy_signed_id_verifier is :generate_and_verify by default for new apps" do
      app "development"

      assert_equal :generate_and_verify, Rails.application.config.active_record.use_legacy_signed_id_verifier
    end

    test "generate signed Id with legacy options when Rails.application.config.active_record.use_legacy_signed_id_verifier is :generate_and_verify" do
      add_to_config <<-RUBY
        config.active_record.use_legacy_signed_id_verifier = :generate_and_verify
      RUBY

      app "development"

      first_rotate_option = ActiveRecord.message_verifiers.instance_variable_get(:@rotate_options).first.call("active_record/signed_id")
      assert_equal ({ digest: "SHA256", serializer: JSON, url_safe: true }), first_rotate_option
    end

    test "generate signed Id with default options when Rails.application.config.active_record.use_legacy_signed_id_verifier is :verify" do
      add_to_config <<-RUBY
        config.active_record.use_legacy_signed_id_verifier = :verify
      RUBY

      app "development"

      assert_equal ({}), ActiveRecord.message_verifiers.instance_variable_get(:@rotate_options).first

      legacy_rotate_option = ActiveRecord.message_verifiers.instance_variable_get(:@rotate_options).second.call("active_record/signed_id")
      assert_equal ({ digest: "SHA256", serializer: JSON, url_safe: true }), legacy_rotate_option
    end

    test "no legacy rotation when Rails.application.config.active_record.use_legacy_signed_id_verifier is false" do
      add_to_config <<-RUBY
        config.active_record.use_legacy_signed_id_verifier = false
      RUBY

      app "development"

      rotate_options = ActiveRecord.message_verifiers.instance_variable_get(:@rotate_options)
      assert_equal [{}], rotate_options
    end

Review comment (suggestion):
`instance_variable_get` is fragile.

Also, the rotation from the Active Record initializer is not guaranteed to be the first in `@rotate_options`.

Instead, we should use a correctly configured message verifier to verify the output of `ActiveRecord.message_verifiers["active_record/signed_id"].generate(...)` (and vice versa).

Code after:
      remove_from_config '.*config\.load_defaults.*\n'

      app_file "config/initializers/new_framework_defaults_7_0.rb", <<-RUBY
        Rails.application.config.active_record.run_commit_callbacks_on_first_saved_instances_in_transaction = false
      RUBY

      app "development"

      assert_equal false, ActiveRecord::Base.run_commit_callbacks_on_first_saved_instances_in_transaction
    end

    test "config.active_record.use_legacy_signed_id_verifier is :generate_and_verify by default for new apps" do
      app "development"

      assert_equal :generate_and_verify, Rails.application.config.active_record.use_legacy_signed_id_verifier
    end

    test "Rails.application.message_verifiers['active_record/signed_id'] generates and verifies messages using legacy options when config.active_record.use_legacy_signed_id_verifier is :generate_and_verify" do
      add_to_config <<-RUBY
        config.active_record.use_legacy_signed_id_verifier = :generate_and_verify
        config.secret_key_base = "secret"
      RUBY

      app "development"

      signed_id_verifier = Rails.application.message_verifiers["active_record/signed_id"]

      secret = app.key_generator.generate_key("active_record/signed_id")
      legacy_verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA256", serializer: JSON, url_safe: true)

      assert_equal "message", legacy_verifier.verify(signed_id_verifier.generate("message"))
      assert_equal "message", signed_id_verifier.verify(legacy_verifier.generate("message"))
    end

    test "Rails.application.message_verifiers['active_record/signed_id'] verifies messages using legacy options when config.active_record.use_legacy_signed_id_verifier is :verify" do
      add_to_config <<-RUBY
        config.active_record.use_legacy_signed_id_verifier = :verify
        config.secret_key_base = "secret"
      RUBY

      app "development"

      signed_id_verifier = Rails.application.message_verifiers["active_record/signed_id"]

      secret = app.key_generator.generate_key("active_record/signed_id")
      legacy_verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA256", serializer: JSON, url_safe: true)

      assert_equal "message", signed_id_verifier.verify(legacy_verifier.generate("message"))
      assert_raises ActiveSupport::MessageVerifier::InvalidSignature do
        legacy_verifier.verify(signed_id_verifier.generate("message"))
      end