|
class TestLock |
|
cattr_accessor :requesting |
|
self.requesting = false |
|
|
|
cattr_accessor :async_requests |
|
self.async_requests = [] |
|
|
|
cattr_accessor :locked |
|
self.locked = false |
|
|
|
def initialize(app) |
|
@app = app |
|
end |
|
|
|
def call(env) |
|
request = AuditedRequest.new(env) |
|
if name_and_location = locked |
|
unless request.ignorable_asset? |
|
message = "Ignoring request at the end of #{name_and_location} because we are tearing down the test. The ignored request was: #{request.method_and_path}" |
|
Rails.logger.tagged('TEST_LOCK') { Rails.logger.debug(message) } |
|
end |
|
[409, {}, [message]] |
|
else |
|
begin |
|
self.requesting = request |
|
|
|
ActiveRecord::Base.connection.with do |
|
response = @app.call(env) |
|
request.rack_response = response |
|
response |
|
end |
|
ensure |
|
async_requests << request if request.should_track? |
|
self.requesting = false |
|
end |
|
end |
|
end |
|
|
|
class << self |
|
def reset |
|
self.requesting = false |
|
self.locked = false |
|
self.async_requests = [] |
|
end |
|
|
|
def join(name_and_location) |
|
self.locked = name_and_location |
|
reported = false |
|
Timeout.timeout(15) do |
|
loop do |
|
if request = requesting |
|
unless reported |
|
Rails.logger.tagged('TEST_LOCK') { Rails.logger.debug("Blocking at the end of #{name_and_location}. The blocked request was: #{request.method_and_path}") } |
|
reported = true |
|
end |
|
sleep(1) |
|
else |
|
break |
|
end |
|
end |
|
end |
|
Rails.logger.tagged('TEST_LOCK') { Rails.logger.debug('TestLock is finished') } |
|
end |
|
|
|
def find_async_request(*args) |
|
async_requests.detect do |async_request| |
|
async_request.matches?(*args) |
|
end |
|
end |
|
end |
|
|
|
class AuditedRequest |
|
attr_reader :rails_request, :rails_response |
|
delegate :url, :request_method, to: :rails_request |
|
|
|
def initialize(rack_env) |
|
@rails_request = ActionDispatch::Request.new(rack_env) |
|
@found = false |
|
end |
|
|
|
def rack_response=(rack_response) |
|
@rails_response = ActionDispatch::Response.new(*rack_response) |
|
end |
|
|
|
def should_track? |
|
@rails_request.xhr? || @rails_request.format.json? |
|
end |
|
|
|
def matches?(url, params_contains:, method:, successful:) |
|
return if found? |
|
return false unless successful == response_successful? |
|
return false unless Rack::Utils.unescape(@rails_request.url).downcase.include?(Rack::Utils.unescape(url).downcase) |
|
return false unless @rails_request.request_method == method.to_s.upcase |
|
return false unless !params_contains || querystring_contains?(params_contains) |
|
|
|
true |
|
end |
|
|
|
def found! |
|
@found = true |
|
end |
|
|
|
def found? |
|
!!@found |
|
end |
|
|
|
def method_and_path |
|
"#{@rails_request.request_method} #{@rails_request.fullpath} [status: #{@rails_response.try(:status)}]" |
|
end |
|
|
|
def response_successful? |
|
if @rails_response |
|
200 <= @rails_response.status && @rails_response.status < 300 |
|
end |
|
end |
|
|
|
def ignorable_asset? |
|
@rails_request.path.include?("/assets/") |
|
end |
|
|
|
private |
|
|
|
def querystring_contains?(querystring_hash) |
|
page_querystring = Rack::Utils.parse_query(URI.parse(@rails_request.url).query) |
|
querystring_hash.all? { |key, value| page_querystring[key.to_s] == value.to_s } |
|
end |
|
end |
|
end |