vcr.rb 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'vcr'
  3. VCR_IGNORE_MATCHING_HOSTS = %w[build elasticsearch selenium ci-service- zammad.org zammad.com znuny.com google.com login.microsoftonline.com github.com badssl.com].freeze
  4. VCR_IGNORE_MATCHING_REGEXPS = [
  5. %r{^192\.168\.\d+\.\d+$}, # typical home network address
  6. %r{^172\.17\.0\.\d+$}, # docker
  7. ].freeze
  8. VCR.configure do |config|
  9. config.cassette_library_dir = 'test/data/vcr_cassettes'
  10. config.hook_into :webmock
  11. config.allow_http_connections_when_no_cassette = %w[1 true].include?(ENV['CI_IGNORE_CASSETTES'])
  12. config.ignore_localhost = true
  13. config.ignore_request do |request|
  14. uri = URI(request.uri)
  15. next true if VCR_IGNORE_MATCHING_HOSTS.any? { |elem| uri.host.include? elem }
  16. next true if VCR_IGNORE_MATCHING_REGEXPS.any? { |elem| uri.host.match? elem }
  17. end
  18. config.register_request_matcher(:oauth_headers) do |r1, r2|
  19. without_onetime_oauth_params = ->(params) { params.gsub(%r{oauth_(nonce|signature|timestamp)="[^"]+", }, '') }
  20. r1.headers.except('Authorization') == r2.headers.except('Authorization') &&
  21. r1.headers['Authorization']&.map(&without_onetime_oauth_params) ==
  22. r2.headers['Authorization']&.map(&without_onetime_oauth_params)
  23. end
  24. end
  25. module RSpec
  26. VCR_ADVISORY = <<~MSG.freeze
  27. If this test is failing unexpectedly, the VCR cassette may be to blame.
  28. This can happen when changing `describe`/`context` labels on some specs;
  29. see commit message 1ebddff95 for details.
  30. Check `git status` to see if a new VCR cassette has been generated.
  31. If so, rename the old cassette to replace the new one and try again.
  32. MSG
  33. module Support
  34. module VCRHelper
  35. def self.inject_advisory(example)
  36. # block argument is an #<RSpec::Expectations::ExpectationNotMetError>
  37. define_method(:notify_failure) do |e, options = {}|
  38. super(e.exception(VCR_ADVISORY + e.message), options)
  39. end
  40. example.run
  41. ensure
  42. remove_method(:notify_failure)
  43. end
  44. end
  45. singleton_class.send(:prepend, VCRHelper)
  46. end
  47. module Expectations
  48. module VCRHelper
  49. def self.inject_advisory(example)
  50. define_method(:handle_matcher) do |*args|
  51. super(*args)
  52. rescue => e
  53. raise e.exception(VCR_ADVISORY + e.message)
  54. end
  55. example.run
  56. ensure
  57. remove_method(:handle_matcher)
  58. end
  59. end
  60. PositiveExpectationHandler.singleton_class.send(:prepend, VCRHelper)
  61. NegativeExpectationHandler.singleton_class.send(:prepend, VCRHelper)
  62. end
  63. end
  64. RSpec.configure do |config|
  65. config.around(:each, use_vcr: true) do |example|
  66. # S3 does not play well with time freezing (Aws::S3::Errors::RequestTimeTooSkewed).
  67. Setting.set('storage_provider', 'DB') if Setting.get('storage_provider') == 'S3'
  68. # Perform live integration tests without using VCR cassettes if CI_IGNORE_CASSETTES is set.
  69. if example.metadata[:integration] && %w[1 true].include?(ENV['CI_IGNORE_CASSETTES'])
  70. next VCR.turned_off(ignore_cassettes: true) do
  71. WebMock.disable!
  72. example.run
  73. ensure
  74. WebMock.enable!
  75. end
  76. end
  77. vcr_options = Array(example.metadata[:use_vcr])
  78. spec_path = Pathname.new(example.file_path).realpath
  79. cassette_path = spec_path.relative_path_from(Rails.root.join('spec')).sub(%r{_spec\.rb$}, '')
  80. cassette_name = "#{example.metadata[:example_group][:full_description]}/#{example.description}".gsub(%r{[^0-9A-Za-z-]+}, '_').downcase
  81. # handle file name limit of 255 chars
  82. if cassette_name.length > 253
  83. hexdigest_cassette_name = Digest::SHA256.hexdigest(cassette_name)
  84. shortened_casset_name = "#{cassette_name.first(30)}-#{cassette_name.last(30)}-#{hexdigest_cassette_name}"
  85. Rails.logger.info "Detected too long VCR filename '#{cassette_name}' (#{cassette_name.length}) and therefore converted it to '#{shortened_casset_name}'"
  86. cassette_name = shortened_casset_name
  87. end
  88. request_profile = [
  89. :method,
  90. :uri,
  91. vcr_options.include?(:with_oauth_headers) ? :oauth_headers : nil
  92. ].compact
  93. VCR.use_cassette(cassette_path.join(cassette_name), match_requests_on: request_profile, allow_playback_repeats: true) do |cassette|
  94. if vcr_options.include?(:time_sensitive) && !cassette.recording?
  95. travel_to(cassette.http_interactions.interactions.first.recorded_at)
  96. end
  97. example.run
  98. end
  99. end
  100. config.around(:each, use_vcr: true) do |example|
  101. RSpec::Support::VCRHelper.inject_advisory(example)
  102. end
  103. config.around(:each, use_vcr: true) do |example|
  104. RSpec::Expectations::VCRHelper.inject_advisory(example)
  105. end
  106. end