diff --git a/.github/workflows/ci_steps.yml b/.github/workflows/ci_steps.yml index 2554bdb9..42734c56 100644 --- a/.github/workflows/ci_steps.yml +++ b/.github/workflows/ci_steps.yml @@ -10,70 +10,32 @@ jobs: strategy: fail-fast: false matrix: - ruby: ['2.6'] + ruby: ['3.3.10'] env: - QT_SELECT: qt5 - OPENSSL_CONF: /dev/null LANG: C.UTF-8 LC_ALL: C.UTF-8 steps: - - name: Fix APT for EOL Debian - run: | - if grep -q buster /etc/apt/sources.list 2>/dev/null; then - echo "deb [trusted=yes] http://archive.debian.org/debian buster main" > /etc/apt/sources.list - echo "deb [trusted=yes] http://archive.debian.org/debian-security buster/updates main" >> /etc/apt/sources.list - fi - echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99archive - echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99archive - - name: Install system packages run: | apt-get update -qq apt-get install -y --no-install-recommends \ - git ca-certificates curl bzip2 \ - build-essential g++ make \ - qt5-qmake qtbase5-dev libqt5webkit5-dev qtchooser \ - xvfb xauth libfontconfig1 libfreetype6 libxrender1 libxext6 libx11-6 \ + git ca-certificates curl \ + build-essential \ + chromium chromium-driver \ libssl-dev zlib1g-dev - - name: Setup qmake - run: | - mkdir -p /usr/share/qtchooser - echo "/usr/lib/x86_64-linux-gnu/qt5/bin" > /usr/share/qtchooser/qt5.conf - echo "/usr/lib/x86_64-linux-gnu" >> /usr/share/qtchooser/qt5.conf - ln -sf /usr/lib/x86_64-linux-gnu/qt5/bin/qmake /usr/bin/qmake 2>/dev/null || true - qmake --version - - - name: Install PhantomJS - run: | - curl -L -o phantomjs.tar.bz2 "https://github.com/Medium/phantomjs/releases/download/v2.1.1/phantomjs-2.1.1-linux-x86_64.tar.bz2" - tar xf phantomjs.tar.bz2 --wildcards '*/bin/phantomjs' --strip-components=2 - mv phantomjs /usr/local/bin/ - rm phantomjs.tar.bz2 - phantomjs --version || echo "PhantomJS installed" - - name: Checkout uses: actions/checkout@v4 - name: Install Bundler - run: gem install bundler -v '< 2' + run: gem install bundler - name: Bundle install - run: | - bundle config set force_ruby_platform true - echo "gem 'ffi', '~> 1.15.0'" >> Gemfile - echo "gem 'regexp_parser', '~> 1.8'" >> Gemfile - echo "gem 'public_suffix', '~> 4.0'" >> Gemfile - echo "gem 'capybara', '~> 3.35.0'" >> Gemfile - echo "gem 'addressable', '~> 2.7.0'" >> Gemfile - echo "gem 'nokogiri', '~> 1.12.0'" >> Gemfile - echo "gem 'mini_mime', '~> 1.1.0'" >> Gemfile - echo "gem 'rack', '~> 2.2.0'" >> Gemfile - bundle install --jobs 4 --retry 3 - env: - QMAKE: /usr/bin/qmake + run: bundle install --jobs 4 --retry 3 - name: Run tests - run: xvfb-run -a bundle exec rspec spec + run: bundle exec rspec spec + env: + CHROME_BIN: /usr/bin/chromium diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..adb7a54f --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,5 @@ +plugins: + - rubocop-rspec + - rubocop-capybara + +inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000..b9a95755 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,248 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2026-02-26 07:29:32 UTC using RuboCop version 1.84.2. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +# Configuration parameters: AllowComments, AllowNil. +Lint/SuppressedException: + Exclude: + - 'lib/billy/browsers/capybara.rb' + +# Offense count: 1 +# Configuration parameters: AllowKeywordBlockArguments. +Lint/UnderscorePrefixedVariableName: + Exclude: + - 'lib/billy/cache.rb' + +# Offense count: 16 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 54 + +# Offense count: 1 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. +# AllowedMethods: refine +Metrics/BlockLength: + Max: 69 + +# Offense count: 2 +# Configuration parameters: CountBlocks, CountModifierForms. +Metrics/BlockNesting: + Max: 4 + +# Offense count: 2 +# Configuration parameters: CountComments, CountAsOne. +Metrics/ClassLength: + Max: 121 + +# Offense count: 2 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/CyclomaticComplexity: + Max: 11 + +# Offense count: 15 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +Metrics/MethodLength: + Max: 45 + +# Offense count: 1 +# Configuration parameters: CountKeywordArgs, MaxOptionalParameters. +Metrics/ParameterLists: + Max: 9 + +# Offense count: 2 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/PerceivedComplexity: + Max: 14 + +# Offense count: 1 +Naming/AccessorMethodName: + Exclude: + - 'spec/support/test_server.rb' + +# Offense count: 1 +# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms. +# CheckDefinitionPathHierarchyRoots: lib, spec, test, src +# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS +Naming/FileName: + Exclude: + - 'Rakefile.rb' + - 'lib/puffing-billy.rb' + +# Offense count: 1 +# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs. +# NamePrefix: is_, has_, have_, does_ +# ForbiddenPrefixes: is_, has_, have_, does_ +# AllowedMethods: is_a? +# MethodDefinitionMacros: define_method, define_singleton_method +Naming/PredicatePrefix: + Exclude: + - 'spec/**/*' + - 'lib/billy/proxy_request_stub.rb' + +# Offense count: 4 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 +Naming/VariableNumber: + Exclude: + - 'spec/lib/proxy_spec.rb' + +# Offense count: 23 +RSpec/Be: + Exclude: + - 'spec/lib/billy/proxy_request_stub_spec.rb' + +# Offense count: 29 +# Configuration parameters: Prefixes, AllowedPatterns. +# Prefixes: when, with, without +RSpec/ContextWording: + Exclude: + - 'spec/lib/billy/cache_spec.rb' + - 'spec/lib/billy/handlers/cache_handler_spec.rb' + - 'spec/lib/billy/handlers/proxy_handler_spec.rb' + - 'spec/lib/billy/proxy_request_stub_spec.rb' + - 'spec/lib/proxy_spec.rb' + +# Offense count: 50 +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 22 + +# Offense count: 7 +RSpec/ExpectInHook: + Exclude: + - 'spec/lib/billy/handlers/proxy_handler_spec.rb' + +# Offense count: 8 +# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. +RSpec/IndexedLet: + Exclude: + - 'spec/lib/billy/cache_spec.rb' + - 'spec/lib/billy/ssl/authority_spec.rb' + - 'spec/lib/billy/ssl/certificate_chain_spec.rb' + - 'spec/lib/billy/ssl/certificate_spec.rb' + +# Offense count: 20 +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: + Exclude: + - 'spec/lib/billy/watir/watir_spec.rb' + - 'spec/lib/proxy_spec.rb' + +# Offense count: 15 +RSpec/LetSetup: + Exclude: + - 'spec/lib/billy/handlers/stub_handler_spec.rb' + - 'spec/lib/proxy_spec.rb' + +# Offense count: 62 +# Configuration parameters: . +# SupportedStyles: have_received, receive +RSpec/MessageSpies: + EnforcedStyle: receive + +# Offense count: 41 +RSpec/MultipleExpectations: + Max: 11 + +# Offense count: 10 +# Configuration parameters: AllowSubject. +RSpec/MultipleMemoizedHelpers: + Max: 15 + +# Offense count: 79 +# Configuration parameters: EnforcedStyle, IgnoreSharedExamples. +# SupportedStyles: always, named_only +RSpec/NamedSubject: + Exclude: + - 'spec/lib/billy/handlers/proxy_handler_spec.rb' + - 'spec/lib/billy/handlers/request_handler_spec.rb' + - 'spec/lib/billy/proxy_connection_spec.rb' + - 'spec/lib/billy/proxy_request_stub_spec.rb' + +# Offense count: 12 +# Configuration parameters: AllowedGroups. +RSpec/NestedGroups: + Max: 5 + +# Offense count: 2 +RSpec/RepeatedExampleGroupBody: + Exclude: + - 'spec/lib/billy/handlers/request_handler_spec.rb' + +# Offense count: 10 +# Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector. +# SupportedInflectors: default, active_support +RSpec/SpecFilePathFormat: + Exclude: + - '**/spec/routing/**/*' + - 'spec/lib/billy/handlers/cache_handler_spec.rb' + - 'spec/lib/billy/handlers/handler_spec.rb' + - 'spec/lib/billy/handlers/proxy_handler_spec.rb' + - 'spec/lib/billy/handlers/request_handler_spec.rb' + - 'spec/lib/billy/handlers/request_log_spec.rb' + - 'spec/lib/billy/handlers/stub_handler_spec.rb' + - 'spec/lib/billy/resource_utils_spec.rb' + - 'spec/lib/billy/ssl/authority_spec.rb' + - 'spec/lib/billy/ssl/certificate_chain_spec.rb' + - 'spec/lib/billy/ssl/certificate_spec.rb' + +# Offense count: 42 +RSpec/StubbedMock: + Exclude: + - 'spec/lib/billy/handlers/cache_handler_spec.rb' + - 'spec/lib/billy/handlers/proxy_handler_spec.rb' + - 'spec/lib/billy/handlers/request_handler_spec.rb' + - 'spec/lib/billy/handlers/stub_handler_spec.rb' + +# Offense count: 4 +RSpec/SubjectDeclaration: + Exclude: + - 'spec/lib/billy/proxy_connection_spec.rb' + - 'spec/lib/billy/proxy_request_stub_spec.rb' + +# Offense count: 3 +RSpec/SubjectStub: + Exclude: + - 'spec/lib/billy/handlers/proxy_handler_spec.rb' + - 'spec/lib/billy/handlers/request_handler_spec.rb' + +# Offense count: 6 +# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. +RSpec/VerifiedDoubles: + Exclude: + - 'spec/lib/billy/handlers/proxy_handler_spec.rb' + - 'spec/lib/billy/handlers/request_handler_spec.rb' + - 'spec/lib/billy/handlers/stub_handler_spec.rb' + +# Offense count: 19 +# Configuration parameters: AllowedConstants. +Style/Documentation: + Enabled: false + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/IfUnlessModifier: + Exclude: + - 'lib/billy/handlers/proxy_handler.rb' + +# Offense count: 4 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/billy/cache.rb' + - 'lib/billy/proxy.rb' + - 'spec/support/test_server.rb' + +# Offense count: 22 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings. +# URISchemes: http, https +Layout/LineLength: + Max: 158 diff --git a/Dockerfile b/Dockerfile index fe2ff43c..d6f27e6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,16 @@ -FROM ruby:1.9.3 +FROM ruby:3.3.10 + +RUN apt-get update -qq && \ + apt-get install -y --no-install-recommends \ + git ca-certificates curl \ + build-essential \ + chromium chromium-driver \ + libssl-dev zlib1g-dev && \ + rm -rf /var/lib/apt/lists/* + +ENV CHROME_BIN=/usr/bin/chromium -RUN apt-get update -y -RUN apt-get install -y qt5-default libqt5webkit5-dev gstreamer1.0-plugins-base gstreamer1.0-tools gstreamer1.0-x RUN gem install bundler -RUN \ - export PHANTOMJS_VERSION='2.1.1' && \ - export PHANTOMJS_URL='https://github.com/Medium/phantomjs/releases/download/v2.1.1/phantomjs-2.1.1-linux-x86_64.tar.bz2' && \ - wget -q ${PHANTOMJS_URL} && \ - tar xfv phantomjs-${PHANTOMJS_VERSION}-linux-x86_64.tar.bz2 \ - -C /usr/bin --wildcards */bin/phantomjs --strip-components=2 RUN mkdir -p /app COPY . /app RUN cd /app && bundle install diff --git a/Gemfile b/Gemfile index b52c54e0..c3b7ebde 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in puffing-billy.gemspec diff --git a/Guardfile b/Guardfile index a3fbf805..464f84ba 100644 --- a/Guardfile +++ b/Guardfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # A sample Guardfile # More info at https://github.com/guard/guard#readme @@ -9,7 +11,9 @@ guard 'rspec', version: 2 do # Rails example watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } - watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } + watch(%r{^app/controllers/(.+)_(controller)\.rb$}) do |m| + ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] + end watch(%r{^spec/support/(.+)\.rb$}) { 'spec' } watch('config/routes.rb') { 'spec/routing' } watch('app/controllers/application_controller.rb') { 'spec/controllers' } @@ -19,5 +23,7 @@ guard 'rspec', version: 2 do # Turnip features and steps watch(%r{^spec/acceptance/(.+)\.feature$}) - watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } + watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| + Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' + end end diff --git a/README.md b/README.md index 8abd7901..6af5f076 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Puffing Billy [![Gem Version](https://badge.fury.io/rb/puffing-billy.svg)](https://badge.fury.io/rb/puffing-billy) [![Build Status](https://travis-ci.org/oesmith/puffing-billy.svg?branch=master)](https://travis-ci.org/oesmith/puffing-billy) +# Puffing Billy [![Gem Version](https://badge.fury.io/rb/puffing-billy.svg)](https://badge.fury.io/rb/puffing-billy) [![Build Status](https://github.com/oesmith/puffing-billy/actions/workflows/ci_steps.yml/badge.svg)](https://github.com/oesmith/puffing-billy/actions/workflows/ci_steps.yml) A rewriting web proxy for testing interactions between your browser and -external sites. Works with ruby + rspec. +external sites. Works with ruby (>= 3.3.10) + rspec. Puffing Billy is like [webmock](https://github.com/bblimke/webmock) or [VCR](https://github.com/vcr/vcr), but for your browser. @@ -30,6 +30,11 @@ end You can also record HTTP interactions and replay them later. See [caching](#caching) below. +## Requirements + +- Ruby >= 3.3.10 +- Chrome/Chromium (recommended) or Firefox for browser-based tests + ## Installation Add this line to your application's Gemfile: @@ -53,20 +58,14 @@ In your `rails_helper.rb`: ```ruby require 'billy/capybara/rspec' -# select a driver for your chosen browser environment -Capybara.javascript_driver = :selenium_billy # Uses Firefox -# Capybara.javascript_driver = :selenium_chrome_billy -# Capybara.javascript_driver = :selenium_chrome_headless_billy -# Capybara.javascript_driver = :apparition_billy -# Capybara.javascript_driver = :webkit_billy -# Capybara.javascript_driver = :poltergeist_billy +# Select a driver for your chosen browser environment. +# The :selenium_chrome_billy and :selenium_chrome_headless_billy drivers +# respect the CHROME_BIN environment variable for custom Chrome/Chromium paths. +Capybara.javascript_driver = :selenium_billy # Firefox +# Capybara.javascript_driver = :selenium_chrome_billy # Chrome +# Capybara.javascript_driver = :selenium_chrome_headless_billy # Chrome (headless, recommended for CI) ``` -> __Note__: `:poltergeist_billy` doesn't support proxying any localhosts, so you must use -`:webkit_billy`, `:apparition_billy`, or a custom headless selenium registration for -headless specs when using puffing-billy for other local rack apps. -See [this phantomjs issue](https://github.com/ariya/phantomjs/issues/11342) for any updates. - ### Setup for Watir In your `rails_helper.rb`: @@ -74,10 +73,19 @@ In your `rails_helper.rb`: ```ruby require 'billy/watir/rspec' -# select a driver for your chosen browser environment -@browser = Billy::Browsers::Watir.new :firefox -# @browser = Billy::Browsers::Watir.new = :chrome -# @browser = Billy::Browsers::Watir.new = :phantomjs +# Basic usage +@browser = Billy::Browsers::Watir.new :chrome +# @browser = Billy::Browsers::Watir.new :firefox + +# CI / containerized environments (Docker, GitHub Actions) +# Pass Chrome options to enable headless mode and container-safe flags. +# Set CHROME_BIN if Chromium is not at the default google-chrome path. +options = Selenium::WebDriver::Chrome::Options.new +options.add_argument('--headless=new') +options.add_argument('--no-sandbox') +options.add_argument('--disable-dev-shm-usage') +options.binary = ENV['CHROME_BIN'] if ENV['CHROME_BIN'] +@browser = Billy::Browsers::Watir.new :chrome, options: options ``` ### In your tests (Capybara/Watir) @@ -188,7 +196,7 @@ And in steps: ```ruby Before('@billy') do - Capybara.current_driver = :poltergeist_billy + Capybara.current_driver = :selenium_chrome_headless_billy end And /^a stub for google$/ do @@ -286,7 +294,7 @@ Billy.configure do |c| c.non_whitelisted_requests_disabled = false c.cache_path = 'spec/req_cache/' c.certs_path = 'spec/req_certs/' - c.proxy_host = 'example.com' # defaults to localhost + c.proxy_host = 'example.com' # defaults to 127.0.0.1 c.proxy_port = 12345 # defaults to random c.proxied_request_host = nil c.proxied_request_port = 80 @@ -648,13 +656,11 @@ this gem. ### Google Chrome Headless example -Google Chrome/Chromium is capable to run as a test browser with the new -headless mode which is not able to handle the deprecated -`--ignore-certificate-errors` flag. But the headless mode is capable of -handling the user PKI certificate store. So you just need to import the -runtime Puffing Billy certificate authority on your system store, or generate a -new store for your current session. The following examples demonstrates the -former variant: +Puffing Billy registers the `:selenium_chrome_headless_billy` driver using +Chrome's `headless=new` mode (available since Chrome 112). This mode supports +the user PKI certificate store, so you need to import the Puffing Billy +certificate authority into your system or session store. The following example +demonstrates how to do that at test suite startup: ```ruby # Install the fabulous `os` gem first @@ -742,4 +748,3 @@ custom on-after hook. ## TODO 1. Integration for test frameworks other than rspec. -2. Show errors from the EventMachine reactor loop in the test output. diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index 61b03388..19d5d343 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,4 @@ #!/usr/bin/env rake +# frozen_string_literal: true + require 'bundler/gem_tasks' diff --git a/bin/proxy.rb b/bin/proxy.rb index 49f89d79..f230200c 100755 --- a/bin/proxy.rb +++ b/bin/proxy.rb @@ -1,3 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require 'billy' Billy::Proxy.new.start(false) diff --git a/lib/billy.rb b/lib/billy.rb index 839829a8..a14b756c 100644 --- a/lib/billy.rb +++ b/lib/billy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'billy/version' require 'billy/config' require 'billy/handlers/handler' @@ -18,11 +20,11 @@ module Billy def self.proxy - @billy_proxy ||= ( + @proxy ||= begin proxy = Billy::Proxy.new proxy.start proxy - ) + end end def self.certificate_authority @@ -33,13 +35,13 @@ def self.certificate_authority # the request beforehand and/or modify the actual response which is passed # back by this method. But you can also implement a custom proxy passing # method if you like to. This is just a shortcut. - def self.pass_request(params, headers, body, url, method) - handler = proxy.request_handler.handlers[:proxy] - response = handler.handle_request(method, url, headers, body) - { - code: response[:status], - body: response[:content], - headers: response[:headers] - } + def self.pass_request(_params, headers, body, url, method) + handler = proxy.request_handler.handlers[:proxy] + response = handler.handle_request(method, url, headers, body) + { + code: response[:status], + body: response[:content], + headers: response[:headers] + } end end diff --git a/lib/billy/browsers/capybara.rb b/lib/billy/browsers/capybara.rb index 66f16ead..8d099d80 100644 --- a/lib/billy/browsers/capybara.rb +++ b/lib/billy/browsers/capybara.rb @@ -1,89 +1,54 @@ +# frozen_string_literal: true + require 'billy' module Billy module Browsers class Capybara - - DRIVERS = { - poltergeist: 'capybara/poltergeist', - webkit: 'capybara/webkit', - selenium: 'selenium/webdriver', - apparition: 'capybara/apparition' - } - def self.register_drivers - DRIVERS.each do |name, driver| - begin - require driver - send("register_#{name}_driver") - rescue LoadError - end - end - end - - private - - def self.register_poltergeist_driver - ::Capybara.register_driver :poltergeist_billy do |app| - options = { - phantomjs_options: [ - '--ignore-ssl-errors=yes', - "--proxy=#{Billy.proxy.host}:#{Billy.proxy.port}" - ] - } - ::Capybara::Poltergeist::Driver.new(app, options) - end - end - - def self.register_webkit_driver - ::Capybara.register_driver :webkit_billy do |app| - options = { - ignore_ssl_errors: true, - proxy: {host: Billy.proxy.host, port: Billy.proxy.port} - } - ::Capybara::Webkit::Driver.new(app, ::Capybara::Webkit::Configuration.to_hash.merge(options)) - end + require 'selenium/webdriver' + register_selenium_driver + rescue LoadError end def self.register_selenium_driver ::Capybara.register_driver :selenium_billy do |app| - profile = Selenium::WebDriver::Firefox::Profile.new - profile.assume_untrusted_certificate_issuer = false - profile.proxy = Selenium::WebDriver::Proxy.new( + options = Selenium::WebDriver::Firefox::Options.new + options.proxy = Selenium::WebDriver::Proxy.new( http: "#{Billy.proxy.host}:#{Billy.proxy.port}", - ssl: "#{Billy.proxy.host}:#{Billy.proxy.port}") - ::Capybara::Selenium::Driver.new(app, profile: profile) + ssl: "#{Billy.proxy.host}:#{Billy.proxy.port}" + ) + ::Capybara::Selenium::Driver.new(app, browser: :firefox, options: options) end ::Capybara.register_driver :selenium_chrome_billy do |app| options = Selenium::WebDriver::Chrome::Options.new + options.binary = ENV['CHROME_BIN'] if ENV['CHROME_BIN'] options.add_argument("--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}") ::Capybara::Selenium::Driver.new( app, browser: :chrome, - options: options + options: options ) end - + ::Capybara.register_driver :selenium_chrome_headless_billy do |app| - options = Selenium::WebDriver::Chrome::Options.new(args: %w[headless disable-gpu no-sandbox - enable-features=NetworkService,NetworkServiceInProcess]) + options = Selenium::WebDriver::Chrome::Options.new + options.binary = ENV['CHROME_BIN'] if ENV['CHROME_BIN'] + + options.add_argument('--headless=new') + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + options.add_argument('--disable-gpu') + options.add_argument("--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}") ::Capybara::Selenium::Driver.new( app, browser: :chrome, - options: options + options: options ) end end - - def self.register_apparition_driver - ::Capybara.register_driver :apparition_billy do |app| - ::Capybara::Apparition::Driver.new(app, ignore_https_errors: true).tap do |driver| - driver.set_proxy(Billy.proxy.host, Billy.proxy.port) - end - end - end end end end diff --git a/lib/billy/browsers/watir.rb b/lib/billy/browsers/watir.rb index 5ee51969..a4b7a89d 100644 --- a/lib/billy/browsers/watir.rb +++ b/lib/billy/browsers/watir.rb @@ -1,45 +1,39 @@ +# frozen_string_literal: true + require 'billy' require 'watir' module Billy module Browsers class Watir < ::Watir::Browser - def initialize(name, args = {}) args = case name - when :chrome then configure_chrome(args) - when :phantomjs then configure_phantomjs(args) - when :firefox then configure_firefox(args) - else - raise NameError, "Invalid browser driver specified. (Expected: :chrome, :phantomjs, :firefox)" - end + when :chrome then configure_chrome(args) + when :firefox then configure_firefox(args) + else + raise NameError, 'Invalid browser driver specified. (Expected: :chrome, :firefox)' + end super end private def configure_chrome(args) - args[:switches] ||= [] - args[:switches] += %W[--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}] - args - end - - def configure_phantomjs(args) - args[:driver_opts] ||= {} - args[:driver_opts][:args] ||= [] - args[:driver_opts][:args] += %W[--proxy=#{Billy.proxy.host}:#{Billy.proxy.port}] + options = args.delete(:options) || Selenium::WebDriver::Chrome::Options.new + options.add_argument("--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}") + args[:options] = options args end def configure_firefox(args) - args[:profile] ||= Selenium::WebDriver::Firefox::Profile.new - args[:profile].proxy = Selenium::WebDriver::Proxy.new( + options = args.delete(:options) || Selenium::WebDriver::Firefox::Options.new + options.proxy = Selenium::WebDriver::Proxy.new( http: "#{Billy.proxy.host}:#{Billy.proxy.port}", ssl: "#{Billy.proxy.host}:#{Billy.proxy.port}" ) + args[:options] = options args end - end end end diff --git a/lib/billy/cache.rb b/lib/billy/cache.rb index 0d4b4263..4e8e1c0a 100644 --- a/lib/billy/cache.rb +++ b/lib/billy/cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'resolv' require 'addressable/uri' require 'yaml' @@ -18,7 +20,7 @@ def cached?(method, url, body, cache_scope) # Only log the key the first time it's looked up (in this method) key = key(method, url, body, cache_scope, true) cached = !@cache[key].nil? || persisted?(key) - purl = url.match(/.*staging\.hirefrederick.com\:443(.*)/) + purl = url.match(/.*staging\.hirefrederick.com:443(.*)/) if purl && cached puts "FOUND CACHE: #{scope} #{method} #{purl.captures[0]} #{key}" elsif purl @@ -60,17 +62,17 @@ def store(key, _scope, method, url, request_headers, body, response_headers, sta @cache[key] = cached - if Billy.config.persist_cache - FileUtils.mkdir_p(Billy.config.cache_path) unless File.exist?(Billy.config.cache_path) + return unless Billy.config.persist_cache + + FileUtils.mkdir_p(Billy.config.cache_path) unless File.exist?(Billy.config.cache_path) - begin - File.open(cache_file(key), 'w') do |f| - Billy.log(:info, "puffing-billy: Writing to cache: #{method} '#{url}'") - f.write(cached.to_yaml(Encoding: :Utf8)) - end - rescue StandardError => e - Billy.log :error, "Error storing cache file: #{e.message}" + begin + File.open(cache_file(key), 'w') do |f| + Billy.log(:info, "puffing-billy: Writing to cache: #{method} '#{url}'") + f.write(cached.to_yaml(Encoding: :Utf8)) end + rescue StandardError => e + Billy.log :error, "Error storing cache file: #{e.message}" end end @@ -78,41 +80,38 @@ def reset @cache = {} end - def key(method, orig_url, body, cache_scope, log_key = false) - key_type = '' - if Billy.config.use_ignore_params - ignore_params = Billy.config.ignore_params.include?(format_url(orig_url, true)) - else - ignore_params = !Billy.config.allow_params.include?(format_url(orig_url, true)) - end + def key(method, orig_url, _body, cache_scope, log_key = false) + ignore_params = if Billy.config.use_ignore_params + Billy.config.ignore_params.include?(format_url(orig_url, true)) + else + !Billy.config.allow_params.include?(format_url(orig_url, true)) + end merge_cached_response_key = _merge_cached_response_key(orig_url) url = Addressable::URI.parse(format_url(orig_url, ignore_params)) key = if merge_cached_response_key - key_type = '1' - method + '_' + Digest::SHA1.hexdigest(cache_scope.to_s + merge_cached_response_key) + "#{method}_#{Digest::SHA1.hexdigest(cache_scope.to_s + merge_cached_response_key)}" else - key_type = '2' - method + '_' + url.host + '_' + Digest::SHA1.hexdigest(cache_scope.to_s + url.to_s) + "#{method}_#{url.host}_#{Digest::SHA1.hexdigest(cache_scope.to_s + url.to_s)}" end body_msg = '' - #if Billy.config.cache_request_body_methods.include?(method) && !ignore_params && !merge_cached_response_key - #body_formatted = JSONUtils.json?(body.to_s) ? JSONUtils.sort_json(body.to_s) : body.to_s - #body_msg = " with body '#{body_formatted}'" - #key_type = 3 - #key += '_' + Digest::SHA1.hexdigest(body_formatted) - #end + # if Billy.config.cache_request_body_methods.include?(method) && !ignore_params && !merge_cached_response_key + # body_formatted = JSONUtils.json?(body.to_s) ? JSONUtils.sort_json(body.to_s) : body.to_s + # body_msg = " with body '#{body_formatted}'" + # key_type = 3 + # key += '_' + Digest::SHA1.hexdigest(body_formatted) + # end Billy.log(:info, "puffing-billy: CACHE KEY for '#{orig_url}#{body_msg}' is '#{key}'") if log_key - #purl = orig_url.match(/.*staging\.hirefrederick.com\:443(.*)/) - #puts "CREATING KEY TYPE: #{key_type} #{scope} #{method} #{purl.captures[0]} #{key}" if purl + # purl = orig_url.match(/.*staging\.hirefrederick.com\:443(.*)/) + # puts "CREATING KEY TYPE: #{key_type} #{scope} #{method} #{purl.captures[0]} #{key}" if purl key end def format_url(url, ignore_params = false, dynamic_jsonp = Billy.config.dynamic_jsonp) url = Addressable::URI.parse(url) port_to_include = Billy.config.ignore_cache_port ? '' : ":#{url.port}" - formatted_url = url.scheme + '://' + url.host + port_to_include + url.path + formatted_url = "#{url.scheme}://#{url.host}#{port_to_include}#{url.path}" return formatted_url if ignore_params @@ -128,7 +127,7 @@ def format_url(url, ignore_params = false, dynamic_jsonp = Billy.config.dynamic_ formatted_url += "?#{query_string}" end - formatted_url += '#' + url.fragment if url.fragment + formatted_url += "##{url.fragment}" if url.fragment formatted_url end @@ -136,9 +135,7 @@ def format_url(url, ignore_params = false, dynamic_jsonp = Billy.config.dynamic_ def cache_file(key) file = File.join(Billy.config.cache_path, "#{key}.yml") - if File.symlink? file - file = File.readlink file - end + file = File.readlink file if File.symlink? file file end @@ -148,7 +145,8 @@ def scope_to(new_scope = nil) end def with_scope(use_scope = nil, &block) - fail ArgumentError, 'Expected a block but none was received.' if block.nil? + raise ArgumentError, 'Expected a block but none was received.' if block.nil? + original_scope = scope scope_to use_scope block.call diff --git a/lib/billy/capybara/cucumber.rb b/lib/billy/capybara/cucumber.rb index e485dca8..53d4d39e 100644 --- a/lib/billy/capybara/cucumber.rb +++ b/lib/billy/capybara/cucumber.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'capybara/cucumber' require 'billy/browsers/capybara' require 'billy/init/cucumber' diff --git a/lib/billy/capybara/rspec.rb b/lib/billy/capybara/rspec.rb index 628f24e8..597e3fed 100644 --- a/lib/billy/capybara/rspec.rb +++ b/lib/billy/capybara/rspec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'capybara/rspec' require 'billy/browsers/capybara' require 'billy/init/rspec' diff --git a/lib/billy/config.rb b/lib/billy/config.rb index cc7994ec..02d908af 100644 --- a/lib/billy/config.rb +++ b/lib/billy/config.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'logger' require 'tmpdir' module Billy class Config - DEFAULT_WHITELIST = ['127.0.0.1', 'localhost'] + DEFAULT_WHITELIST = ['127.0.0.1', 'localhost'].freeze RANDOM_AVAILABLE_PORT = 0 # https://github.com/eventmachine/eventmachine/wiki/FAQ#wiki-can-i-start-a-server-on-a-random-available-port attr_accessor :logger, :cache, :cache_request_headers, :whitelist, :path_blacklist, :ignore_params, :allow_params, @@ -15,7 +17,7 @@ class Config :refresh_persisted_cache def initialize - @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT) + @logger = defined?(Rails) ? Rails.logger : Logger.new($stdout) reset end @@ -37,7 +39,7 @@ def reset @non_whitelisted_requests_disabled = false @cache_path = File.join(Dir.tmpdir, 'puffing-billy') @certs_path = File.join(Dir.tmpdir, 'puffing-billy', 'certs') - @proxy_host = 'localhost' + @proxy_host = '127.0.0.1' @proxy_port = RANDOM_AVAILABLE_PORT @proxied_request_inactivity_timeout = 10 # defaults from https://github.com/igrigorik/em-http-request/wiki/Redirects-and-Timeouts @proxied_request_connect_timeout = 5 @@ -61,12 +63,10 @@ def self.configure end def self.log(*args) - unless config.logger.nil? - config.logger.send(*args) - end - end + return if config.logger.nil? - private + config.logger.send(*args) + end def self.config @config ||= Config.new diff --git a/lib/billy/cucumber.rb b/lib/billy/cucumber.rb index 6d84a580..91e00872 100644 --- a/lib/billy/cucumber.rb +++ b/lib/billy/cucumber.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Deprecated require 'billy/capybara/cucumber' diff --git a/lib/billy/handlers/cache_handler.rb b/lib/billy/handlers/cache_handler.rb index 08a384ab..d459ff35 100644 --- a/lib/billy/handlers/cache_handler.rb +++ b/lib/billy/handlers/cache_handler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'billy/handlers/handler' require 'addressable/uri' require 'cgi' @@ -17,31 +19,27 @@ def initialize def handle_request(method, url, headers, body, cache_scope) method = method.downcase - if handles_request?(method, url, headers, body, cache_scope) - if (response = cache.fetch(method, url, body, cache_scope)) - Billy.log(:info, "puffing-billy: CACHE #{method} for '#{url}'") + if handles_request?(method, url, headers, body, + cache_scope) && (response = cache.fetch(method, url, body, cache_scope)) + Billy.log(:info, "puffing-billy: CACHE #{method} for '#{url}'") - if Billy.config.dynamic_jsonp - replace_response_callback(response, url) - end + replace_response_callback(response, url) if Billy.config.dynamic_jsonp - if Billy.config.after_cache_handles_request - request = { method: method, url: url, headers: headers, body: body } - Billy.config.after_cache_handles_request.call(request, response) - end + if Billy.config.after_cache_handles_request + request = { method: method, url: url, headers: headers, body: body } + Billy.config.after_cache_handles_request.call(request, response) + end - if Billy.config.cache_simulates_network_delays - Kernel.sleep(Billy.config.cache_simulates_network_delay_time) - end + Kernel.sleep(Billy.config.cache_simulates_network_delay_time) if Billy.config.cache_simulates_network_delays - return response - end + return response end nil end def handles_request?(method, url, _headers, body, cache_scope) return false if Billy.config.refresh_persisted_cache + cached?(method, url, body, cache_scope) end @@ -49,13 +47,13 @@ def handles_request?(method, url, _headers, body, cache_scope) def replace_response_callback(response, url) request_uri = Addressable::URI.parse(url) - if request_uri.query - params = CGI.parse(request_uri.query) - callback_name = Billy.config.dynamic_jsonp_callback_name - if params[callback_name].any? && response[:content].match(/\w+\(/) - response[:content].sub!(/\w+\(/, params[callback_name].first + '(') - end - end + return unless request_uri.query + + params = CGI.parse(request_uri.query) + callback_name = Billy.config.dynamic_jsonp_callback_name + return unless params[callback_name].any? && response[:content].match(/\w+\(/) + + response[:content] = response[:content].sub(/\w+\(/, "#{params[callback_name].first}(") end end end diff --git a/lib/billy/handlers/handler.rb b/lib/billy/handlers/handler.rb index 16641d02..ed6858ef 100644 --- a/lib/billy/handlers/handler.rb +++ b/lib/billy/handlers/handler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Billy module Handler ## diff --git a/lib/billy/handlers/proxy_handler.rb b/lib/billy/handlers/proxy_handler.rb index dc56fff9..c47b3c72 100644 --- a/lib/billy/handlers/proxy_handler.rb +++ b/lib/billy/handlers/proxy_handler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'billy/handlers/handler' require 'addressable/uri' require 'eventmachine' @@ -11,31 +13,30 @@ class ProxyHandler def_delegators :request_log, :requests - def handles_request?(_method, url, _headers, _body, cache_scope) - purl = url.match(/.*staging\.hirefrederick.com\:443/) + def handles_request?(_method, url, _headers, _body, _cache_scope) + purl = url.match(/.*staging\.hirefrederick.com:443/) return false if !Billy.config.refresh_persisted_cache && purl + !disabled_request?(url) end def handle_request(method, url, headers, body, cache_scope) if handles_request?(method, url, headers, body, cache_scope) opts = { inactivity_timeout: Billy.config.proxied_request_inactivity_timeout, - connect_timeout: Billy.config.proxied_request_connect_timeout } + connect_timeout: Billy.config.proxied_request_connect_timeout } if Billy.config.proxied_request_host && !bypass_internal_proxy?(url) opts.merge!({ proxy: { host: Billy.config.proxied_request_host, - port: Billy.config.proxied_request_port }} ) + port: Billy.config.proxied_request_port } }) end - #cache_scope = Billy::Cache.instance.scope + # cache_scope = Billy::Cache.instance.scope cache_key = Billy::Cache.instance.key(method.downcase, url, body, cache_scope) req = EventMachine::HttpRequest.new(url, opts) req = req.send(method.downcase, build_request_options(url, headers, body)) - if req.error - return { error: "Request to #{url} failed with error: #{req.error}" } - end + return { error: "Request to #{url} failed with error: #{req.error}" } if req.error if req.response response = process_response(req, cache_key) @@ -43,12 +44,13 @@ def handle_request(method, url, headers, body, cache_scope) unless allowed_response_code?(response[:status]) if Billy.config.non_successful_error_level == :error return { error: "#{method} Request failed due to response status #{response[:status]} for '#{url}' which was not allowed." } - else - Billy.log(:warn, "puffing-billy: Received response status code #{response[:status]} for '#{url}'") end + + Billy.log(:warn, "puffing-billy: Received response status code #{response[:status]} for '#{url}'") + end - purl = url.match(/.*staging\.hirefrederick.com\:443(.*)/) + purl = url.match(/.*staging\.hirefrederick.com:443(.*)/) if cacheable?(url, response[:headers], response[:status]) puts "CACHING: #{cache_scope} #{method} #{purl.captures[0]} #{cache_key}" if purl @@ -64,8 +66,11 @@ def handle_request(method, url, headers, body, cache_scope) response[:content] ) else - Billy.log(:info, "puffing-billy: NOT CACHEABLE: Status: #{response[:status]} #{cache_scope} #{method} #{url} #{cache_key}") - puts "NOT CACHABLE: Status: #{response[:status]} #{cache_scope} #{method} #{purl.captures[0]} #{cache_key}" if purl + Billy.log(:info, + "puffing-billy: NOT CACHEABLE: Status: #{response[:status]} #{cache_scope} #{method} #{url} #{cache_key}") + if purl + puts "NOT CACHABLE: Status: #{response[:status]} #{cache_scope} #{method} #{purl.captures[0]} #{cache_key}" + end end Billy.log(:info, "puffing-billy: PROXY #{method} succeeded for #{url}") @@ -75,14 +80,14 @@ def handle_request(method, url, headers, body, cache_scope) nil end - private + private def build_request_options(url, headers, body) - headers = Hash[headers.map { |k, v| [k.downcase, v] }] + headers = headers.transform_keys(&:downcase) headers['accept-encoding'] = '' uri = Addressable::URI.parse(url) - headers.merge!({'authorization' => [uri.user, uri.password]}) if uri.userinfo + headers.merge!({ 'authorization' => [uri.user, uri.password] }) if uri.userinfo req_opts = { redirects: 0, @@ -122,12 +127,12 @@ def cacheable?(url, _headers, status) # Don't save a cache file if the cache is off or we're not refreshing the cache return false if !Billy.config.cache || !Billy.config.refresh_persisted_cache - #orig_url = url + # orig_url = url url = Addressable::URI.parse(url) # Cache the responses if they aren't whitelisted host[:port]s but always cache blacklisted paths on any hosts - #if orig_url.match(/background/) - #byebug - #end + # if orig_url.match(/background/) + # byebug + # end cacheable_status?(status) && (!whitelisted_url?(url) || blacklisted_path?(url.path)) end diff --git a/lib/billy/handlers/request_handler.rb b/lib/billy/handlers/request_handler.rb index c5c51a4b..645363f2 100644 --- a/lib/billy/handlers/request_handler.rb +++ b/lib/billy/handlers/request_handler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'forwardable' module Billy @@ -15,12 +17,12 @@ def handlers end def handle_request(method, url, headers, body) - previous_duplicate_requests = request_log.requests.select {|x| x[:method] == method && x[:url] == url } + previous_duplicate_requests = request_log.requests.select { |x| x[:method] == method && x[:url] == url } cache_scope = previous_duplicate_requests.length request = request_log.record(method, url, headers, body, cache_scope) # Process the handlers by order of importance - [:stubs, :cache, :proxy].each do |key| + %i[stubs cache proxy].each do |key| if (response = handlers[key].handle_request(method, url, headers, body, cache_scope)) @request_log.complete(request, key, response[:cache_key]) return response @@ -28,17 +30,17 @@ def handle_request(method, url, headers, body) end body_msg = Billy.config.cache_request_body_methods.include?(method) ? " with body '#{body}'" : '' - request_log.complete(request, :error) + request_log.complete(request, :error, nil) { error: "Connection to #{url}#{body_msg} not cached and new http connections are disabled" } - rescue => error - { error: error.message } + rescue StandardError => e + { error: e.message } end - #def handles_request?(method, url, headers, body) - #[:stubs, :cache, :proxy].any? do |key| - #handlers[key].handles_request?(method, url, headers, body) - #end - #end + # def handles_request?(method, url, headers, body) + # [:stubs, :cache, :proxy].any? do |key| + # handlers[key].handles_request?(method, url, headers, body) + # end + # end def request_log @request_log ||= RequestLog.new diff --git a/lib/billy/handlers/request_log.rb b/lib/billy/handlers/request_log.rb index a925a874..2c7c54ec 100644 --- a/lib/billy/handlers/request_log.rb +++ b/lib/billy/handlers/request_log.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Billy class RequestLog attr_reader :requests @@ -12,7 +14,7 @@ def reset def record(method, url, headers, body, cache_scope) Billy.log(:info, "puffing-billy: REQUEST LOG: #{method} #{url}") - purl = url.match(/.*staging\.hirefrederick.com\:443(.*)/) + purl = url.match(/.*staging\.hirefrederick.com:443(.*)/) puts "REQUEST LOG: #{cache_scope} #{method} #{purl.captures[0]}" if purl return unless Billy.config.record_requests diff --git a/lib/billy/handlers/stub_handler.rb b/lib/billy/handlers/stub_handler.rb index 385185f0..5ce8b716 100644 --- a/lib/billy/handlers/stub_handler.rb +++ b/lib/billy/handlers/stub_handler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'billy/handlers/handler' require 'addressable/uri' @@ -6,21 +8,19 @@ class StubHandler include Handler def handle_request(method, url, headers, body, cache_scope) - if handles_request?(method, url, headers, body, cache_scope) - if (stub = find_stub(method, url)) - query_string = Addressable::URI.parse(url).query || '' - params = CGI.parse(query_string) - stub.call(method, url, params, headers, body).tap do |response| - Billy.log(:info, "puffing-billy: STUB #{method} for '#{url}'") - return { status: response[0], headers: response[1], content: response[2] } - end + if handles_request?(method, url, headers, body, cache_scope) && (stub = find_stub(method, url)) + query_string = Addressable::URI.parse(url).query || '' + params = CGI.parse(query_string) + stub.call(method, url, params, headers, body).tap do |response| + Billy.log(:info, "puffing-billy: STUB #{method} for '#{url}'") + return { status: response[0], headers: response[1], content: response[2] } end end nil end - def handles_request?(method, url, _headers, _body, cache_scope) + def handles_request?(method, url, _headers, _body, _cache_scope) !find_stub(method, url).nil? end diff --git a/lib/billy/init/cucumber.rb b/lib/billy/init/cucumber.rb index d456e54a..aec0981f 100644 --- a/lib/billy/init/cucumber.rb +++ b/lib/billy/init/cucumber.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'cucumber' module Billy diff --git a/lib/billy/init/rspec.rb b/lib/billy/init/rspec.rb index 0a6e7266..e45fd7a4 100644 --- a/lib/billy/init/rspec.rb +++ b/lib/billy/init/rspec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Billy module RspecHelper def proxy diff --git a/lib/billy/json_utils.rb b/lib/billy/json_utils.rb index 50bd7fff..54340066 100644 --- a/lib/billy/json_utils.rb +++ b/lib/billy/json_utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' module Billy @@ -12,10 +14,10 @@ def self.json?(value) # data structure while preserving the order of arrays. def self.sort_hash_keys(data) return data unless data.is_a?(Hash) || data.is_a?(Array) + if data.is_a? Hash - data.keys.sort.reduce({}) do |seed, key| + data.keys.sort.each_with_object({}) do |key, seed| seed[key] = sort_hash_keys(data[key]) - seed end else data.map do |element| diff --git a/lib/billy/proxy.rb b/lib/billy/proxy.rb index a726c3cc..256a9ef9 100644 --- a/lib/billy/proxy.rb +++ b/lib/billy/proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'cgi' require 'eventmachine' require 'timeout' @@ -5,9 +7,10 @@ module Billy class Proxy extend Forwardable - attr_reader :request_handler + attr_reader :request_handler, :port - def_delegators :request_handler, :stub, :stubs, :unstub, :reset, :reset_cache, :restore_cache, :requests, :handle_request + def_delegators :request_handler, :stub, :stubs, :unstub, :reset, :reset_cache, :restore_cache, :requests, + :handle_request def initialize @request_handler = Billy::RequestHandler.new @@ -17,7 +20,7 @@ def initialize def start(threaded = true) if threaded Thread.new { main_loop } - sleep(0.01) while (not defined?(@signature)) || @signature.nil? + sleep(0.01) while !defined?(@port) || @port.nil? else main_loop end @@ -39,10 +42,6 @@ def host Billy.config.proxy_host end - def port - Socket.unpack_sockaddr_in(EM.get_sockname(@signature)).first - end - def cache Billy::Cache.instance end @@ -50,7 +49,7 @@ def cache protected def wait_for_server_shutdown!(server_port) - Timeout::timeout(60) do + Timeout.timeout(60) do sleep(0.01) while port_in_use? server_port end rescue Timeout::Error @@ -81,6 +80,8 @@ def main_loop end end + @port = Socket.unpack_sockaddr_in(EM.get_sockname(@signature)).first + Billy.log(:info, "puffing-billy: Proxy listening on #{url}") end end diff --git a/lib/billy/proxy_connection.rb b/lib/billy/proxy_connection.rb index 3f44c7d3..aa0a9219 100644 --- a/lib/billy/proxy_connection.rb +++ b/lib/billy/proxy_connection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'addressable/uri' require 'eventmachine' require 'http/parser' @@ -9,8 +11,7 @@ module Billy class ProxyConnection < EventMachine::Connection include EventMachine::Deferrable - attr_accessor :handler - attr_accessor :cache + attr_accessor :handler, :cache def post_init @parser = Http::Parser.new(self) @@ -22,7 +23,7 @@ def receive_data(data) def on_message_begin @headers = nil - @body = '' + @body = String.new end def on_headers_complete(headers) @@ -61,7 +62,7 @@ def handle_request handler.handle_request(@parser.http_method, @url, @headers, @body).tap do |response| if response.key?(:error) close_connection - fail "puffing-billy: #{response[:error]}" + raise "puffing-billy: #{response[:error]}" else send_response(response) end @@ -74,13 +75,13 @@ def handle_request def prepare_response_headers_for_evma_httpserver(headers) # Remove the headers below because they will be added later by evma_httpserver (EventMachine::DelegatedHttpResponse). # See https://github.com/eventmachine/evma_httpserver/blob/master/lib/evma_httpserver/response.rb - headers_to_remove = [ - 'transfer-encoding', - 'content-length', - 'content-encoding' + headers_to_remove = %w[ + transfer-encoding + content-length + content-encoding ] - headers.delete_if {|key, value| headers_to_remove.include?(key.downcase) } + headers.delete_if { |key, _value| headers_to_remove.include?(key.downcase) } end def send_response(response) diff --git a/lib/billy/proxy_request_stub.rb b/lib/billy/proxy_request_stub.rb index 5d1a4196..5ef79390 100644 --- a/lib/billy/proxy_request_stub.rb +++ b/lib/billy/proxy_request_stub.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'multi_json' module Billy @@ -20,11 +22,11 @@ def and_return(response) def call(method, url, params, headers, body) push_request(method, url, params, headers, body) - if @response.respond_to?(:call) - res = @response.call(params, headers, body, url, method) - else - res = @response - end + res = if @response.respond_to?(:call) + @response.call(params, headers, body, url, method) + else + @response + end code = res[:code] || 200 @@ -36,13 +38,13 @@ def call(method, url, params, headers, body) body = MultiJson.dump(res[:json]) elsif res[:jsonp] headers = { 'Content-Type' => 'application/javascript' }.merge(headers) - if res[:callback] - callback = res[:callback] - elsif res[:callback_param] - callback = params[res[:callback_param]][0] - else - callback = params['callback'][0] - end + callback = if res[:callback] + res[:callback] + elsif res[:callback_param] + params[res[:callback_param]][0] + else + params['callback'][0] + end body = "#{callback}(#{MultiJson.dump(res[:jsonp])})" elsif res[:text] headers = { 'Content-Type' => 'text/plain' }.merge(headers) @@ -62,12 +64,12 @@ def has_requests? end def matches?(method, url) - if @method == 'ALL' || method == @method - if @url.is_a?(Regexp) - url.match(@url) - else - Billy.config.strip_query_params ? (url.split('?')[0] == @url) : (url == @url) - end + return unless @method == 'ALL' || method == @method + + if @url.is_a?(Regexp) + url.match(@url) + else + Billy.config.strip_query_params ? (url.split('?')[0] == @url) : (url == @url) end end @@ -76,15 +78,15 @@ def matches?(method, url) attr_writer :requests def push_request(method, url, params, headers, body) - if Billy.config.record_stub_requests - @requests.push({ - method: method, - url: url, - params: params, - headers: headers, - body: body - }) - end + return unless Billy.config.record_stub_requests + + @requests.push({ + method: method, + url: url, + params: params, + headers: headers, + body: body + }) end end end diff --git a/lib/billy/railtie.rb b/lib/billy/railtie.rb index 246e7a77..63ce7378 100644 --- a/lib/billy/railtie.rb +++ b/lib/billy/railtie.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Billy class Railtie < Rails::Railtie railtie_name 'billy' diff --git a/lib/billy/rspec.rb b/lib/billy/rspec.rb index 6f33b719..a19a9550 100644 --- a/lib/billy/rspec.rb +++ b/lib/billy/rspec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Deprecated require 'billy/capybara/rspec' diff --git a/lib/billy/ssl/authority.rb b/lib/billy/ssl/authority.rb index 8fe798be..a1712cb2 100644 --- a/lib/billy/ssl/authority.rb +++ b/lib/billy/ssl/authority.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require 'openssl' @@ -69,7 +68,7 @@ def generate cert = OpenSSL::X509::Certificate.new configure(cert) add_extensions(cert) - cert.sign(key, OpenSSL::Digest::SHA256.new) + cert.sign(key, OpenSSL::Digest.new('SHA256')) end # Setup all relevant properties of the given certificate to produce diff --git a/lib/billy/ssl/certificate.rb b/lib/billy/ssl/certificate.rb index 734fd876..01c701ad 100644 --- a/lib/billy/ssl/certificate.rb +++ b/lib/billy/ssl/certificate.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require 'openssl' @@ -62,7 +61,7 @@ def generate cert = OpenSSL::X509::Certificate.new configure(cert) add_extensions(cert) - cert.sign(Billy.certificate_authority.key, OpenSSL::Digest::SHA256.new) + cert.sign(Billy.certificate_authority.key, OpenSSL::Digest.new('SHA256')) end # Generate a new certificate signing request (CSR) which will be picked @@ -71,7 +70,7 @@ def signing_request req = OpenSSL::X509::Request.new req.public_key = key.public_key req.subject = OpenSSL::X509::Name.new([['CN', domain]]) - req.sign(key, OpenSSL::Digest::SHA256.new) + req.sign(key, OpenSSL::Digest.new('SHA256')) end # Setup all relevant properties of the given certificate to produce diff --git a/lib/billy/ssl/certificate_chain.rb b/lib/billy/ssl/certificate_chain.rb index 9176af41..801b1f02 100644 --- a/lib/billy/ssl/certificate_chain.rb +++ b/lib/billy/ssl/certificate_chain.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require 'fileutils' @@ -34,7 +33,7 @@ def initialize(domain, *certs) # produce a temporary file which will be remove after the current process # terminates. def file - contents = certificates.map { |cert| cert.to_pem }.join + contents = certificates.map(&:to_pem).join write_file("chain-#{domain}.pem", contents) end end diff --git a/lib/billy/ssl/certificate_helpers.rb b/lib/billy/ssl/certificate_helpers.rb index 8330d64d..d3dd80de 100644 --- a/lib/billy/ssl/certificate_helpers.rb +++ b/lib/billy/ssl/certificate_helpers.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true require 'openssl' @@ -7,7 +6,6 @@ module Billy # A set of common certificate helper methods. module CertificateHelpers - # Give back the date from now plus given days. def days_from_now(days) Time.now + (days * 24 * 60 * 60) diff --git a/lib/billy/version.rb b/lib/billy/version.rb index 4a180a91..98173f0d 100644 --- a/lib/billy/version.rb +++ b/lib/billy/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Billy - VERSION = '2.1.1' + VERSION = '3.0.0' end diff --git a/lib/billy/watir/cucumber.rb b/lib/billy/watir/cucumber.rb index 1af19e2e..c0cc898a 100644 --- a/lib/billy/watir/cucumber.rb +++ b/lib/billy/watir/cucumber.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + require 'billy/browsers/watir' require 'billy/init/cucumber' diff --git a/lib/billy/watir/rspec.rb b/lib/billy/watir/rspec.rb index dd1c6ffc..4b898dc4 100644 --- a/lib/billy/watir/rspec.rb +++ b/lib/billy/watir/rspec.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + require 'billy/browsers/watir' require 'billy/init/rspec' diff --git a/lib/puffing-billy.rb b/lib/puffing-billy.rb index bf02ebe8..6b128aa7 100644 --- a/lib/puffing-billy.rb +++ b/lib/puffing-billy.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + require 'billy' PuffingBilly ||= Billy diff --git a/lib/puffing-billy/rspec.rb b/lib/puffing-billy/rspec.rb index fce688a1..2776799a 100644 --- a/lib/puffing-billy/rspec.rb +++ b/lib/puffing-billy/rspec.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + require 'billy/rspec' PuffingBilly ||= Billy diff --git a/lib/tasks/billy.rake b/lib/tasks/billy.rake index 31b4b648..ca3376d1 100644 --- a/lib/tasks/billy.rake +++ b/lib/tasks/billy.rake @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'addressable/uri' namespace :cache do @@ -12,8 +14,9 @@ namespace :cache do desc 'Print out specific cache file information' task :print_details, :sha do |_t, args| - fail "Missing sha; usage: rake cache:print_details['']" unless args[:sha] - cache_array = load_cache(Billy.config.cache_path, '*' + args[:sha] + '*.yml') + raise "Missing sha; usage: rake cache:print_details['']" unless args[:sha] + + cache_array = load_cache(Billy.config.cache_path, "*#{args[:sha]}*.yml") sort_cache(cache_array).each do |cache| print_cache_details(cache) @@ -22,7 +25,8 @@ namespace :cache do desc 'Find specific cache files by URL' task :find_by_url, :api_path do |_t, args| - fail "Missing api path; usage: rake cache:find_by_url['']" unless args[:api_path] + raise "Missing api path; usage: rake cache:find_by_url['']" unless args[:api_path] + cache_array = load_cache filtered_cache_array = cache_array.select { |f| f[:url_path].include?(args[:api_path]) } @@ -33,9 +37,10 @@ namespace :cache do desc 'Find specific cache files by scope' task :find_by_scope, :scope do |_t, args| - fail "Missing scope; usage: rake cache:find_by_scope['']" unless args[:scope] + raise "Missing scope; usage: rake cache:find_by_scope['']" unless args[:scope] + cache_array = load_cache - filtered_cache_array = cache_array.select { |f| f[:scope] && f[:scope].include?(args[:scope]) } + filtered_cache_array = cache_array.select { |f| f[:scope]&.include?(args[:scope]) } sort_cache(filtered_cache_array).each do |cache| print_cache_details(cache) @@ -45,7 +50,7 @@ namespace :cache do desc 'Find cache files with non-successful status codes' task :find_non_successful do cache_array = load_cache - filtered_cache_array = cache_array.select { |f| !(200..299).include?(f[:status]) } + filtered_cache_array = cache_array.reject { |f| (200..299).include?(f[:status]) } sort_cache(filtered_cache_array).each do |cache| print_cache_details(cache) @@ -59,8 +64,8 @@ namespace :cache do Dir.glob(cache_path + file_pattern) do |filename| data = load_cache_file(filename) url = Addressable::URI.parse(data[:url]) - data[:url_path] = "#{url.path}#{url.query ? '?' + url.query : ''}#{url.fragment ? '#' + url.fragment : ''}" - data[:filename] = filename.gsub(Rails.root.to_s + '/', '') + data[:url_path] = "#{url.path}#{url.query ? "?#{url.query}" : ''}#{url.fragment ? "##{url.fragment}" : ''}" + data[:filename] = filename.gsub("#{Rails.root}/", '') cache_array << data end cache_array diff --git a/puffing-billy.gemspec b/puffing-billy.gemspec index 3eb196d0..29354ee9 100644 --- a/puffing-billy.gemspec +++ b/puffing-billy.gemspec @@ -1,5 +1,6 @@ -# -*- encoding: utf-8 -*- -require File.expand_path('../lib/billy/version', __FILE__) +# frozen_string_literal: true + +require File.expand_path('lib/billy/version', __dir__) Gem::Specification.new do |gem| gem.authors = ['Olly Smith'] @@ -7,6 +8,7 @@ Gem::Specification.new do |gem| gem.description = 'A stubbing proxy server for ruby. Connect it to your browser in integration tests to fake interactions with remote HTTP(S) servers.' gem.summary = 'Easy request stubs for browser tests.' gem.homepage = 'https://github.com/oesmith/puffing-billy' + gem.required_ruby_version = '>= 3.3.10' gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR) gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } @@ -15,26 +17,27 @@ Gem::Specification.new do |gem| gem.require_paths = ['lib'] gem.version = Billy::VERSION - gem.add_development_dependency 'rspec' - gem.add_development_dependency 'thin' - gem.add_development_dependency 'faraday' - gem.add_development_dependency 'poltergeist' - gem.add_development_dependency 'apparition' - # selenium-webdriver 3.8 drops support for PhantomJS - gem.add_development_dependency 'selenium-webdriver', '<= 3.7.0' - gem.add_development_dependency 'capybara' - gem.add_development_dependency 'capybara-webkit', '~> 1.0' - gem.add_development_dependency 'rack' - gem.add_development_dependency 'guard' - gem.add_development_dependency 'rb-inotify' - gem.add_development_dependency 'pry' - gem.add_development_dependency 'cucumber' - gem.add_development_dependency 'watir', '~> 6.10.0' gem.add_runtime_dependency 'addressable', '~> 2.5' - gem.add_runtime_dependency 'eventmachine', '~> 1.2' - gem.add_runtime_dependency 'em-synchrony' gem.add_runtime_dependency 'em-http-request', '~> 1.1', '>= 1.1.0' + gem.add_runtime_dependency 'em-synchrony' + gem.add_runtime_dependency 'eventmachine', '~> 1.2' gem.add_runtime_dependency 'eventmachine_httpserver' - gem.add_runtime_dependency 'http_parser.rb', '~> 0.6.0' + gem.add_runtime_dependency 'http_parser.rb', '~> 0.8.0' gem.add_runtime_dependency 'multi_json' + + gem.add_development_dependency 'capybara' + gem.add_development_dependency 'cucumber' + gem.add_development_dependency 'faraday' + gem.add_development_dependency 'guard' + gem.add_development_dependency 'pry' + gem.add_development_dependency 'puma' + gem.add_development_dependency 'rack' + gem.add_development_dependency 'rb-inotify' + gem.add_development_dependency 'rspec' + gem.add_development_dependency 'rubocop' + gem.add_development_dependency 'rubocop-capybara' + gem.add_development_dependency 'rubocop-rspec' + gem.add_development_dependency 'selenium-webdriver', '>= 4.0' + gem.add_development_dependency 'thin' + gem.add_development_dependency 'watir', '~> 7.0' end diff --git a/spec/features/examples/facebook_api_spec.rb b/spec/features/examples/facebook_api_spec.rb index e428189c..b2a3f5d1 100644 --- a/spec/features/examples/facebook_api_spec.rb +++ b/spec/features/examples/facebook_api_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require 'spec_helper' require 'base64' # FIXME: Looks like Facebook API changed recently and this test fails consistently now -RS 2018-03-05 -xdescribe 'Facebook API example', type: :feature, js: true do +xdescribe 'Facebook API example', :js, type: :feature do before do proxy.stub('https://www.facebook.com:443/dialog/oauth').and_return(proc do |params, _, _| # mock a signed request from facebook. the JS api never verifies the @@ -15,7 +17,7 @@ proxy.stub('https://graph.facebook.com:443/me').and_return(jsonp: { name: 'Tester 1' }) end - it 'should show me as logged-in' do + it 'shows me as logged-in' do visit '/facebook_api.html' click_on 'Login' expect(page).to have_content 'Hi, Tester 1' diff --git a/spec/features/examples/intercept_request_spec.rb b/spec/features/examples/intercept_request_spec.rb index f9520890..e694f3cf 100644 --- a/spec/features/examples/intercept_request_spec.rb +++ b/spec/features/examples/intercept_request_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + require 'spec_helper' require 'timeout' -describe 'intercept request example', type: :feature, js: true do +describe 'intercept request example', :js, type: :feature do before do Billy.config.record_stub_requests = true end - it 'should intercept a GET request directly' do + it 'intercepts a GET request directly' do stub = proxy.stub('http://example.com/').and_return( headers: { 'Access-Control-Allow-Origin' => '*' }, code: 200 @@ -16,13 +18,13 @@ expect(stub.requests).not_to be_empty end - it 'should intercept a POST request through an intermediary page' do + it 'intercepts a POST request through an intermediary page' do stub = proxy.stub('http://example.com/', method: 'post').and_return( headers: { 'Access-Control-Allow-Origin' => '*' }, code: 200 ) visit '/intercept_request.html' - Timeout::timeout(5) do + Timeout.timeout(5) do sleep(0.1) until stub.has_requests? end request = stub.requests.shift diff --git a/spec/features/examples/post_api_spec.rb b/spec/features/examples/post_api_spec.rb index 6dce722f..298223f1 100644 --- a/spec/features/examples/post_api_spec.rb +++ b/spec/features/examples/post_api_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' -describe 'jQuery POST API example', type: :feature, js: true do +describe 'jQuery POST API example', :js, type: :feature do before do proxy.stub('http://example.com/api', method: 'post').and_return( headers: { 'Access-Control-Allow-Origin' => '*' }, diff --git a/spec/features/examples/preflight_request_spec.rb b/spec/features/examples/preflight_request_spec.rb index 987872e2..3cba7fc5 100644 --- a/spec/features/examples/preflight_request_spec.rb +++ b/spec/features/examples/preflight_request_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' -describe 'jQuery preflight request example', type: :feature, js: true do +describe 'jQuery preflight request example', :js, type: :feature do let(:url) { 'http://example.com/api' } before do diff --git a/spec/features/examples/tumblr_api_spec.rb b/spec/features/examples/tumblr_api_spec.rb index 054024ee..615c97a0 100644 --- a/spec/features/examples/tumblr_api_spec.rb +++ b/spec/features/examples/tumblr_api_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.shared_examples 'tumblr/expectations' do - it 'should show news stories' do + it 'shows news stories' do visit '/tumblr_api.html' expect(page).to have_link('News Item 1', href: 'http://example.com/news/1') expect(page).to have_content('News item 1 content here') @@ -10,7 +12,7 @@ end end -describe 'Tumblr API example', type: :feature, js: true do +describe 'Tumblr API example', :js, type: :feature do context 'without scope external references' do before do proxy.stub('http://blog.howmanyleft.co.uk/api/read/json').and_return( @@ -27,7 +29,8 @@ 'regular-body' => 'News item 2 content here' } ] - }) + } + ) end include_examples 'tumblr/expectations' diff --git a/spec/lib/billy/cache_spec.rb b/spec/lib/billy/cache_spec.rb index 9a872c02..a839cf8b 100644 --- a/spec/lib/billy/cache_spec.rb +++ b/spec/lib/billy/cache_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::Cache do - let(:cache) { Billy::Cache.instance } + let(:cache) { described_class.instance } let(:params) { '?foo=bar' } let(:callback) { '&callback=quux' } let(:fragment) { '#baz' } @@ -18,12 +20,15 @@ it 'is a no-op if there are no params' do expect(cache.format_url(base_url)).to eq base_url end + it 'appends params if there are params' do expect(cache.format_url(params_url)).to eq params_url end + it 'appends params and fragment if both are present' do expect(cache.format_url(params_fragment_url)).to eq params_fragment_url end + it 'does not raise error for URLs with pipes' do expect { cache.format_url(pipe_url) }.not_to raise_error end @@ -34,7 +39,7 @@ end it 'omits the params listed in Billy.config.dynamic_jsonp_keys' do - allow(Billy.config).to receive(:dynamic_jsonp_keys) { ['foo'] } + allow(Billy.config).to receive(:dynamic_jsonp_keys).and_return(['foo']) expect(cache.format_url(params_url_with_callback, false, true)).to eq "#{base_url}?callback=quux" end @@ -49,9 +54,11 @@ it 'is a no-op if there are no params' do expect(cache.format_url(base_url, true)).to eq base_url end + it 'omits params if there are params' do expect(cache.format_url(params_url, true)).to eq base_url end + it 'omits params and fragment if both are present' do expect(cache.format_url(params_fragment_url, true)).to eq base_url end @@ -64,7 +71,7 @@ let(:regex_to_match_analytics_urls_only) do # Note that it matches the forward slash at the end of the URL, which doesn't match regular_url: - /www\.example\-analytics\.com\/user\// + %r{www\.example-analytics\.com/user/} end before do @@ -74,8 +81,10 @@ end it "has one cache key for the two analytics urls that match, and a separate one for the other that doesn't" do - expect(cache.key('post', analytics_url1, 'body', cache_scope)).to eq cache.key('post', analytics_url2, 'body', cache_scope) - expect(cache.key('post', analytics_url1, 'body', cache_scope)).not_to eq cache.key('post', regular_url, 'body', cache_scope) + expect(cache.key('post', analytics_url1, 'body', + cache_scope)).to eq cache.key('post', analytics_url2, 'body', cache_scope) + expect(cache.key('post', analytics_url1, 'body', + cache_scope)).not_to eq cache.key('post', regular_url, 'body', cache_scope) end it 'More specifically, the cache keys should be identical for the 2 analytics urls' do @@ -91,32 +100,30 @@ context 'with cache_request_body_methods set' do before do - allow(Billy.config).to receive(:cache_request_body_methods) { - ['patch'] - } + allow(Billy.config).to receive(:cache_request_body_methods).and_return(['patch']) end - context "for requests with methods specified in cache_request_body_methods" do - # Note: Body hashing logic is currently commented out in Billy::Cache#key (lines 99-104) + context 'for requests with methods specified in cache_request_body_methods' do + # NOTE: Body hashing logic is currently commented out in Billy::Cache#key (lines 99-104) # So cache keys dont differ based on body content even for cache_request_body_methods - it "should have the same cache key for requests with different bodies (body hashing disabled)" do - key1 = cache.key('patch', "http://example.com", "body1", cache_scope) - key2 = cache.key('patch', "http://example.com", "body2", cache_scope) + it 'has the same cache key for requests with different bodies (body hashing disabled)' do + key1 = cache.key('patch', 'http://example.com', 'body1', cache_scope) + key2 = cache.key('patch', 'http://example.com', 'body2', cache_scope) # Body hashing is commented out, so keys are the same regardless of body - expect(key1).to eq key2 + expect(key1).to eq key2 end - it "should have the same cache key for requests with the same bodies" do - key1 = cache.key('patch', "http://example.com", "body1", cache_scope) - key2 = cache.key('patch', "http://example.com", "body1", cache_scope) + it 'has the same cache key for requests with the same bodies' do + key1 = cache.key('patch', 'http://example.com', 'body1', cache_scope) + key2 = cache.key('patch', 'http://example.com', 'body1', cache_scope) expect(key1).to eq key2 end end - it "should have the same cache key for request with different bodies if their methods are not included in cache_request_body_methods" do - key1 = cache.key('put', "http://example.com", "body1", cache_scope) - key2 = cache.key('put', "http://example.com", "body2", cache_scope) - expect(key1).to eq key2 + it 'has the same cache key for request with different bodies if their methods are not included in cache_request_body_methods' do + key1 = cache.key('put', 'http://example.com', 'body1', cache_scope) + key2 = cache.key('put', 'http://example.com', 'body2', cache_scope) + expect(key1).to eq key2 end end end @@ -124,37 +131,37 @@ describe 'key' do context 'with use_ignore_params set to false' do before do - allow(Billy.config).to receive(:use_ignore_params) { false } + allow(Billy.config).to receive(:use_ignore_params).and_return(false) end - it "should use the same cache key if the base url IS NOT whitelisted in allow_params" do + it 'uses the same cache key if the base url IS NOT whitelisted in allow_params' do key1 = cache.key('put', params_url, 'body', cache_scope) key2 = cache.key('put', params_url, 'body', cache_scope) expect(key1).to eq key2 end - it "should have the same cache key if the base IS whitelisted in allow_params" do + it 'has the same cache key if the base IS whitelisted in allow_params' do allow(Billy.config).to receive(:allow_params) { [base_url] } key1 = cache.key('put', params_url, 'body', cache_scope) key2 = cache.key('put', params_url, 'body', cache_scope) expect(key1).to eq key2 end - it "should have different cache keys if the base url is added in between two requests" do + it 'has different cache keys if the base url is added in between two requests' do key1 = cache.key('put', params_url, 'body', cache_scope) allow(Billy.config).to receive(:allow_params) { [base_url] } key2 = cache.key('put', params_url, 'body', cache_scope) expect(key1).not_to eq key2 end - it "should not use ignore_params when whitelisted" do + it 'does not use ignore_params when whitelisted' do allow(Billy.config).to receive(:allow_params) { [base_url] } expect(cache).to receive(:format_url).once.with(params_url, true).and_call_original expect(cache).to receive(:format_url).once.with(params_url, false).and_call_original cache.key('put', params_url, 'body', cache_scope) end - it "should use ignore_params when not whitelisted" do + it 'uses ignore_params when not whitelisted' do expect(cache).to receive(:format_url).twice.with(params_url, true).and_call_original cache.key('put', params_url, 'body', cache_scope) end diff --git a/spec/lib/billy/handlers/cache_handler_spec.rb b/spec/lib/billy/handlers/cache_handler_spec.rb index 5fc97bf6..851ee5a9 100644 --- a/spec/lib/billy/handlers/cache_handler_spec.rb +++ b/spec/lib/billy/handlers/cache_handler_spec.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::CacheHandler do - let(:handler) { Billy::CacheHandler.new } + let(:handler) { described_class.new } let(:request_url) { 'http://example.test:8080/index?some=param&callback=dynamicCallback5678' } let(:request) do { - method: 'post', - url: request_url, - headers: { 'Accept-Encoding' => 'gzip', - 'Cache-Control' => 'no-cache' }, - body: 'Some body' + method: 'post', + url: request_url, + headers: { 'Accept-Encoding' => 'gzip', + 'Cache-Control' => 'no-cache' }, + body: 'Some body' } end let(:cache_scope) { 0 } @@ -48,17 +50,19 @@ request[:url], request[:headers], request[:body], - cache_scope)).to be nil + cache_scope)).to be_nil end it 'returns a cached response if the request can be handled' do expect(Billy::Cache.instance).to receive(:cached?).and_return(true) - expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, content: 'The response body') + expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, + content: 'The response body') expect(handler.handle_request(request[:method], request[:url], request[:headers], request[:body], - cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, content: 'The response body') + cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, + content: 'The response body') end context 'updating jsonp callback names enabled' do @@ -68,60 +72,66 @@ it 'updates the cached response if the callback is dynamic' do expect(Billy::Cache.instance).to receive(:cached?).and_return(true) - expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, content: 'dynamicCallback1234({"yolo":"kitten"})') + expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, + content: 'dynamicCallback1234({"yolo":"kitten"})') expect(handler.handle_request(request[:method], request[:url], request[:headers], request[:body], - cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, content: 'dynamicCallback5678({"yolo":"kitten"})') + cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, + content: 'dynamicCallback5678({"yolo":"kitten"})') end it 'is flexible about the format of the response body' do expect(Billy::Cache.instance).to receive(:cached?).and_return(true) - expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, content: "/**/ dynamicCallback1234(\n{\"yolo\":\"kitten\"})") + expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, + content: "/**/ dynamicCallback1234(\n{\"yolo\":\"kitten\"})") expect(handler.handle_request(request[:method], request[:url], request[:headers], request[:body], - cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, content: "/**/ dynamicCallback5678(\n{\"yolo\":\"kitten\"})") + cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, + content: "/**/ dynamicCallback5678(\n{\"yolo\":\"kitten\"})") end it 'does not interfere with non-jsonp requests' do - jsonp_request = request + request other_request = { - method: 'get', - url: 'http://example.test:8080/index?hanukkah=latkes', + method: 'get', + url: 'http://example.test:8080/index?hanukkah=latkes', headers: { 'Accept-Encoding' => 'gzip', 'Cache-Control' => 'no-cache' }, - body: 'no jsonp' + body: 'no jsonp' } - allow(Billy::Cache.instance).to receive(:cached?).and_return(true) - allow(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, - headers: { 'Connection' => 'close' }, - content: 'no jsonp but has parentheses()') + allow(Billy::Cache.instance).to receive_messages(cached?: true, fetch: { status: 200, + headers: { 'Connection' => 'close' }, + content: 'no jsonp but has parentheses()' }) expect(handler.handle_request(other_request[:method], other_request[:url], other_request[:headers], other_request[:body], - cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, content: 'no jsonp but has parentheses()') + cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, + content: 'no jsonp but has parentheses()') end context 'when after_cache_handles_request is set' do - it "should call the callback with the request and response" do + it 'calls the callback with the request and response' do allow(Billy.config).to receive(:after_cache_handles_request) do proc do |request, response| - response[:headers]['Access-Control-Allow-Origin'] = "*" + response[:headers]['Access-Control-Allow-Origin'] = '*' response[:content] = request[:body] end end expect(Billy::Cache.instance).to receive(:cached?).and_return(true) - expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, content: 'The response body') + expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, + headers: { 'Connection' => 'close' }, content: 'The response body') expect(handler.handle_request(request[:method], request[:url], request[:headers], request[:body], - cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close', 'Access-Control-Allow-Origin' => "*" }, content: 'Some body') + cache_scope)).to eql(status: 200, + headers: { 'Connection' => 'close', 'Access-Control-Allow-Origin' => '*' }, content: 'Some body') end end @@ -135,14 +145,16 @@ end end - it 'should call the callback with the specified name' do + it 'calls the callback with the specified name' do expect(Billy::Cache.instance).to receive(:cached?).and_return(true) - expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, content: 'dynamicCallback1234({"yolo":"kitten"})') + expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, + headers: { 'Connection' => 'close' }, content: 'dynamicCallback1234({"yolo":"kitten"})') expect(handler.handle_request(request[:method], request[:url], request[:headers], request[:body], - cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, content: 'dynamicCallback5678({"yolo":"kitten"})') + cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, + content: 'dynamicCallback5678({"yolo":"kitten"})') end end end @@ -154,12 +166,14 @@ it 'does not change the response' do expect(Billy::Cache.instance).to receive(:cached?).and_return(true) - expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, content: 'dynamicCallback1234({"yolo":"kitten"})') + expect(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, + content: 'dynamicCallback1234({"yolo":"kitten"})') expect(handler.handle_request(request[:method], request[:url], request[:headers], request[:body], - cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, content: 'dynamicCallback1234({"yolo":"kitten"})') + cache_scope)).to eql(status: 200, headers: { 'Connection' => 'close' }, + content: 'dynamicCallback1234({"yolo":"kitten"})') end end @@ -170,13 +184,13 @@ request[:url], request[:headers], request[:body], - cache_scope)).to be nil + cache_scope)).to be_nil end context 'network delay simulation' do before do - allow(Billy::Cache.instance).to receive(:cached?).and_return(true) - allow(Billy::Cache.instance).to receive(:fetch).and_return(status: 200, headers: { 'Connection' => 'close' }, content: 'dynamicCallback1234({"yolo":"kitten"})') + allow(Billy::Cache.instance).to receive_messages(cached?: true, fetch: { status: 200, headers: { 'Connection' => 'close' }, + content: 'dynamicCallback1234({"yolo":"kitten"})' }) end context 'when cache_simulates_network_delays is disabled' do diff --git a/spec/lib/billy/handlers/handler_spec.rb b/spec/lib/billy/handlers/handler_spec.rb index 71994f7a..5287e67d 100644 --- a/spec/lib/billy/handlers/handler_spec.rb +++ b/spec/lib/billy/handlers/handler_spec.rb @@ -1,9 +1,13 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::Handler do let(:handler) { Class.new { include Billy::Handler }.new } + it '#handle_request raises an error if not overridden' do - expect(handler.handle_request(nil, nil, nil, nil)).to eql(error: 'The handler has not overridden the handle_request method!') + expect(handler.handle_request(nil, nil, nil, + nil)).to eql(error: 'The handler has not overridden the handle_request method!') end it '#handles_request returns false by default' do diff --git a/spec/lib/billy/handlers/proxy_handler_spec.rb b/spec/lib/billy/handlers/proxy_handler_spec.rb index ad170480..d3d5855d 100644 --- a/spec/lib/billy/handlers/proxy_handler_spec.rb +++ b/spec/lib/billy/handlers/proxy_handler_spec.rb @@ -1,14 +1,17 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::ProxyHandler do - subject { Billy::ProxyHandler.new } + subject { described_class.new } + let(:request) do { - method: 'post', - url: 'http://usern:pw@example.test:8080/index?some=param', - headers: { 'Accept-Encoding' => 'gzip', - 'Cache-Control' => 'no-cache' }, - body: 'Some body' + method: 'post', + url: 'http://usern:pw@example.test:8080/index?some=param', + headers: { 'Accept-Encoding' => 'gzip', + 'Cache-Control' => 'no-cache' }, + body: 'Some body' } end let(:cache_scope) { 0 } @@ -27,6 +30,7 @@ cache_scope)).to be true end end + context 'with non-whitelisted requests disabled' do before do expect(Billy.config).to receive(:non_whitelisted_requests_disabled).and_return(true) @@ -43,7 +47,7 @@ context 'a whitelisted host' do context 'with a blacklisted path' do before do - expect(Billy.config).to receive(:path_blacklist) { ['/index'] } + expect(Billy.config).to receive(:path_blacklist).and_return(['/index']) end it 'does not handle requests for blacklisted paths' do @@ -57,7 +61,7 @@ context 'as a regex' do before do - expect(Billy.config).to receive(:whitelist) { [%r{example\.test\/a}] } + expect(Billy.config).to receive(:whitelist).and_return([%r{example\.test/a}]) end it 'handles requests for the host without a port' do @@ -79,7 +83,7 @@ context 'without a port' do before do - expect(Billy.config).to receive(:whitelist) { ['example.test'] } + expect(Billy.config).to receive(:whitelist).and_return(['example.test']) end it 'handles requests for the host without a port' do @@ -101,7 +105,7 @@ context 'with a port' do before do - expect(Billy.config).to receive(:whitelist) { ['example.test:8080'] } + expect(Billy.config).to receive(:whitelist).and_return(['example.test:8080']) end it 'does not handle requests whitelisted for a specific port' do @@ -131,7 +135,7 @@ request[:url], request[:headers], request[:body], - cache_scope)).to be nil + cache_scope)).to be_nil end context 'with a handled request' do @@ -179,15 +183,13 @@ request[:url], request[:headers], request[:body], - cache_scope)).to be nil + cache_scope)).to be_nil end it 'caches the response if cacheable' do # cacheable? requires Billy.config.cache and refresh_persisted_cache to be true - allow(Billy.config).to receive(:cache).and_return(true) - allow(Billy.config).to receive(:refresh_persisted_cache).and_return(true) - allow(Billy.config).to receive(:whitelist).and_return([]) - allow(Billy.config).to receive(:path_blacklist).and_return(['/index']) + allow(Billy.config).to receive_messages(cache: true, refresh_persisted_cache: true, whitelist: [], + path_blacklist: ['/index']) expect(Billy::Cache.instance).to receive(:store) subject.handle_request(request[:method], request[:url], @@ -197,13 +199,12 @@ end it 'uses the timeouts defined in configuration' do - allow(Billy.config).to receive(:proxied_request_inactivity_timeout).and_return(42) - allow(Billy.config).to receive(:proxied_request_connect_timeout).and_return(24) + allow(Billy.config).to receive_messages(proxied_request_inactivity_timeout: 42, + proxied_request_connect_timeout: 24) expect(EventMachine::HttpRequest).to receive(:new).with(request[:url], - inactivity_timeout: 42, - connect_timeout: 24 - ) + { inactivity_timeout: 42, + connect_timeout: 24 }) subject.handle_request(request[:method], request[:url], @@ -213,14 +214,12 @@ end it 'uses the internal proxy settings defined in configuration' do - allow(Billy.config).to receive(:proxied_request_host).and_return('10.10.10.10') - allow(Billy.config).to receive(:proxied_request_port).and_return('2080') + allow(Billy.config).to receive_messages(proxied_request_host: '10.10.10.10', proxied_request_port: '2080') expect(EventMachine::HttpRequest).to receive(:new).with(request[:url], - inactivity_timeout: 10, - connect_timeout: 5, - proxy: { host: '10.10.10.10', port: '2080' } - ) + { inactivity_timeout: 10, + connect_timeout: 5, + proxy: { host: '10.10.10.10', port: '2080' } }) subject.handle_request(request[:method], request[:url], @@ -234,15 +233,15 @@ describe '#build_request_options' do it 'creates authorization header when URL has basic auth' do request_options = subject.send(:build_request_options, request[:url], - request[:headers], - request[:body]) + request[:headers], + request[:body]) expect(request_options[:head]).to have_key 'authorization' end it 'does not include authorization header without basic auth' do - request_options = subject.send(:build_request_options, request[:url].gsub('usern:pw@',''), - request[:headers], - request[:body]) + request_options = subject.send(:build_request_options, request[:url].gsub('usern:pw@', ''), + request[:headers], + request[:body]) expect(request_options[:head]).not_to have_key 'authorization' end end diff --git a/spec/lib/billy/handlers/request_handler_spec.rb b/spec/lib/billy/handlers/request_handler_spec.rb index 2258de4c..8d6fb5ec 100644 --- a/spec/lib/billy/handlers/request_handler_spec.rb +++ b/spec/lib/billy/handlers/request_handler_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::RequestHandler do - subject { Billy::RequestHandler.new } + subject { described_class.new } it 'implements Handler' do expect(subject).to be_a Billy::Handler @@ -22,7 +24,7 @@ end context 'with stubbed handlers' do - let(:args) { %w(get url headers body) } + let(:args) { %w[get url headers body] } let(:stub_handler) { double('StubHandler') } let(:cache_handler) { double('CacheHandler') } let(:proxy_handler) { double('ProxyHandler') } @@ -38,10 +40,10 @@ allow(subject).to receive(:handlers).and_return(handlers) end - # Note: handles_request? is commented out in the lib, so we skip these tests + # NOTE: handles_request? is commented out in the lib, so we skip these tests describe '#handles_request?', skip: 'handles_request? method is commented out in lib' do it 'returns false if no handlers handle the request' do - handlers.each do |_key, handler| + handlers.each_value do |handler| allow(handler).to receive(:handles_request?).and_return(false) end expect(subject.handles_request?(*args)).to be false @@ -68,49 +70,61 @@ describe '#handle_request' do before do - allow(Billy::config).to receive(:record_requests).and_return(true) + allow(Billy.config).to receive(:record_requests).and_return(true) end it 'returns stubbed responses' do - expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return({ cache_key: 'test' }) - expect(cache_handler).to_not receive(:handle_request) - expect(proxy_handler).to_not receive(:handle_request) + expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return({ cache_key: 'test' }) + expect(cache_handler).not_to receive(:handle_request) + expect(proxy_handler).not_to receive(:handle_request) result = subject.handle_request(*args) expect(result).to include(cache_key: 'test') expect(subject.requests.first).to include(status: :complete, handler: :stubs, method: 'get', url: 'url') end it 'returns cached responses' do - expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) - expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return({ cache_key: 'test' }) - expect(proxy_handler).to_not receive(:handle_request) + expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) + expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return({ cache_key: 'test' }) + expect(proxy_handler).not_to receive(:handle_request) result = subject.handle_request(*args) expect(result).to include(cache_key: 'test') expect(subject.requests.first).to include(status: :complete, handler: :cache, method: 'get', url: 'url') end it 'returns proxied responses' do - expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) - expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) - expect(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return({ cache_key: 'test' }) + expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) + expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) + expect(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return({ cache_key: 'test' }) result = subject.handle_request(*args) expect(result).to include(cache_key: 'test') expect(subject.requests.first).to include(status: :complete, handler: :proxy, method: 'get', url: 'url') end it 'returns an error hash if request is not handled' do - expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) - expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) - expect(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) + expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) + expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) + expect(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) expect(subject.handle_request(*args)).to eql(error: 'Connection to url not cached and new http connections are disabled') expect(subject.requests.first).to include(status: :complete, handler: :error, method: 'get', url: 'url') end it 'returns an error hash with body message if request cached based on body is not handled' do args[0] = Billy.config.cache_request_body_methods[0] - expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) - expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) - expect(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_return(nil) + expect(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) + expect(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) + expect(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_return(nil) expect(subject.handle_request(*args)).to eql(error: "Connection to url with body 'body' not cached and new http connections are disabled") expect(subject.requests.first).to include(status: :complete, handler: :error, method: 'post', url: 'url') end @@ -120,14 +134,17 @@ allow(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything) allow(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything) - allow(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_raise("Any Proxy Error") - expect(subject.handle_request(*args)).to eql(error: "Any Proxy Error") + allow(proxy_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_raise('Any Proxy Error') + expect(subject.handle_request(*args)).to eql(error: 'Any Proxy Error') - allow(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_raise("Any Cache Error") - expect(subject.handle_request(*args)).to eql(error: "Any Cache Error") + allow(cache_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_raise('Any Cache Error') + expect(subject.handle_request(*args)).to eql(error: 'Any Cache Error') - allow(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, anything).and_raise("Any Stub Error") - expect(subject.handle_request(*args)).to eql(error: "Any Stub Error") + allow(stub_handler).to receive(:handle_request).with(anything, anything, anything, anything, + anything).and_raise('Any Stub Error') + expect(subject.handle_request(*args)).to eql(error: 'Any Stub Error') end end @@ -147,7 +164,7 @@ describe '#reset' do it 'resets all of the handlers' do - handlers.each do |_key, handler| + handlers.each_value do |handler| expect(handler).to receive(:reset) end expect(subject.request_log).to receive(:reset) diff --git a/spec/lib/billy/handlers/request_log_spec.rb b/spec/lib/billy/handlers/request_log_spec.rb index c096f9ec..0ea1b755 100644 --- a/spec/lib/billy/handlers/request_log_spec.rb +++ b/spec/lib/billy/handlers/request_log_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::RequestLog do - let(:request_log) { Billy::RequestLog.new } + let(:request_log) { described_class.new } let(:cache_scope) { 0 } describe '#record' do it 'returns the request details if record_requests is enabled' do - allow(Billy::config).to receive(:record_requests).and_return(true) + allow(Billy.config).to receive(:record_requests).and_return(true) expected_request = { scope: cache_scope, cache_key: nil, @@ -21,14 +23,14 @@ end it 'returns nil if record_requests is disabled' do - allow(Billy::config).to receive(:record_requests).and_return(false) + allow(Billy.config).to receive(:record_requests).and_return(false) expect(request_log.record(:method, :url, :headers, :body, cache_scope)).to be_nil end end describe '#complete' do it 'marks the request as complete if record_requests is enabled' do - allow(Billy::config).to receive(:record_requests).and_return(true) + allow(Billy.config).to receive(:record_requests).and_return(true) request = request_log.record(:method, :url, :headers, :body, cache_scope) expected_request = { @@ -45,7 +47,7 @@ end it 'marks the request as complete if record_requests is disabled' do - allow(Billy::config).to receive(:record_requests).and_return(false) + allow(Billy.config).to receive(:record_requests).and_return(false) expect(request_log.complete(nil, :handler, :cache_key)).to be_nil end end @@ -56,7 +58,7 @@ end it 'returns the currently known requests' do - allow(Billy::config).to receive(:record_requests).and_return(true) + allow(Billy.config).to receive(:record_requests).and_return(true) request1 = request_log.record(:method, :url, :headers, :body, cache_scope) request2 = request_log.record(:method, :url, :headers, :body, cache_scope) @@ -66,7 +68,7 @@ describe '#reset' do it 'resets known requests' do - allow(Billy::config).to receive(:record_requests).and_return(true) + allow(Billy.config).to receive(:record_requests).and_return(true) request1 = request_log.record(:method, :url, :headers, :body, cache_scope) request2 = request_log.record(:method, :url, :headers, :body, cache_scope) diff --git a/spec/lib/billy/handlers/stub_handler_spec.rb b/spec/lib/billy/handlers/stub_handler_spec.rb index 32fda434..ce85a1da 100644 --- a/spec/lib/billy/handlers/stub_handler_spec.rb +++ b/spec/lib/billy/handlers/stub_handler_spec.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::StubHandler do - let(:handler) { Billy::StubHandler.new } + let(:handler) { described_class.new } let(:request) do { - method: 'GET', - url: 'http://example.test:8080/index?some=param', - headers: { 'Accept-Encoding' => 'gzip', - 'Cache-Control' => 'no-cache' }, - body: 'Some body' + method: 'GET', + url: 'http://example.test:8080/index?some=param', + headers: { 'Accept-Encoding' => 'gzip', + 'Cache-Control' => 'no-cache' }, + body: 'Some body' } end let(:cache_scope) { 0 } @@ -28,7 +30,7 @@ describe '#handle_request' do it 'returns nil if the request is not stubbed' do expect(handler).to receive(:handles_request?).and_return(false) - expect(handler.handle_request(nil, nil, nil, nil, cache_scope)).to be nil + expect(handler.handle_request(nil, nil, nil, nil, cache_scope)).to be_nil end it 'returns a response hash if the request is stubbed' do diff --git a/spec/lib/billy/proxy_connection_spec.rb b/spec/lib/billy/proxy_connection_spec.rb index dc8514b4..e5c241a0 100644 --- a/spec/lib/billy/proxy_connection_spec.rb +++ b/spec/lib/billy/proxy_connection_spec.rb @@ -1,20 +1,22 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::ProxyConnection do - context '#prepare_response_headers_for_evma_httpserver' do - let(:subject) { Billy::ProxyConnection.new('') } - - it 'should remove duplicated headers fields' do - provided_headers = { - 'transfer-encoding' => '', - 'content-length' => '', - 'content-encoding' => '', - 'key' => 'value' - } - expected_headers = { 'key' => 'value' } - headers = subject.send(:prepare_response_headers_for_evma_httpserver, provided_headers) + describe '#prepare_response_headers_for_evma_httpserver' do + let(:subject) { described_class.new('') } + + it 'removes duplicated headers fields' do + provided_headers = { + 'transfer-encoding' => '', + 'content-length' => '', + 'content-encoding' => '', + 'key' => 'value' + } + expected_headers = { 'key' => 'value' } + headers = subject.send(:prepare_response_headers_for_evma_httpserver, provided_headers) - expect(headers).to eql expected_headers + expect(headers).to eql expected_headers end end end diff --git a/spec/lib/billy/proxy_request_stub_spec.rb b/spec/lib/billy/proxy_request_stub_spec.rb index 4329c931..3cf8dcb5 100644 --- a/spec/lib/billy/proxy_request_stub_spec.rb +++ b/spec/lib/billy/proxy_request_stub_spec.rb @@ -1,86 +1,88 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::ProxyRequestStub do - context '#matches?' do - it 'should match urls and methods' do - expect(Billy::ProxyRequestStub.new('http://example.com') + describe '#matches?' do + it 'matches urls and methods' do + expect(described_class.new('http://example.com') .matches?('GET', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new('http://example.com') - .matches?('POST', 'http://example.com')).to_not be + expect(described_class.new('http://example.com') + .matches?('POST', 'http://example.com')).not_to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :get) + expect(described_class.new('http://example.com', method: :get) .matches?('GET', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :post) - .matches?('GET', 'http://example.com')).to_not be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :options) - .matches?('GET', 'http://example.com')).to_not be + expect(described_class.new('http://example.com', method: :post) + .matches?('GET', 'http://example.com')).not_to be + expect(described_class.new('http://example.com', method: :options) + .matches?('GET', 'http://example.com')).not_to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :post) + expect(described_class.new('http://example.com', method: :post) .matches?('POST', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new('http://fooxample.com', method: :post) - .matches?('POST', 'http://example.com')).to_not be - expect(Billy::ProxyRequestStub.new('http://fooxample.com', method: :options) - .matches?('POST', 'http://example.com')).to_not be + expect(described_class.new('http://fooxample.com', method: :post) + .matches?('POST', 'http://example.com')).not_to be + expect(described_class.new('http://fooxample.com', method: :options) + .matches?('POST', 'http://example.com')).not_to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :options) + expect(described_class.new('http://example.com', method: :options) .matches?('OPTIONS', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :options) - .matches?('OPTIONS', 'http://zzzzzexample.com')).to_not be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :post) - .matches?('OPTIONS', 'http://example.com')).to_not be + expect(described_class.new('http://example.com', method: :options) + .matches?('OPTIONS', 'http://zzzzzexample.com')).not_to be + expect(described_class.new('http://example.com', method: :post) + .matches?('OPTIONS', 'http://example.com')).not_to be end - it 'should match regexps' do - expect(Billy::ProxyRequestStub.new(/http:\/\/.+\.com/, method: :post) + it 'matches regexps' do + expect(described_class.new(%r{http://.+\.com}, method: :post) .matches?('POST', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new(/http:\/\/.+\.co\.uk/, method: :get) - .matches?('GET', 'http://example.com')).to_not be + expect(described_class.new(%r{http://.+\.co\.uk}, method: :get) + .matches?('GET', 'http://example.com')).not_to be end - it 'should match up to but not including query strings' do - stub = Billy::ProxyRequestStub.new('http://example.com/foo/bar/') - expect(stub.matches?('GET', 'http://example.com/foo/')).to_not be + it 'matches up to but not including query strings' do + stub = described_class.new('http://example.com/foo/bar/') + expect(stub.matches?('GET', 'http://example.com/foo/')).not_to be expect(stub.matches?('GET', 'http://example.com/foo/bar/')).to be expect(stub.matches?('GET', 'http://example.com/foo/bar/?baz=bap')).to be end - it 'should match all methods' do - expect(Billy::ProxyRequestStub.new('http://example.com', method: :all) + it 'matches all methods' do + expect(described_class.new('http://example.com', method: :all) .matches?('GET', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :all) + expect(described_class.new('http://example.com', method: :all) .matches?('POST', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :all) + expect(described_class.new('http://example.com', method: :all) .matches?('OPTIONS', 'http://example.com')).to be - expect(Billy::ProxyRequestStub.new('http://example.com', method: :all) + expect(described_class.new('http://example.com', method: :all) .matches?('HEAD', 'http://example.com')).to be end end - context "#matches? (with strip_query_params false in config)" do + describe '#matches? (with strip_query_params false in config)' do before do Billy.config.strip_query_params = false end - it 'should not match up to request with query strings' do - stub = Billy::ProxyRequestStub.new('http://example.com/foo/bar/') - expect(stub.matches?('GET', 'http://example.com/foo/')).to_not be + it 'does not match up to request with query strings' do + stub = described_class.new('http://example.com/foo/bar/') + expect(stub.matches?('GET', 'http://example.com/foo/')).not_to be expect(stub.matches?('GET', 'http://example.com/foo/bar/')).to be - expect(stub.matches?('GET', 'http://example.com/foo/bar/?baz=bap')).to_not be + expect(stub.matches?('GET', 'http://example.com/foo/bar/?baz=bap')).not_to be end end - context '#call (without #and_return)' do - let(:subject) { Billy::ProxyRequestStub.new('url') } + describe '#call (without #and_return)' do + let(:subject) { described_class.new('url') } it 'returns a 204 empty response' do expect(subject.call('', '', {}, {}, nil)).to eql [204, { 'Content-Type' => 'text/plain' }, ''] end end - context '#and_return + #call' do - let(:subject) { Billy::ProxyRequestStub.new('url') } + describe '#and_return + #call' do + let(:subject) { described_class.new('url') } - it 'should generate bare responses' do + it 'generates bare responses' do subject.and_return body: 'baz foo bar' expect(subject.call('', '', {}, {}, nil)).to eql [ 200, @@ -89,7 +91,7 @@ ] end - it 'should generate text responses' do + it 'generates text responses' do subject.and_return text: 'foo bar baz' expect(subject.call('', '', {}, {}, nil)).to eql [ 200, @@ -98,7 +100,7 @@ ] end - it 'should generate JSON responses' do + it 'generates JSON responses' do subject.and_return json: { foo: 'bar' } expect(subject.call('', '', {}, {}, nil)).to eql [ 200, @@ -108,7 +110,7 @@ end context 'JSONP' do - it 'should generate JSONP responses' do + it 'generates JSONP responses' do subject.and_return jsonp: { foo: 'bar' } expect(subject.call('', '', { 'callback' => ['baz'] }, {}, nil)).to eql [ 200, @@ -117,7 +119,7 @@ ] end - it 'should generate JSONP responses with custom callback parameter' do + it 'generates JSONP responses with custom callback parameter' do subject.and_return jsonp: { foo: 'bar' }, callback_param: 'cb' expect(subject.call('', '', { 'cb' => ['bap'] }, {}, nil)).to eql [ 200, @@ -126,7 +128,7 @@ ] end - it 'should generate JSONP responses with custom callback name' do + it 'generates JSONP responses with custom callback name' do subject.and_return jsonp: { foo: 'bar' }, callback: 'cb' expect(subject.call('', '', {}, {}, nil)).to eql [ 200, @@ -136,7 +138,7 @@ end end - it 'should generate redirection responses' do + it 'generates redirection responses' do subject.and_return redirect_to: 'http://example.com' expect(subject.call('', '', {}, {}, nil)).to eql [ 302, @@ -145,7 +147,7 @@ ] end - it 'should set headers' do + it 'sets headers' do subject.and_return text: 'foo', headers: { 'HTTP-X-Foo' => 'bar' } expect(subject.call('', '', {}, {}, nil)).to eql [ 200, @@ -154,7 +156,7 @@ ] end - it 'should set status codes' do + it 'sets status codes' do subject.and_return text: 'baz', code: 410 expect(subject.call('', '', {}, {}, nil)).to eql [ 410, @@ -163,7 +165,7 @@ ] end - it 'should use a callable' do + it 'uses a callable' do expected_params = { 'param1' => ['one'], 'param2' => ['two'] } expected_headers = { 'header1' => 'three', 'header2' => 'four' } expected_body = 'body text' @@ -183,7 +185,7 @@ ] end - it 'should use a callable with Billy.pass_request' do + it 'uses a callable with Billy.pass_request' do # Stub the proxy handler to avoid ArgumentError from cache_scope mismatch allow(Billy.proxy.request_handler.handlers[:proxy]).to receive(:handle_request).and_return( status: 200, @@ -211,19 +213,19 @@ end end - context '#stub_requests' do - let(:subject) { Billy::ProxyRequestStub.new('url') } + describe '#stub_requests' do + let(:subject) { described_class.new('url') } - before :each do + before do Billy.config.record_stub_requests = true end - it 'should record requests' do + it 'records requests' do subject.call('', '', {}, {}, nil) expect(subject.has_requests?).to be true end - it 'should record multiple requests' do + it 'records multiple requests' do expected_amount = 3 expected_amount.times do subject.call('', '', {}, {}, nil) @@ -232,7 +234,7 @@ expect(subject.requests.length).to eql expected_amount end - it 'should set a request' do + it 'sets a request' do expected_request = { method: 'POST', url: 'test-url', diff --git a/spec/lib/billy/resource_utils_spec.rb b/spec/lib/billy/resource_utils_spec.rb index 62e3cf93..be3441ab 100644 --- a/spec/lib/billy/resource_utils_spec.rb +++ b/spec/lib/billy/resource_utils_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::JSONUtils do @@ -6,29 +8,31 @@ it 'sorts simple Hashes' do data = { c: 'three', a: 'one', b: 'two' } expected = { a: 'one', b: 'two', c: 'three' } - expect(Billy::JSONUtils.sort_hash_keys(data)).to eq expected + expect(described_class.sort_hash_keys(data)).to eq expected end it 'does not sort simple Arrays' do - data = [3, 1, 2, 'two', 'three', 'one'] - expect(Billy::JSONUtils.sort_hash_keys(data)).to eq data + data = [3, 1, 2, 'two', 'three', 'one'] + expect(described_class.sort_hash_keys(data)).to eq data end it 'does not sort multi-dimensional Arrays' do - data = [[3, 2, 1], [5, 4, 6], %w(b c a)] - expect(Billy::JSONUtils.sort_hash_keys(data)).to eq data + data = [[3, 2, 1], [5, 4, 6], %w[b c a]] + expect(described_class.sort_hash_keys(data)).to eq data end it 'sorts multi-dimensional Hashes' do data = { c: { l: 2, m: 3, k: 1 }, a: { f: 3, e: 2, d: 1 }, b: { i: 2, h: 1, j: 3 } } expected = { a: { d: 1, e: 2, f: 3 }, b: { h: 1, i: 2, j: 3 }, c: { k: 1, l: 2, m: 3 } } - expect(Billy::JSONUtils.sort_hash_keys(data)).to eq expected + expect(described_class.sort_hash_keys(data)).to eq expected end it 'sorts abnormal data structures' do - data = { b: [%w(b c a), { ab: 5, aa: 4, ac: 6 }, [3, 2, 1], { ba: true, bc: false, bb: nil }], a: { f: 3, e: 2, d: 1 } } - expected = { a: { d: 1, e: 2, f: 3 }, b: [%w(b c a), { aa: 4, ab: 5, ac: 6 }, [3, 2, 1], { ba: true, bb: nil, bc: false }] } - expect(Billy::JSONUtils.sort_hash_keys(data)).to eq expected + data = { b: [%w[b c a], { ab: 5, aa: 4, ac: 6 }, [3, 2, 1], { ba: true, bc: false, bb: nil }], + a: { f: 3, e: 2, d: 1 } } + expected = { a: { d: 1, e: 2, f: 3 }, + b: [%w[b c a], { aa: 4, ab: 5, ac: 6 }, [3, 2, 1], { ba: true, bb: nil, bc: false }] } + expect(described_class.sort_hash_keys(data)).to eq expected end end @@ -36,7 +40,7 @@ it 'sorts JSON' do data = '{"c":"three","a":"one","b":"two"}' expected = '{"a":"one","b":"two","c":"three"}' - expect(Billy::JSONUtils.sort_json(data)).to eq expected + expect(described_class.sort_json(data)).to eq expected end end end @@ -46,10 +50,11 @@ let(:non_json) { 'Not JSON.' } it 'identifies JSON' do - expect(Billy::JSONUtils.json?(json)).to be true + expect(described_class.json?(json)).to be true end + it 'identifies non-JSON' do - expect(Billy::JSONUtils.json?(non_json)).to be false + expect(described_class.json?(non_json)).to be false end end end diff --git a/spec/lib/billy/ssl/authority_spec.rb b/spec/lib/billy/ssl/authority_spec.rb index 2a9da5ca..1bfd7a40 100644 --- a/spec/lib/billy/ssl/authority_spec.rb +++ b/spec/lib/billy/ssl/authority_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::Authority do - let(:auth1) { Billy::Authority.new } - let(:auth2) { Billy::Authority.new } + let(:auth1) { described_class.new } + let(:auth2) { described_class.new } - context('#key') do + describe('#key') do it 'generates a new key each time' do expect(auth1.key).not_to be(auth2.key) end @@ -14,7 +16,7 @@ end end - context('#cert') do + describe('#cert') do it 'generates a new certificate each time' do expect(auth1.cert).not_to be(auth2.cert) end @@ -35,12 +37,12 @@ it 'configures the subject' do expect(auth1.cert.subject.to_s).to \ - be_eql('/CN=Puffing Billy/O=Puffing Billy') + eql('/CN=Puffing Billy/O=Puffing Billy') end it 'configures the certificate authority constrain' do expect(auth1.cert.extensions.first.to_s).to \ - be_eql('basicConstraints = critical, CA:TRUE') + eql('basicConstraints = critical, CA:TRUE') end it 'configures SSLv3' do @@ -49,7 +51,7 @@ end end - context('#key_file') do + describe('#key_file') do it 'pass back the path' do expect(auth1.key_file).to match(/ca.key$/) end @@ -59,7 +61,7 @@ end it 'creates a PEM formatted certificate' do - expect(File.read(auth1.key_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/) + expect(File.read(auth1.key_file)).to match(%r{^[A-Za-z0-9\-+/=]+$}) end it 'writes out a private key' do @@ -68,7 +70,7 @@ end end - context('#cert_file') do + describe('#cert_file') do it 'pass back the path' do expect(auth1.cert_file).to match(/ca.crt$/) end @@ -78,7 +80,7 @@ end it 'creates a PEM formatted certificate' do - expect(File.read(auth1.cert_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/) + expect(File.read(auth1.cert_file)).to match(%r{^[A-Za-z0-9\-+/=]+$}) end end end diff --git a/spec/lib/billy/ssl/certificate_chain_spec.rb b/spec/lib/billy/ssl/certificate_chain_spec.rb index d64489b2..ef767303 100644 --- a/spec/lib/billy/ssl/certificate_chain_spec.rb +++ b/spec/lib/billy/ssl/certificate_chain_spec.rb @@ -1,23 +1,25 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::CertificateChain do let(:cert1) { Billy::Certificate.new('localhost') } let(:cert2) { Billy::Certificate.new('localhost.localdomain') } let(:chain) do - Billy::CertificateChain.new('localhost', cert1.cert, cert2.cert) + described_class.new('localhost', cert1.cert, cert2.cert) end - context('#initialize') do + describe('#initialize') do it 'holds all certificates in order' do - expect(chain.certificates).to be_eql([cert1.cert, cert2.cert]) + expect(chain.certificates).to eql([cert1.cert, cert2.cert]) end it 'holds the domain' do - expect(chain.domain).to be_eql('localhost') + expect(chain.domain).to eql('localhost') end end - context('#file') do + describe('#file') do it 'pass back the path' do expect(chain.file).to match(/chain-localhost.pem/) end @@ -33,7 +35,7 @@ end it 'creates a PEM formatted certificate chain' do - expect(File.read(chain.file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/) + expect(File.read(chain.file)).to match(%r{^[A-Za-z0-9\-+/=]+$}) end end end diff --git a/spec/lib/billy/ssl/certificate_spec.rb b/spec/lib/billy/ssl/certificate_spec.rb index 56fc34a2..e45a6afd 100644 --- a/spec/lib/billy/ssl/certificate_spec.rb +++ b/spec/lib/billy/ssl/certificate_spec.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + require 'spec_helper' describe Billy::Certificate do - let(:cert1) { Billy::Certificate.new('localhost') } - let(:cert2) { Billy::Certificate.new('localhost.localdomain') } + let(:cert1) { described_class.new('localhost') } + let(:cert2) { described_class.new('localhost.localdomain') } - context('#domain') do + describe('#domain') do it 'holds the domain' do - expect(Billy::Certificate.new('test.tld').domain).to be_eql('test.tld') + expect(described_class.new('test.tld').domain).to eql('test.tld') end end - context('#key') do + describe('#key') do it 'generates a new key each time' do expect(cert1.key).not_to be(cert2.key) end @@ -20,7 +22,7 @@ end end - context('#cert') do + describe('#cert') do it 'generates a new certificate each time' do expect(cert1.cert).not_to be(cert2.cert) end @@ -40,12 +42,12 @@ end it 'configures the correct subject' do - expect(cert1.cert.subject.to_s).to be_eql('/CN=localhost') + expect(cert1.cert.subject.to_s).to eql('/CN=localhost') end it 'configures the subject alternative names' do expect(cert1.cert.extensions.first.to_s).to \ - be_eql('subjectAltName = DNS:localhost') + eql('subjectAltName = DNS:localhost') end it 'configures SSLv3' do @@ -54,7 +56,7 @@ end end - context('#key_file') do + describe('#key_file') do it 'pass back the path' do expect(cert1.key_file).to match(/request-localhost.key$/) end @@ -64,7 +66,7 @@ end it 'creates a PEM formatted certificate' do - expect(File.read(cert1.key_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/) + expect(File.read(cert1.key_file)).to match(%r{^[A-Za-z0-9\-+/=]+$}) end it 'writes out a private key' do @@ -73,7 +75,7 @@ end end - context('#cert_file') do + describe('#cert_file') do it 'pass back the path' do expect(cert1.cert_file).to match(/request-localhost.crt$/) end @@ -83,7 +85,7 @@ end it 'creates a PEM formatted certificate' do - expect(File.read(cert1.cert_file)).to match(/^[A-Za-z0-9\-\+\/\=]+$/) + expect(File.read(cert1.cert_file)).to match(%r{^[A-Za-z0-9\-+/=]+$}) end end end diff --git a/spec/lib/billy/watir/watir_spec.rb b/spec/lib/billy/watir/watir_spec.rb index 86b921a2..544906d9 100644 --- a/spec/lib/billy/watir/watir_spec.rb +++ b/spec/lib/billy/watir/watir_spec.rb @@ -1,17 +1,19 @@ +# frozen_string_literal: true + require 'spec_helper' -describe 'Watir-specific tests', type: :feature, js: true do +describe 'Watir-specific tests', :js, type: :feature do before do proxy.stub('http://www.example.com/get').and_return( text: 'Success!' ) end - it 'should raise a NameError if an invalid browser driver is specified' do - expect{Billy::Browsers::Watir.new :invalid}.to raise_error(NameError) + it 'raises a NameError if an invalid browser driver is specified' do + expect { Billy::Browsers::Watir.new :invalid }.to raise_error(NameError) end - it 'should respond to a stubbed GET request' do + it 'responds to a stubbed GET request' do @browser.goto 'http://www.example.com/get' expect(@browser.text).to eq 'Success!' end diff --git a/spec/lib/proxy_spec.rb b/spec/lib/proxy_spec.rb index e8c6530d..f1a4d152 100644 --- a/spec/lib/proxy_spec.rb +++ b/spec/lib/proxy_spec.rb @@ -1,90 +1,92 @@ +# frozen_string_literal: true + require 'spec_helper' require 'resolv' shared_examples_for 'a proxy server' do - it 'should proxy GET requests' do + it 'proxies GET requests' do expect(http.get('/echo').body).to eql 'GET /echo' end - it 'should proxy POST requests' do + it 'proxies POST requests' do expect(http.post('/echo', foo: 'bar').body).to eql "POST /echo\nfoo=bar" end - it 'should proxy PUT requests' do - expect(http.post('/echo', foo: 'bar').body).to eql "POST /echo\nfoo=bar" + it 'proxies PUT requests' do + expect(http.put('/echo', foo: 'bar').body).to eql "PUT /echo\nfoo=bar" end - it 'should proxy HEAD requests' do + it 'proxies HEAD requests' do expect(http.head('/echo').headers['HTTP-X-EchoServer']).to eql 'HEAD /echo' end - it 'should proxy DELETE requests' do + it 'proxies DELETE requests' do expect(http.delete('/echo').body).to eql 'DELETE /echo' end - it 'should proxy OPTIONS requests' do + it 'proxies OPTIONS requests' do expect(http.run_request(:options, '/echo', nil, nil).body).to eql 'OPTIONS /echo' end end shared_examples_for 'a request stub' do - it 'should stub GET requests' do + it 'stubs GET requests' do proxy.stub("#{url}/foo") - .and_return(text: 'hello, GET!') + .and_return(text: 'hello, GET!') expect(http.get('/foo').body).to eql 'hello, GET!' end - it 'should stub GET response statuses' do + it 'stubs GET response statuses' do proxy.stub("#{url}/foo") - .and_return(code: 200) - expect(http.get('/foo').status).to eql 200 + .and_return(code: 200) + expect(http.get('/foo').status).to be 200 end - it 'should stub POST requests' do + it 'stubs POST requests' do proxy.stub("#{url}/bar", method: :post) - .and_return(text: 'hello, POST!') + .and_return(text: 'hello, POST!') expect(http.post('/bar', foo: :bar).body).to eql 'hello, POST!' end - it 'should stub PUT requests' do + it 'stubs PUT requests' do proxy.stub("#{url}/baz", method: :put) - .and_return(text: 'hello, PUT!') + .and_return(text: 'hello, PUT!') expect(http.put('/baz', foo: :bar).body).to eql 'hello, PUT!' end - it 'should stub HEAD requests' do + it 'stubs HEAD requests' do proxy.stub("#{url}/bap", method: :head) - .and_return(headers: { 'HTTP-X-Hello' => 'hello, HEAD!' }) + .and_return(headers: { 'HTTP-X-Hello' => 'hello, HEAD!' }) expect(http.head('/bap').headers['http-x-hello']).to eql 'hello, HEAD!' end - it 'should stub DELETE requests' do + it 'stubs DELETE requests' do proxy.stub("#{url}/bam", method: :delete) - .and_return(text: 'hello, DELETE!') + .and_return(text: 'hello, DELETE!') expect(http.delete('/bam').body).to eql 'hello, DELETE!' end - it 'should stub OPTIONS requests' do + it 'stubs OPTIONS requests' do proxy.stub("#{url}/bim", method: :options) - .and_return(text: 'hello, OPTIONS!') + .and_return(text: 'hello, OPTIONS!') expect(http.run_request(:options, '/bim', nil, nil).body).to eql 'hello, OPTIONS!' end - it 'should expose the currently registered stubs' do + it 'exposes the currently registered stubs' do stub1 = proxy.stub("#{url}/foo", method: :options) - .and_return(text: 'hello, OPTIONS!') + .and_return(text: 'hello, OPTIONS!') stub2 = proxy.stub("#{url}/bar", method: :options) - .and_return(text: 'hello, OPTIONS!') + .and_return(text: 'hello, OPTIONS!') expect(proxy.stubs).to eql([stub2, stub1]) end end -# Note: Caching integration tests are skipped because the lib's cacheable? method +# NOTE: Caching integration tests are skipped because the lib's cacheable? method # has custom logic (staging.hirefrederick.com checks) that makes these tests unreliable. # The core caching logic is tested in cache_spec.rb and cache_handler_spec.rb. shared_examples_for 'a cache' do context 'whitelisted GET requests' do - it 'should not be cached' do + it 'is not cached' do assert_noncached_url end @@ -94,7 +96,7 @@ Billy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port}"] end - it 'should not be cached ' do + it 'is not cached' do assert_noncached_url end end @@ -105,7 +107,7 @@ Billy.config.whitelist = [] end - it 'should be cached' do + it 'is cached' do assert_cached_url end @@ -115,7 +117,7 @@ Billy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port + 1}"] end - it 'should be cached' do + it 'is cached' do assert_cached_url end end @@ -126,14 +128,14 @@ Billy.config.ignore_params = ['/analytics'] end - it 'should be cached' do + it 'is cached' do r = http.get('/analytics?some_param=5') expect(r.body).to eql 'GET /analytics' expect do expect do r = http.get('/analytics?some_param=20') end.to change { r.headers['HTTP-X-EchoCount'].to_i }.by(1) - end.to_not change { r.body } + end.not_to(change { r.body }) end end @@ -142,7 +144,7 @@ Billy.config.path_blacklist = ['/api'] end - it 'should be cached' do + it 'is cached' do assert_cached_url('/api') end @@ -151,11 +153,11 @@ Billy.config.path_blacklist = [/widgets$/] end - it 'should not cache a non-match' do + it 'does not cache a non-match' do assert_noncached_url('/widgets/5/edit') end - it 'should cache a match' do + it 'caches a match' do assert_cached_url('/widgets') end end @@ -165,7 +167,7 @@ let(:cache_path) { Billy.config.cache_path } let(:cached_key) { proxy.cache.key('get', "#{url}/foo", '', 0) } let(:cached_file) do - f = cached_key + '.yml' + f = "#{cached_key}.yml" File.join(cache_path, f) end @@ -179,7 +181,7 @@ File.delete(cached_file) if File.exist?(cached_file) end - # Note: Cache persistence tests are partially skipped because cacheable? has custom lib logic + # NOTE: Cache persistence tests are partially skipped because cacheable? has custom lib logic context 'enabled' do before do Billy.config.persist_cache = true @@ -188,12 +190,12 @@ proxy.reset end - it 'should persist', skip: 'Depends on lib-specific cacheable? logic' do + it 'persists', skip: 'Depends on lib-specific cacheable? logic' do http.get('/foo') expect(File.exist?(cached_file)).to be true end - it 'should be read initially from persistent cache', skip: 'Depends on lib-specific cache lookup logic' do + it 'is read initially from persistent cache', skip: 'Depends on lib-specific cache lookup logic' do File.open(cached_file, 'w') do |f| cached = { headers: {}, @@ -207,10 +209,11 @@ end context 'cache_request_headers requests', skip: 'Depends on lib-specific cacheable? logic' do - it 'should not be cached by default' do + it 'is not cached by default' do http.get('/foo') # Only call fetch_from_persistence if file exists next unless File.exist?(cached_file) + saved_cache = Billy.proxy.cache.fetch_from_persistence(cached_key) expect(saved_cache.keys).not_to include :request_headers end @@ -220,10 +223,11 @@ Billy.config.cache_request_headers = true end - it 'should be cached' do + it 'is cached' do http.get('/foo') # Only call fetch_from_persistence if file exists next unless File.exist?(cached_file) + saved_cache = Billy.proxy.cache.fetch_from_persistence(cached_key) expect(saved_cache.keys).to include :request_headers end @@ -231,14 +235,15 @@ end context 'ignore_cache_port requests', skip: 'Depends on lib-specific cacheable? logic' do - it 'should be cached without port' do + it 'is cached without port' do r = http.get('/foo') # Only call fetch_from_persistence if file exists next unless File.exist?(cached_file) + url = URI(r.env[:url]) saved_cache = Billy.proxy.cache.fetch_from_persistence(cached_key) - expect(saved_cache[:url]).to_not eql(url.to_s) + expect(saved_cache[:url]).not_to eql(url.to_s) expect(saved_cache[:url]).to eql(url.to_s.gsub(":#{url.port}", '')) end end @@ -246,9 +251,9 @@ context 'non_whitelisted_requests_disabled requests' do before { Billy.config.non_whitelisted_requests_disabled = true } - it 'should raise error when disabled' do + it 'raises error when disabled' do # TODO: Suppress stderr output: https://gist.github.com/adamstegman/926858 - expect { http.get('/foo') }.to raise_error(Faraday::ConnectionFailed, 'end of file reached') + expect { http.get('/foo') }.to raise_error(Faraday::Error) end end @@ -259,12 +264,12 @@ Billy.config.non_successful_cache_disabled = true end - it 'should not cache non-successful response when enabled' do + it 'does not cache non-successful response when enabled' do http_error.get('/foo') expect(File.exist?(cached_file)).to be false end - it 'should cache successful response when enabled', skip: 'Depends on lib-specific cacheable? logic' do + it 'caches successful response when enabled', skip: 'Depends on lib-specific cacheable? logic' do assert_cached_url end end @@ -276,7 +281,7 @@ Billy.config.non_successful_error_level = :error end - it 'should raise error for non-successful responses when :error' do + it 'raises error for non-successful responses when :error' do expect { http_error.get('/foo') }.to raise_error(Faraday::ConnectionFailed) end end @@ -302,7 +307,7 @@ def assert_noncached_url(url = '/foo') expect do r = http.get(url) end.to change { r.headers['HTTP-X-EchoCount'].to_i }.by(1) - end.to_not change { r.body } + end.not_to(change { r.body }) end def assert_cached_url(url = '/foo') @@ -315,8 +320,8 @@ def assert_cached_url(url = '/foo') expect do expect do r = http.get(url) - end.to_not change { r.headers['HTTP-X-EchoCount'] } - end.to_not change { r.body } + end.not_to(change { r.headers['HTTP-X-EchoCount'] }) + end.not_to(change { r.body }) end end @@ -329,9 +334,9 @@ def assert_cached_url(url = '/foo') request: { timeout: 1.0 } } faraday_ssl_options = faraday_options.merge(ssl: { - verify: true, - ca_file: Billy.certificate_authority.cert_file - }) + verify: true, + ca_file: Billy.certificate_authority.cert_file + }) @http = Faraday.new @http_url, faraday_options @https = Faraday.new @https_url, faraday_ssl_options @@ -341,12 +346,14 @@ def assert_cached_url(url = '/foo') context 'proxying' do context 'HTTP' do let!(:http) { @http } - it_should_behave_like 'a proxy server' + + it_behaves_like 'a proxy server' end context 'HTTPS' do let!(:http) { @https } - it_should_behave_like 'a proxy server' + + it_behaves_like 'a proxy server' end end @@ -354,33 +361,37 @@ def assert_cached_url(url = '/foo') context 'HTTP' do let!(:url) { @http_url } let!(:http) { @http } - it_should_behave_like 'a request stub' + + it_behaves_like 'a request stub' end context 'HTTPS' do let!(:url) { @https_url } let!(:http) { @https } - it_should_behave_like 'a request stub' + + it_behaves_like 'a request stub' end end context 'caching' do it 'defaults to nil scope' do - expect(proxy.cache.scope).to be nil + expect(proxy.cache.scope).to be_nil end context 'HTTP' do let!(:url) { @http_url } let!(:http) { @http } let!(:http_error) { @http_error } - it_should_behave_like 'a cache' + + it_behaves_like 'a cache' end context 'HTTPS' do let!(:url) { @https_url } let!(:http) { @https } let!(:http_error) { @http_error } - it_should_behave_like 'a cache' + + it_behaves_like 'a cache' end context 'with a cache scope' do @@ -396,7 +407,7 @@ def assert_cached_url(url = '/foo') proxy.cache.use_default_scope end - it_should_behave_like 'a cache' + it_behaves_like 'a cache' it 'uses the cache scope' do expect(proxy.cache.scope).to eq('my_cache') @@ -404,7 +415,7 @@ def assert_cached_url(url = '/foo') it 'can be reset to the default scope' do proxy.cache.use_default_scope - expect(proxy.cache.scope).to be nil + expect(proxy.cache.scope).to be_nil end it 'can execute a block against a cache scope' do @@ -419,14 +430,14 @@ def assert_cached_url(url = '/foo') expect { proxy.cache.with_scope 'some_scope' }.to raise_error ArgumentError end - it 'should have different keys for the same request under a different scope' do - # Note: The cache.key method uses the cache_scope parameter (4th arg), not the instance @scope + it 'has different keys for the same request under a different scope' do + # NOTE: The cache.key method uses the cache_scope parameter (4th arg), not the instance @scope # So we test by passing different cache_scope values args_scope_0 = ['get', "#{url}/foo", '', 0] args_scope_1 = ['get', "#{url}/foo", '', 1] key_scope_0 = proxy.cache.key(*args_scope_0) key_scope_1 = proxy.cache.key(*args_scope_1) - expect(key_scope_0).to_not eq key_scope_1 + expect(key_scope_0).not_to eq key_scope_1 end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c0e6d4e3..2a2109e1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,6 @@ -Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f } +# frozen_string_literal: true + +Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require f } require 'pry' require 'billy/capybara/rspec' @@ -7,26 +9,19 @@ require 'logger' require 'fileutils' -# Patch RequestLog#complete to accept optional cache_key (lib bug: line 31 of request_handler.rb passes only 2 args) -module Billy - class RequestLog - def complete(request, handler, cache_key = nil) - return unless Billy.config.record_requests - - request.merge! status: :complete, - handler: handler, - cache_key: cache_key - end - end -end - -browser = Billy::Browsers::Watir.new :phantomjs -Capybara.app = Rack::Directory.new(File.expand_path('../../examples', __FILE__)) -Capybara.server = :webrick -Capybara.javascript_driver = :poltergeist_billy +chrome_options = Selenium::WebDriver::Chrome::Options.new +chrome_options.add_argument('--headless=new') +chrome_options.add_argument('--no-sandbox') +chrome_options.add_argument('--disable-dev-shm-usage') +chrome_options.add_argument('--disable-gpu') +chrome_options.binary = ENV['CHROME_BIN'] if ENV['CHROME_BIN'] +browser = Billy::Browsers::Watir.new :chrome, options: chrome_options +Capybara.app = Rack::Directory.new(File.expand_path('../examples', __dir__)) +Capybara.server = :puma, { Silent: true } +Capybara.javascript_driver = :selenium_chrome_headless_billy Billy.configure do |config| - config.logger = Logger.new(File.expand_path('../../log/test.log', __FILE__)) + config.logger = Logger.new(File.expand_path('../log/test.log', __dir__)) end RSpec.configure do |config| @@ -45,11 +40,11 @@ def complete(request, handler, cache_key = nil) @browser = browser end - config.before :each do + config.before do proxy.reset_cache end - config.after :each do + config.after do Billy.config.reset end diff --git a/spec/support/test_server.rb b/spec/support/test_server.rb index 323fe5f8..f5a04946 100644 --- a/spec/support/test_server.rb +++ b/spec/support/test_server.rb @@ -1,12 +1,16 @@ +# frozen_string_literal: true + require 'eventmachine' require 'thin' require 'faraday' -module Thin::Backends - class TcpServer - def get_port - # seriously, eventmachine, how hard does getting a port have to be? - Socket.unpack_sockaddr_in(EM.get_sockname(@signature)).first +module Thin + module Backends + class TcpServer + def get_port + # seriously, eventmachine, how hard does getting a port have to be? + Socket.unpack_sockaddr_in(EM.get_sockname(@signature)).first + end end end end @@ -42,7 +46,7 @@ def start_test_servers def echo_app_setup(response_code = 200) counter = 0 - Proc.new do |env| + proc do |env| req_body = env['rack.input'].read request_info = "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}" res_body = request_info @@ -51,7 +55,7 @@ def echo_app_setup(response_code = 200) [ response_code, { 'HTTP-X-EchoServer' => request_info, - 'HTTP-X-EchoCount' => "#{counter}" }, + 'HTTP-X-EchoCount' => counter.to_s }, [res_body] ] end @@ -63,7 +67,6 @@ def certificate_chain(domain) chain = Billy::CertificateChain.new(domain, cert.cert, ca) { private_key_file: cert.key_file, cert_chain_file: chain.file } - end def start_server(echo, ssl = false)