rspec-httpd 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce5a7956af7f5d0fa605929340963f4b212b1eca01a638bb52829fbe190716b5
4
- data.tar.gz: 426cf96fa555de1949629f65cb1f619f8be7b4980fde0e47e2ed9b5d1432cc7a
3
+ metadata.gz: e88c3329bebe29bcae34340feeb0585974fdc35b5a8b35f0451d7208e51f9c4f
4
+ data.tar.gz: a56d6764673da02062227b36b141ba8bdc163cb864489688f988a0f47a63f036
5
5
  SHA512:
6
- metadata.gz: d2c05620102049c75517f09cdc042e068503600d4633b87228adb2be70bd75a85cc0372876dd3bee2404b301f6d3c61adb2358daea265eff6ea3691355abe837
7
- data.tar.gz: ad091863872c48f7f356afa9422a83fc8d25bdd8fa80f6bd53682228f962940e9d64f72426af4c751b987517c63d364f106c46e5a45bd645a3013dfcc2c9dec6
6
+ metadata.gz: 9e1ac270f0c7e49cb2a923964bdc3ebbb7b0f91e64442768dff60a04063051ac4cce35d68cd3ab8860b0c4103c55c79c5526e3d0a834e987e32a405c3a212d13
7
+ data.tar.gz: 13a98bb4462fcdebb3cc33f40975e15a658a73f1c9e70e299dfa8c9a21b8ef407d316b3b24fca8cf5909c79d486d8e1040dc3a21ec454383cd690a9ac3e6c9a8
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
@@ -0,0 +1,78 @@
1
+ require "rspec/core"
2
+ require "forwardable"
3
+ require "logger"
4
+
5
+ module RSpec::Httpd
6
+ end
7
+
8
+ require_relative "httpd/version"
9
+ require_relative "httpd/server"
10
+ require_relative "httpd/client"
11
+ require_relative "httpd/config"
12
+ require_relative "httpd/expectation"
13
+
14
+ module RSpec::Httpd
15
+ extend self
16
+
17
+ attr_reader :config
18
+
19
+ # Set the configuration for the default client.
20
+ #
21
+ # See also: RSpec::Httpd.http
22
+ def configure(&block)
23
+ @config = Config.new.tap(&block)
24
+ end
25
+
26
+ def logger #:nodoc:
27
+ @logger ||= Logger.new(STDERR).tap { |logger| logger.level = Logger::INFO }
28
+ end
29
+
30
+ # builds and returns a client.
31
+ #
32
+ # You can use this method to retrieve a client connection to a server
33
+ # specified via host:, port:, and, optionally, a command.
34
+ def client(host:, port:, command: nil)
35
+ @clients ||= {}
36
+ @clients[[host, port, command]] ||= begin
37
+ Server.start!(host: host, port: port, command: command) if command
38
+ Client.new host: host, port: port
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # returns the default client
45
+ #
46
+ # The default client is the one configured via RSpec::Httpd.configure.
47
+ def http
48
+ config = ::RSpec::Httpd.config ||
49
+ raise("RSpec::Httpd configuration missing; run RSpec::Httpd.configure { |config| ... }")
50
+
51
+ client(host: config.host, port: config.port, command: config.command)
52
+ end
53
+
54
+ public
55
+
56
+ def expect_response(expected = nil, status: nil, client: nil)
57
+ client ||= http
58
+
59
+ # only check status? This lets us write
60
+ #
61
+ # expect_response 201
62
+ #
63
+ if expected.is_a?(Integer) && status.nil?
64
+ expect(client.status).to eq(expected)
65
+ return
66
+ end
67
+
68
+ # do_expect_last_request is implemented in RSpec::Httpd::Expectation, and mixed in
69
+ # here, because it needs access to the expect() implementation.
70
+
71
+ expect(client.status).to eq(status || 200)
72
+ unless expected.nil?
73
+ do_expect_last_request(expected: expected, client: client)
74
+ end
75
+ end
76
+
77
+ include RSpec::Httpd::Expectation
78
+ end
@@ -0,0 +1,122 @@
1
+ require "net/http"
2
+ require "json"
3
+
4
+ module RSpec::Httpd
5
+ class Client
6
+ # host and port. Set at initialization
7
+ attr_reader :host
8
+ attr_reader :port
9
+
10
+ def initialize(host:, port:)
11
+ @host, @port = host, port
12
+ end
13
+
14
+ # request, response, and status, set during a request/response cycle
15
+ attr_reader :request
16
+ attr_reader :response
17
+
18
+ def status
19
+ Integer(response.code)
20
+ end
21
+
22
+ # returns the headers of the latest response
23
+ def headers
24
+ @headers ||= HeadersHash.new(response)
25
+ end
26
+
27
+ # returns the parsed response of the latest request
28
+ def result
29
+ @result ||= ResponseParser.parse(response)
30
+ end
31
+
32
+ # A GET request
33
+ def get(url, headers: {})
34
+ run_request(::Net::HTTP::Get, url, body: nil, headers: headers)
35
+ end
36
+
37
+ # A POST request
38
+ def post(url, body, headers: {})
39
+ run_request(::Net::HTTP::Post, url, body: body, headers: headers)
40
+ end
41
+
42
+ # A PUT request
43
+ def put(url, body, headers: {})
44
+ run_request(::Net::HTTP::Put, url, body: body || {}, headers: headers)
45
+ end
46
+
47
+ # A DELETE request
48
+ def delete(url, headers: {})
49
+ run_request(::Net::HTTP::Delete, url, body: nil, headers: headers)
50
+ end
51
+
52
+ private
53
+
54
+ def run_request(request_klass, url, body:, headers:)
55
+ @result = nil
56
+
57
+ build_request!(request_klass, url, body: body, headers: headers)
58
+ log_request!
59
+ @response = Net::HTTP.start(host, port) { |http| http.request(request) }
60
+ end
61
+
62
+ def build_request!(request_klass, url, body:, headers:)
63
+ @request = request_klass.new url, headers
64
+ if body
65
+ @request["Content-Type"] = "application/json"
66
+ @request.body = JSON.generate body
67
+ end
68
+ end
69
+
70
+ def log_request!
71
+ if request.body
72
+ RSpec::Httpd.logger.info "#{request.method} #{request.uri} #{body.inspect[0..100]}"
73
+ else
74
+ RSpec::Httpd.logger.info "#{request.method} #{request.uri}"
75
+ end
76
+ end
77
+
78
+ class HeadersHash < Hash
79
+ def initialize(response)
80
+ response.each_header do |k, v|
81
+ case self[k]
82
+ when Array then self[k].concat v
83
+ when nil then self[k] = v
84
+ else self[k].concat(v)
85
+ end
86
+ end
87
+ end
88
+
89
+ def [](key)
90
+ super key.downcase
91
+ end
92
+
93
+ private
94
+
95
+ def []=(key, value)
96
+ super key.downcase, value
97
+ end
98
+ end
99
+
100
+ module ResponseParser
101
+ def self.parse(response)
102
+ content_type = response["content-type"]
103
+
104
+ result = if content_type&.include?("application/json")
105
+ JSON.parse(response.body)
106
+ else
107
+ response.body.force_encoding("utf-8").encode
108
+ end
109
+
110
+ result.extend(self)
111
+ result.__response__ = response
112
+ result
113
+ end
114
+
115
+ attr_accessor :__response__
116
+
117
+ def status
118
+ Integer(__response__.code)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,11 @@
1
+ class RSpec::Httpd::Config
2
+ attr_accessor :host
3
+ attr_accessor :port
4
+ attr_accessor :command
5
+
6
+ def initialize
7
+ self.host = "127.0.0.1"
8
+ self.port = 12_345
9
+ self.command = "bundle exec rackup -E test -p 12345"
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ require "rspec/core"
2
+ require "forwardable"
3
+ require "logger"
4
+
5
+ module RSpec::Httpd::Expectation
6
+ def do_expect_last_request(expected:, client:)
7
+ actual = client.result
8
+
9
+ ctx = {
10
+ request: client.request, actual: actual, expected: expected
11
+ }
12
+
13
+ case expected
14
+ when Regexp then expect(actual).to match(expected), -> { format_failure SHOULD_MATCH_ERROR, ctx }
15
+ when Hash then expect(actual).to include(expected), -> { format_failure SHOULD_INCLUDE_ERROR, ctx }
16
+ else expect(actual).to eq(expected), -> { format_failure SHOULD_EQUAL_ERROR, ctx }
17
+ end
18
+ end
19
+
20
+ SHOULD_MATCH_ERROR = "%request -- %actual should match %expected".freeze
21
+ SHOULD_INCLUDE_ERROR = "%request -- %actual should include %expected".freeze
22
+ SHOULD_EQUAL_ERROR = "%request -- %actual should equal %expected".freeze
23
+
24
+ private
25
+
26
+ def format_failure(format, ctx)
27
+ format.gsub(/\%(\S+)/) do
28
+ value = ctx.fetch(Regexp.last_match(1).to_sym)
29
+ case value
30
+ when Net::HTTPGenericRequest
31
+ request = value
32
+ s = "#{request.method} #{request.path}"
33
+ s += " body:#{request.body.inspect}" if request.body
34
+ s
35
+ else
36
+ value.inspect
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ # rubocop:disable Lint/HandleExceptions
2
+
3
+ require "socket"
4
+ require "timeout"
5
+
6
+ module RSpec::Httpd
7
+ module Server
8
+ MAX_STARTUP_TIME = 10
9
+
10
+ extend self
11
+
12
+ # builds and returns a server object.
13
+ #
14
+ # You can use this method to retrieve a client connection to a server
15
+ # specified via host:, port:, and, optionally, a command.
16
+ def start!(host:, port:, command:)
17
+ @servers ||= {}
18
+ @servers[[host, port, command]] ||= do_start(host, port, command)
19
+ end
20
+
21
+ private
22
+
23
+ def do_start(host, port, command)
24
+ logger = RSpec::Httpd.logger
25
+ logger.debug "Starting server: #{command}"
26
+
27
+ pid = spawn(command)
28
+
29
+ at_exit do
30
+ begin
31
+ logger.debug "Stopping server at pid #{pid}: #{command}"
32
+ Process.kill("KILL", pid)
33
+ sleep 0.2
34
+ rescue Errno::ESRCH
35
+ end
36
+
37
+ die "Cannot stop server at pid #{pid}: #{command}" if port_open?(host, port)
38
+ end
39
+
40
+ unless wait_for_server(host: host, port: port, pid: pid, timeout: MAX_STARTUP_TIME)
41
+ logger.error "server didn't start at http://#{host}:#{port} pid #{pid}: #{command}"
42
+ exit 1
43
+ end
44
+
45
+ logger.info "Started server at pid #{pid}: #{command}"
46
+ pid
47
+ end
48
+
49
+ def wait_for_server(host:, port:, pid:, timeout:)
50
+ while timeout > 0
51
+ sleep 0.1
52
+ return true if port_open?(host, port)
53
+ return false if Process.waitpid(pid, Process::WNOHANG)
54
+
55
+ timeout -= 0.1
56
+ next if timeout > 0
57
+
58
+ return false
59
+ end
60
+ end
61
+
62
+ def port_open?(host, port)
63
+ Timeout.timeout(0.01) do
64
+ s = TCPSocket.new(host, port)
65
+ s.close
66
+ return true
67
+ end
68
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error
69
+ false
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ module RSpec::Httpd
2
+ module GemHelper
3
+ extend self
4
+
5
+ def version(name)
6
+ spec = Gem.loaded_specs[name]
7
+ return "unreleased" unless spec
8
+
9
+ version = spec.version.to_s
10
+ version += "+unreleased" if unreleased?(spec)
11
+ version
12
+ end
13
+
14
+ private
15
+
16
+ def unreleased?(spec)
17
+ return false unless defined?(Bundler::Source::Gemspec)
18
+ return true if spec.source.is_a?(::Bundler::Source::Gemspec)
19
+ return true if spec.source.is_a?(::Bundler::Source::Path)
20
+
21
+ false
22
+ end
23
+ end
24
+
25
+ VERSION = GemHelper.version "rspec-httpd"
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-httpd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Enrico Thierbach
@@ -58,16 +58,15 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
- - Gemfile
62
61
  - README.md
63
- - Rakefile
64
62
  - VERSION
65
- - bin/console
66
- - bin/rubocop
67
63
  - lib/rspec-httpd.rb
68
- - rspec-httpd.gemspec
69
- - scripts/release
70
- - scripts/release.rb
64
+ - lib/rspec/httpd.rb
65
+ - lib/rspec/httpd/client.rb
66
+ - lib/rspec/httpd/config.rb
67
+ - lib/rspec/httpd/expectation.rb
68
+ - lib/rspec/httpd/server.rb
69
+ - lib/rspec/httpd/version.rb
71
70
  homepage: https://github.com/radiospiel/rspec-httpd
72
71
  licenses:
73
72
  - Nonstandard
data/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gemspec
data/Rakefile DELETED
@@ -1,18 +0,0 @@
1
- require "rubocop/rake_task"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
- RuboCop::RakeTask.new(:rubocop)
6
-
7
- desc "Run rspec and then rubocop"
8
- task default: [:spec, :rubocop]
9
-
10
- desc "release a new development gem version"
11
- task :release do
12
- sh "scripts/release.rb"
13
- end
14
-
15
- desc "release a new stable gem version"
16
- task "release:stable" do
17
- sh "BRANCH=stable scripts/release.rb"
18
- end
data/bin/console DELETED
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env ruby
2
- root=File.expand_path "#{File.dirname(__FILE__)}/.."
3
- $: << "#{root}/lib"
4
-
5
- require "bundler"
6
- Bundler.require
7
-
8
- require "irb"
9
- require "rspec-httpd"
10
-
11
-
12
- IRB.start
13
-
data/bin/rubocop DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'rubocop' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
14
-
15
- bundle_binstub = File.expand_path("../bundle", __FILE__)
16
-
17
- if File.file?(bundle_binstub)
18
- if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
- load(bundle_binstub)
20
- else
21
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
- end
24
- end
25
-
26
- require "rubygems"
27
- require "bundler/setup"
28
-
29
- load Gem.bin_path("rubocop", "rubocop")
data/rspec-httpd.gemspec DELETED
@@ -1,23 +0,0 @@
1
- Gem::Specification.new do |s|
2
- s.name = "rspec-httpd"
3
- s.version = File.read("VERSION").chomp
4
- s.date = "2019-03-01"
5
- s.summary = "RSpec testing for HTTP requests"
6
- s.description = "RSpec testing for HTTP requests"
7
- s.authors = ["Enrico Thierbach"]
8
- s.email = "eno@open-lab.org"
9
- s.homepage = "https://github.com/radiospiel/rspec-httpd"
10
- s.license = "Nonstandard"
11
- s.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR).reject { |f| f.match(%r{tasks/|spec/|log/|^\.}) }
12
-
13
- # Runtime dependencies of this gem:
14
- # s.add_dependency 'activesupport', '~> 4.2.10'
15
- # s.add_dependency 'expectation', '1.1.1'
16
- # s.add_dependency 'faraday', '> 0.9'
17
- # s.add_dependency 'uri_template', '~> 0.7', '>= 0.7.0'
18
-
19
- # Gems for dev and test env:
20
- s.add_development_dependency "rake", "~> 12.0"
21
- s.add_development_dependency "rspec", "~> 3.4"
22
- s.add_development_dependency "rubocop", "~> 0.65.0"
23
- end
data/scripts/release DELETED
@@ -1,2 +0,0 @@
1
- #!/bin/bash
2
- $0.rb "$@"
data/scripts/release.rb DELETED
@@ -1,92 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # -- helpers ------------------------------------------------------------------
4
-
5
- def sys(cmd)
6
- STDERR.puts "> #{cmd}"
7
- system cmd
8
- return true if $?.success?
9
-
10
- STDERR.puts "> #{cmd} returned with exitstatus #{$?.exitstatus}"
11
- $?.success?
12
- end
13
-
14
- def sys!(cmd, error: nil)
15
- return true if sys(cmd)
16
- STDERR.puts error if error
17
- exit 1
18
- end
19
-
20
- def die!(msg)
21
- STDERR.puts msg
22
- exit 1
23
- end
24
-
25
- ROOT = File.expand_path("#{File.dirname(__FILE__)}/..")
26
-
27
- GEMSPEC = Dir.glob("*.gemspec").first || die!("Missing gemspec file.")
28
-
29
- # -- Version reading and bumping ----------------------------------------------
30
-
31
- module Version
32
- extend self
33
-
34
- VERSION_FILE = "#{Dir.getwd}/VERSION"
35
-
36
- def read_version
37
- version = File.exist?(VERSION_FILE) ? File.read(VERSION_FILE) : "0.0.1"
38
- version.chomp!
39
- raise "Invalid version number in #{VERSION_FILE}" unless version =~ /^\d+\.\d+\.\d+$/
40
- version
41
- end
42
-
43
- def auto_version_bump
44
- old_version_number = read_version
45
- old = old_version_number.split('.')
46
-
47
- current = old[0..-2] << old[-1].next
48
- current.join('.')
49
- end
50
-
51
- def bump_version
52
- next_version = ENV["VERSION"] || auto_version_bump
53
- File.open(VERSION_FILE, "w") { |io| io.write next_version }
54
- end
55
- end
56
-
57
- # -- check, bump, release a new gem version -----------------------------------
58
-
59
- Dir.chdir ROOT
60
- $BASE_BRANCH = ENV['BRANCH'] || 'master'
61
-
62
- # ENV["BUNDLE_GEMFILE"] = "#{Dir.getwd}/Gemfile"
63
- # sys! "bundle install"
64
-
65
- sys! "git diff --exit-code > /dev/null", error: 'There are unstaged changes in your working directory'
66
- sys! "git diff --cached --exit-code > /dev/null", error: 'There are staged but uncommitted changes'
67
-
68
- sys! "git checkout #{$BASE_BRANCH}"
69
- sys! "git pull"
70
-
71
- Version.bump_version
72
- version = Version.read_version
73
-
74
- sys! "git add VERSION"
75
- sys! "git commit -m \"bump gem to v#{version}\""
76
- sys! "git tag -a v#{version} -m \"Tag #{version}\""
77
-
78
- sys! "gem build #{GEMSPEC}"
79
-
80
- sys! "git push origin #{$BASE_BRANCH}"
81
- sys! 'git push --tags --force'
82
-
83
- sys! "gem push #{Dir.glob('*.gem').first}"
84
-
85
- sys! "mkdir -p pkg"
86
- sys! "mv *.gem pkg"
87
-
88
- STDERR.puts <<-MSG
89
- ================================================================================
90
- Thank you for releasing a new gem version. You made my day.
91
- ================================================================================
92
- MSG