vcr.rb 4.4 KB

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