vcr.rb 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. require 'vcr'
  3. VCR_IGNORE_MATCHING_HOSTS = %w[elasticsearch selenium ci-service- zammad.org zammad.com znuny.com google.com login.microsoftonline.com github.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. # Perform live integration tests without using VCR cassettes if CI_IGNORE_CASSETTES is set.
  67. if example.metadata[:integration] && %w[1 true].include?(ENV['CI_IGNORE_CASSETTES'])
  68. next VCR.turned_off(ignore_cassettes: true) do
  69. WebMock.disable!
  70. example.run
  71. ensure
  72. WebMock.enable!
  73. end
  74. end
  75. vcr_options = Array(example.metadata[:use_vcr])
  76. spec_path = Pathname.new(example.file_path).realpath
  77. cassette_path = spec_path.relative_path_from(Rails.root.join('spec')).sub(%r{_spec\.rb$}, '')
  78. cassette_name = "#{example.metadata[:example_group][:full_description]}/#{example.description}".gsub(%r{[^0-9A-Za-z-]+}, '_').downcase
  79. # handle file name limit of 255 chars
  80. if cassette_name.length > 253
  81. hexdigest_cassette_name = Digest::SHA256.hexdigest(cassette_name)
  82. shortened_casset_name = "#{cassette_name.first(30)}-#{cassette_name.last(30)}-#{hexdigest_cassette_name}"
  83. Rails.logger.info "Detected too long VCR filename '#{cassette_name}' (#{cassette_name.length}) and therefore converted it to '#{shortened_casset_name}'"
  84. cassette_name = shortened_casset_name
  85. end
  86. request_profile = [
  87. :method,
  88. :uri,
  89. vcr_options.include?(:with_oauth_headers) ? :oauth_headers : nil
  90. ].compact
  91. VCR.use_cassette(cassette_path.join(cassette_name), match_requests_on: request_profile) do |cassette|
  92. if vcr_options.include?(:time_sensitive) && !cassette.recording?
  93. travel_to(cassette.http_interactions.interactions.first.recorded_at)
  94. end
  95. example.run
  96. end
  97. end
  98. config.around(:each, use_vcr: true) do |example|
  99. RSpec::Support::VCRHelper.inject_advisory(example)
  100. end
  101. config.around(:each, use_vcr: true) do |example|
  102. RSpec::Expectations::VCRHelper.inject_advisory(example)
  103. end
  104. end