Files
redux-scraper/spec/helpers/http_client_mock_helpers.rb
2025-07-25 04:20:40 +00:00

174 lines
5.1 KiB
Ruby

# typed: true
class HttpClientMockHelpers
extend T::Sig
include HasColorLogger
extend FactoryBot::Syntax::Methods
extend RSpec::Mocks::ExampleMethods
extend RSpec::Matchers
def self.init_from_manifest(http_client_mock, manifest_directory_path)
manifest =
YAML.safe_load(
File.read(Rails.root.join(manifest_directory_path, "manifest.yml")),
)
requests =
manifest.map do |request_info|
{
uri: request_info["url"],
content_type:
(
if request_info["url"].include?(".json")
"application/json"
else
"text/html"
end
),
contents:
File.read(
Rails.root.join(manifest_directory_path, request_info["file"]),
),
status_code: request_info["status"],
method: request_info["method"],
caused_by_entry: :any,
requested_at: request_info["requested_at"],
}
end
HttpClientMockHelpers.init_http_client_mock(
http_client_mock,
requests,
any_order: true,
)
end
sig do
params(
http_client_mock: Scraper::HttpClient,
requests: T::Array[T.untyped],
any_order: T::Boolean,
).returns(T::Array[HttpLogEntry])
end
def self.init_http_client_mock(http_client_mock, requests, any_order: false)
if any_order
init_any_order(http_client_mock, requests)
else
init_ordered(http_client_mock, requests)
end
end
sig do
params(requests: T::Array[T.untyped], any_order: T::Boolean).returns(
T::Array[HttpLogEntry],
)
end
def self.init_with(requests, any_order: false)
if any_order
init_any_order(Scraper::ClientFactory.http_client_mock, requests)
else
init_ordered(Scraper::ClientFactory.http_client_mock, requests)
end
end
def self.init_ordered(http_client_mock, requests)
log_entries = []
requests.each do |request|
sha256 = Digest::SHA256.digest(request[:contents])
log_entry =
build(
:http_log_entry,
uri: request[:uri],
verb: :get,
content_type: request[:content_type],
status_code: request[:status_code] || 200,
performed_by: "direct",
response_time_ms: rand(20..100),
requested_at: request[:requested_at] || Time.now,
request_headers: build(:http_log_entry_header),
response_headers: build(:http_log_entry_header),
response:
BlobFile.find_by(sha256: sha256) ||
build(
:blob_file,
content_type: request[:content_type],
contents: request[:contents],
),
)
log_entry.save!
log_entries << log_entry
caused_by_entry = nil
if request[:caused_by_entry_idx]
caused_by_entry = log_entries[request[:caused_by_entry_idx]]
elsif request[:caused_by_entry] == :any
caused_by_entry = :any
elsif request[:caused_by_entry]
caused_by_entry = request[:caused_by_entry]
end
method = request[:method] || :get
expect(http_client_mock).to(
receive(method).with(
log_entry.uri.to_s,
http_client_opts_with(
caused_by_entry: caused_by_entry,
use_http_cache: request[:use_http_cache],
),
) do |uri, opts|
logger.info "[mock http client] [#{method}] [#{uri}] [#{opts.inspect.truncate(80)}]"
Scraper::HttpClient::Response.new(
status_code: log_entry.status_code,
body: log_entry.response.content_bytes,
log_entry: log_entry,
)
end,
)
end
log_entries
end
def self.init_any_order(http_client_mock, requests)
log_entries_by_uri =
requests
.map do |request|
sha256 = Digest::SHA256.digest(request[:contents])
log_entry =
create(
:http_log_entry,
uri: request[:uri],
verb: :get,
content_type: request[:content_type],
status_code: request[:status_code] || 200,
performed_by: "direct",
response_time_ms: rand(20..100),
response:
BlobFile.find_by(sha256: sha256) ||
build(
:blob_file,
content_type: request[:content_type],
contents: request[:contents],
),
)
[request[:uri], log_entry]
end
.to_h
allow(http_client_mock).to receive(:get) do |uri, opts|
log_entry = log_entries_by_uri[uri]
expect(log_entry).not_to(
be_nil,
"no log entry found for uri: #{uri}\nValid uris: \n#{log_entries_by_uri.keys.join("\n")}",
)
logger.info "[mock http client] [get] [#{uri}] [#{opts.inspect.truncate(80)}]"
Scraper::HttpClient::Response.new(
status_code: log_entry.status_code,
body: log_entry.response.content_bytes,
log_entry: log_entry,
)
end
log_entries_by_uri.values
end
end