cucumber-rest 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZGZhNzhkMDJlMzgyZTFjMTNlNjUzM2VkODM2NjhiYjUxODc4ZWIxZA==
5
+ data.tar.gz: !binary |-
6
+ MmM1ZGIwMTQ2ZDlkNGQ3OWIwYTJjNzQxNWJiYzc3OWZiZTA2ODUwNg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTVlYjc2ZTFkN2ZmNGUwYmFjMDVlZmYyMzUzM2FkODU2NTNiMjFhY2ZlYTE0
10
+ ZDdhYzhkMzlmOGI1YTNmNzc2YTZiMzkxOTlhOGM1NjUyY2E0NmIyNzBjMzRi
11
+ MzljZmRkNTFlNmM5ZjdhNTY1NTFlNTg3YjRmMmVhNGE1ZjRmZWU=
12
+ data.tar.gz: !binary |-
13
+ NWNlNDBlMjgyOTcxODRjNzVkZDg4NGMxMTYwZjU2M2IzMTRhYzdkZmM0NzBi
14
+ ZjM5OTc4M2EzM2U2NzljYjA2MTE4NGQ2ZTBiZDExMjQwY2I4MzY4MDdmOGU1
15
+ ZTI3NmU5MTYwOGE2ZWRlZmMxYzkyOTgyNTBjZGM3YWU3MTZkOTM=
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ log
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --no-color
2
+ --require spec_helper
3
+ --format progress -o log/spec.log
4
+ --format html -o log/spec.html
5
+ -cfd
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.2
5
+ deploy:
6
+ provider: rubygems
7
+ api_key:
8
+ secure: DfssT15CddiWJaakPGmBqroMixeyvSDtJsDa6dXW1qfO7gnoYQ1VGc0pevMs/1NsJBS/LDPGMN30Y+aTQ/JceiItp6O4LZaWN894C9+tXb3/9QKmTUFQkLqsb44lQeuRESPTtmtVKqMwbaLfiya6BActAfQvNdGY1JmBNAQsx8g=
9
+ gem: cucumber-rest
10
+ on:
11
+ repo: blinkboxbooks/cucumber-rest.rb
12
+ branch: master
data/CHANGELOG.md ADDED
@@ -0,0 +1,83 @@
1
+ # Change log
2
+
3
+ ## 0.1.10 ([#27](https://git.mobcastdev.com/TEST/cucumber-rest/pull/27) 2014-12-16 13:37:19)
4
+
5
+ Step def for asserting HTTP 501 responses
6
+
7
+ Patch
8
+
9
+ * Step def for asserting HTTP 501 responses
10
+
11
+ ## 0.1.9 ([#26](https://git.mobcastdev.com/TEST/cucumber-rest/pull/26) 2014-12-16 11:09:35)
12
+
13
+ Add a step for checking we specifically return a HTTP 202
14
+
15
+ Patch
16
+
17
+ * Adds a step def for checking HTTP 202 responses
18
+
19
+ ## 0.1.8 ([#25](https://git.mobcastdev.com/TEST/cucumber-rest/pull/25) 2014-12-10 16:31:08)
20
+
21
+ Http 204 status codes
22
+
23
+ Patch
24
+
25
+ Add support for handling HTTP 204 responses, checking that the body is empty.
26
+
27
+ Also whitespace changes because hard tabs are bad, mmkay?
28
+
29
+ ## 0.1.7 ([#24](https://git.mobcastdev.com/TEST/cucumber-rest/pull/24) 2014-12-08 16:47:02)
30
+
31
+ Using `ensure_status` instead of `ensure_status_class`
32
+
33
+ A patch to fix checking the status with the correct method.
34
+
35
+ ## 0.1.6 ([#23](https://git.mobcastdev.com/TEST/cucumber-rest/pull/23) 2014-12-08 16:18:09)
36
+
37
+ Made the item specified that is being create non capturing
38
+
39
+ A patch to make the user specified resource being created in the step's regex non-capturing.
40
+
41
+ ## 0.1.5 ([#22](https://git.mobcastdev.com/TEST/cucumber-rest/pull/22) 2014-12-01 14:58:07)
42
+
43
+ adding step for checking 201 Created response
44
+
45
+ patch: adding step for checking 201 Created response
46
+
47
+ ## 0.1.4 ([#21](https://git.mobcastdev.com/TEST/cucumber-rest/pull/21) 2014-10-14 16:38:44)
48
+
49
+ RSpec 3
50
+
51
+ ### Improvements
52
+
53
+ - Move to RSpec 3
54
+
55
+ ## 0.1.3 ([#20](https://git.mobcastdev.com/TEST/cucumber-rest/pull/20) 2014-10-13 10:21:36)
56
+
57
+ Fix where the version file is read from
58
+
59
+ Patch
60
+
61
+ VERSION file location was misdefined as one level too deep. If the VERSION file can't be found in the right folder, default to a more sane "0.0.0".
62
+
63
+ ## 0.1.2 ([#19](https://git.mobcastdev.com/TEST/cucumber-rest/pull/19) 2014-10-09 17:28:41)
64
+
65
+ 410 Gone step
66
+
67
+ Patch
68
+
69
+ ## 0.1.1 ([#18](https://git.mobcastdev.com/TEST/cucumber-rest/pull/18) 2014-07-15 10:52:31)
70
+
71
+ CP-1598 - Make Cucumber-Rest stricter about what it considers a valid Date in HTTP headers
72
+
73
+ Patch to make Cucumber-Rest fall back to using DateTime.httpdate as opposed to generic Date parsing; such that it's more in keeping with the HTTP RFCs
74
+
75
+ ## 0.1.0 ([#17](https://git.mobcastdev.com/TEST/cucumber-rest/pull/17) 2014-06-30 17:42:16)
76
+
77
+ Moved to artifactory
78
+
79
+ ### New Features
80
+
81
+ - Moved to Artifactory
82
+ - Moved to using VERSION file.
83
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org/"
2
+
3
+ gemspec
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 blinkbox Books Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Cucumber::Rest
2
+
3
+ A set of cucumber step definitions and helpers for testing RESTful APIs
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :build => :test
8
+ task :test => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.10
@@ -0,0 +1,33 @@
1
+ ($LOAD_PATH << File.expand_path("../lib", __FILE__)).uniq!
2
+ require "cucumber/rest/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "cucumber-rest"
6
+ s.version = Cucumber::Rest::VERSION
7
+ s.summary = "Cucumber steps and support for testing RESTful services."
8
+ s.description = "A set of Cucumber step definitions and support functions which encapsulate common RESTful functionality."
9
+ s.author = "blinkbox books"
10
+ s.email = "greg@blinkbox.com"
11
+ s.homepage = "http://www.blinkboxbooks.com"
12
+ s.license = "MIT"
13
+
14
+ s.files = `git ls-files`.split($/)
15
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
+ s.require_paths = ["lib"]
18
+ s.extra_rdoc_files = ["README.md"]
19
+
20
+ s.post_install_message = ":: Coded for blinkbox books :: Love books, love code? Get in touch ::"
21
+
22
+ s.add_runtime_dependency "activesupport", ">= 3.2"
23
+ s.add_runtime_dependency "cucumber", "~> 1.3"
24
+ s.add_runtime_dependency "multi_json", "~> 1.7"
25
+ s.add_runtime_dependency "rspec", "~> 3.0"
26
+ s.add_runtime_dependency "rack", "~> 1.5"
27
+ s.add_runtime_dependency "http_capture", "~> 0.0", ">= 0.0.4"
28
+
29
+ s.add_development_dependency "bundler", "~> 1.3"
30
+ s.add_development_dependency "rake", "~> 10.1"
31
+
32
+ s.add_development_dependency "cucumber_spinner"
33
+ end
@@ -0,0 +1,12 @@
1
+ require 'http_capture'
2
+
3
+ module Cucumber
4
+ module Rest
5
+ # Helper functions for the handling of response bodies
6
+ module Body
7
+ def self.ensure_empty(response: HttpCapture::RESPONSES.last)
8
+ raise "Request body was not empty:\n #{response.body}" if response.body.to_s.size != 0
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,152 @@
1
+ require "date"
2
+ require "http_capture"
3
+
4
+ module Cucumber
5
+ module Rest
6
+ # Helper functions for checking the cacheability of responses.
7
+ module Caching
8
+
9
+ class EmptyHTTPDateError < RuntimeError; end
10
+
11
+ # Ensures that a response is privately cacheable.
12
+ #
13
+ # This function uses a strict interpretation of RFC 2616 to ensure the widest interoperability with
14
+ # implementations, including HTTP 1.0.
15
+ #
16
+ # @param response [HttpCapture::Response] The response to check. If not supplied defaults to the last response.
17
+ # @param min_duration [Integer] The minimum permitted cache duration, in seconds.
18
+ # @param max_duration [Integer] The maximum permitted cache duration, in seconds.
19
+ # @param duration [Integer] The required cache duration, in seconds. Convenient if min and max are the same.
20
+ # @return [nil]
21
+ def self.ensure_response_is_publicly_cacheable(args = {})
22
+ response, min_duration, max_duration = extract_args(args)
23
+ ensure_cache_headers(response, false)
24
+
25
+ cache_control = parse_cache_control(response["Cache-Control"])
26
+ ensure_cache_directives(cache_control, "public", "max-age")
27
+ prohibit_cache_directives(cache_control, "private", "no-cache", "no-store")
28
+
29
+ age = response["Age"].to_i
30
+ date = parse_httpdate(response["Date"])
31
+ expires = parse_expires_httpdate(response["Expires"])
32
+ max_age = cache_control["max-age"]
33
+ expected_max_age = age + ((expires - date) * 24 * 3600).to_i
34
+ unless (max_age - expected_max_age).abs <= 1 # 1 second leeway
35
+ raise "Age, Date, Expires and Cache-Control:max-age are inconsistent"
36
+ end
37
+
38
+ ensure_cache_duration(max_age, min_duration, max_duration)
39
+ end
40
+
41
+ # Ensures that a response is privately cacheable.
42
+ #
43
+ # This function uses a strict interpretation of RFC 2616, including precedence rules for Date, Expires and
44
+ # Cache-Control:max-age to ensure the widest interoperability with implementations, including HTTP 1.0.
45
+ #
46
+ # @param response [HttpCapture::Response] The response to check. If not supplied defaults to the last response.
47
+ # @param min_duration [Integer] The minimum permitted cache duration, in seconds.
48
+ # @param max_duration [Integer] The maximum permitted cache duration, in seconds.
49
+ # @param duration [Integer] The required cache duration, in seconds. Convenient if min and max are the same.
50
+ # @return [nil]
51
+ def self.ensure_response_is_privately_cacheable(args = {})
52
+ response, min_duration, max_duration = extract_args(args)
53
+ ensure_cache_headers(response, false)
54
+
55
+ cache_control = parse_cache_control(response["Cache-Control"])
56
+ ensure_cache_directives(cache_control, "private", "max-age")
57
+ prohibit_cache_directives(cache_control, "public", "no-cache", "no-store")
58
+
59
+ date = parse_httpdate(response["Date"])
60
+ expires = parse_expires_httpdate(response["Expires"])
61
+ raise "Expires should not be later than Date" if expires && expires > date
62
+
63
+ ensure_cache_duration(cache_control["max-age"], min_duration, max_duration)
64
+ end
65
+
66
+ # Ensures that a response is not cacheable.
67
+ #
68
+ # This function uses a strict interpretation of RFC 2616, to ensure the widest interoperability with
69
+ # implementations, including HTTP 1.0.
70
+ #
71
+ # @param response [HttpCapture::Response] The response to check. If not supplied defaults to the last response.
72
+ # @return [nil]
73
+ def self.ensure_response_is_not_cacheable(args = {})
74
+ response, * = extract_args(args)
75
+ ensure_cache_headers(response, true)
76
+
77
+ cache_control = parse_cache_control(response["Cache-Control"])
78
+ ensure_cache_directives(cache_control, "no-store")
79
+ prohibit_cache_directives(cache_control, "public", "private", "max-age") # TODO: prohibit no-cache?
80
+
81
+ date = parse_httpdate(response["Date"])
82
+ expires = parse_expires_httpdate(response["Expires"]) rescue nil # invalid values are treated as < now, which is fine
83
+ raise "Expires should not be later than Date" if expires && expires > date
84
+ end
85
+
86
+ private
87
+
88
+ def self.extract_args(args)
89
+ response = args[:response] || HttpCapture::RESPONSES.last
90
+ if response.nil?
91
+ raise "There is no response to check. Have you required the right capture file from HttpCapture?"
92
+ end
93
+
94
+ min_duration = args[:min_duration] || args[:duration]
95
+ max_duration = args[:max_duration] || args[:duration]
96
+
97
+ return response, min_duration, max_duration
98
+ end
99
+
100
+ def self.ensure_cache_headers(response, pragma_nocache)
101
+ ["Cache-Control", "Date", "Expires"].each { |h| raise "Required header '#{h}' is missing" if response[h].nil? }
102
+
103
+ unless (/\bno-cache\b/ === response["Pragma"]) == pragma_nocache
104
+ raise "Pragma should #{pragma_nocache ? "" : "not "}include the 'no-cache' directive"
105
+ end
106
+ end
107
+
108
+ def self.parse_cache_control(cache_control)
109
+ cache_control.split(",").each_with_object({}) do |entry, hash|
110
+ key, value = entry.split("=", 2).map(&:strip)
111
+ hash[key] = value =~ /^\d+$/ ? value.to_i : value
112
+ end
113
+ end
114
+
115
+ def self.ensure_cache_directives(cache_control, *directives)
116
+ directives.each do |directive|
117
+ raise "Cache-Control should include the '#{directive}' directive" unless cache_control.has_key?(directive)
118
+ end
119
+ end
120
+
121
+ def self.prohibit_cache_directives(cache_control, *directives)
122
+ directives.each do |directive|
123
+ raise "Cache-Control should not include the '#{directive}' directive" if cache_control.has_key?(directive)
124
+ end
125
+ end
126
+
127
+ def self.ensure_cache_duration(actual, min_expected, max_expected)
128
+ if min_expected && actual < min_expected
129
+ raise "Cache duration is #{actual}s but expected at least #{min_expected}s"
130
+ end
131
+ if max_expected && actual > max_expected
132
+ raise "Cache duration is #{actual}s but expected no more than #{max_expected}s"
133
+ end
134
+ end
135
+
136
+ def self.parse_httpdate(date)
137
+ raise EmptyHTTPDateError, "Empty date value" if (date.empty? || date.nil?)
138
+ DateTime.httpdate(date)
139
+ end
140
+
141
+ def self.parse_expires_httpdate(date)
142
+ begin
143
+ parse_httpdate(date)
144
+ rescue EmptyHTTPDateError, ArgumentError => e
145
+ warn "Invalid Expires header value, handling as a past value"
146
+ DateTime.httpdate()
147
+ end
148
+ end
149
+
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,37 @@
1
+ require "http_capture"
2
+ require "rack"
3
+
4
+ module Cucumber
5
+ module Rest
6
+ # Helper functions for checking the cacheability of responses.
7
+ module Status
8
+
9
+ def self.ensure_status(expected, response: HttpCapture::RESPONSES.last)
10
+ actual = response.status
11
+ unless expected == actual
12
+ actual_name = Rack::Utils::HTTP_STATUS_CODES[actual]
13
+ expected_name = Rack::Utils::HTTP_STATUS_CODES[expected]
14
+ message = "Request status was #{actual} #{actual_name}; expected #{expected} #{expected_name}"
15
+ raise message
16
+ end
17
+ end
18
+
19
+ def self.ensure_status_class(expected, response: HttpCapture::RESPONSES.last)
20
+ min = case expected
21
+ when :informational then 100
22
+ when :success then 200
23
+ when :redirection then 300
24
+ when :client_error then 400
25
+ when :server_error then 500
26
+ end
27
+ max = min + 99
28
+ expected_range = min..max
29
+ actual = response.status
30
+ unless expected_range === actual
31
+ message = "Request status was #{actual} #{Rack::Utils::HTTP_STATUS_CODES[actual]}; expected #{expected_range}"
32
+ raise message
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ require "active_support/core_ext/numeric/time"
2
+ require "cucumber/rest/caching"
3
+
4
+ Then(/^(?:the response|it)? is publicly cacheable$/) do
5
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable
6
+ end
7
+
8
+ Then(/^(?:the response|it)? is publicly cacheable for (a|\d+(?:\.\d+)?) (week|day|hour|minute|second)s?$/) do |num, unit|
9
+ num = num == "a" ? 1 : num.to_f
10
+ duration = num.send(unit.to_sym).to_i
11
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(duration: duration)
12
+ end
13
+
14
+ Then(/^(?:the response|it)? is privately cacheable$/) do
15
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable
16
+ end
17
+
18
+ Then(/^(?:the response|it)? is not cacheable$/) do
19
+ Cucumber::Rest::Caching.ensure_response_is_not_cacheable
20
+ end
@@ -0,0 +1,47 @@
1
+ require "cucumber/rest/status"
2
+ require "cucumber/rest/body"
3
+
4
+ Then(/^the request (?:is|was) successful$/) do
5
+ Cucumber::Rest::Status.ensure_status_class(:success)
6
+ end
7
+
8
+ Then(/^the request (?:is|was) successful and (?:a resource|.+) was created$/) do
9
+ Cucumber::Rest::Status.ensure_status(201)
10
+ end
11
+
12
+ Then(/^the request (?:is|was) successfully accepted$/) do
13
+ Cucumber::Rest::Status.ensure_status(202)
14
+ end
15
+
16
+ Then(/^the request (?:is|was) successful and (?:no|an empty) response body is returned$/) do
17
+ Cucumber::Rest::Status.ensure_status(204)
18
+ Cucumber::Rest::Body.ensure_empty
19
+ end
20
+
21
+ Then(/^(?:it|the request) fails because it (?:is|was) invalid$/) do
22
+ Cucumber::Rest::Status.ensure_status(400)
23
+ end
24
+
25
+ Then(/^(?:it|the request) fails because (?:.+) (?:is|was|am|are) unauthori[sz]ed$/) do
26
+ Cucumber::Rest::Status.ensure_status(401)
27
+ end
28
+
29
+ Then(/^(?:it|the request) fails because (?:.+) (?:is|was) forbidden$/) do
30
+ Cucumber::Rest::Status.ensure_status(403)
31
+ end
32
+
33
+ Then(/^(?:it|the request) fails because the (?:.+) (?:is|was) not found$/) do
34
+ Cucumber::Rest::Status.ensure_status(404)
35
+ end
36
+
37
+ Then(/^(?:it|the request) fails because there (?:is|was) a conflict(?: with .+)?$/) do
38
+ Cucumber::Rest::Status.ensure_status(409)
39
+ end
40
+
41
+ Then(/^(?:it|the request) fails because the (?:.+) (?:is|was|has) gone$/) do
42
+ Cucumber::Rest::Status.ensure_status(410)
43
+ end
44
+
45
+ Then(/^(?:it|the request) fails because the (?:.+) (?:is|was) not implemented$/) do
46
+ Cucumber::Rest::Status.ensure_status(501)
47
+ end
@@ -0,0 +1,2 @@
1
+ require "cucumber/rest/steps/caching"
2
+ require "cucumber/rest/steps/status"
@@ -0,0 +1,5 @@
1
+ module Cucumber
2
+ module Rest
3
+ VERSION = File.read(File.join(__dir__,"../../../VERSION")) rescue "0.0.0"
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module Cucumber
2
+ module Rest
3
+ end
4
+ end
5
+
6
+ require "cucumber/rest/steps"
@@ -0,0 +1,21 @@
1
+ require "cucumber/rest/body"
2
+
3
+ describe Cucumber::Rest::Body, :body do
4
+ context "#ensure_empty" do
5
+ def generate_response(body: nil)
6
+ response = MockResponse.new
7
+ response.body = body
8
+ response
9
+ end
10
+
11
+ it "does not raise an error when the response body is empty" do
12
+ response = generate_response(body: nil)
13
+ expect { Cucumber::Rest::Body.ensure_empty(response: response) }.to_not raise_error
14
+ end
15
+
16
+ it "raises an error when the response body is non-empty" do
17
+ response = generate_response(body: "something")
18
+ expect { Cucumber::Rest::Body.ensure_empty(response: response) }.to raise_error
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,304 @@
1
+ require "cucumber/rest/caching"
2
+
3
+ shared_examples "a cache header inspector" do |method, *header_names|
4
+
5
+ ["Cache-Control", "Date", "Expires"].each do |header_name|
6
+ it "raises an error when the #{header_name} header is missing" do
7
+ response = generate_response
8
+ response[header_name] = nil
9
+ expect {
10
+ Cucumber::Rest::Caching.send(method, { response: response })
11
+ }.to raise_error "Required header '#{header_name}' is missing"
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ describe Cucumber::Rest::Caching, :caching do
18
+
19
+ context "#ensure_response_is_publicly_cacheable" do
20
+
21
+ def generate_response(duration = 3600, date = DateTime.now, age = nil)
22
+ response = MockResponse.new
23
+ response["Cache-Control"] = "public, max-age=#{duration}"
24
+ response["Expires"] = (date + (duration / (24.0 * 3600))).strftime(RFC822_DATE_FORMAT)
25
+ if age
26
+ date += age / 86400.0
27
+ response["Age"] = age.to_s if age
28
+ end
29
+ response["Date"] = date.strftime(RFC822_DATE_FORMAT)
30
+ response.body = "test"
31
+ response
32
+ end
33
+
34
+ context "with cacheable responses" do
35
+ it_behaves_like "a cache header inspector", :ensure_response_is_publicly_cacheable
36
+
37
+ it "does not raise an error when the public cache headers are set correctly" do
38
+ duration = 3600
39
+ response = generate_response(duration)
40
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response, duration: duration)
41
+ end
42
+
43
+ it "does not raise an error when the public cache headers are set correctly, including an Age header" do
44
+ duration = 3600
45
+ response = generate_response(duration, DateTime.now, 243)
46
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response, duration: duration)
47
+ end
48
+
49
+ ["max-age"].each do |directive| # TODO: Should include "public"
50
+ it "raises an error when the Cache-Control header does not include the #{directive} directive" do
51
+ response = generate_response
52
+ response["Cache-Control"] = response["Cache-Control"].split(/\s*,\s*/).reject { |d| d =~ /^#{directive}($|=)/ }.join(", ")
53
+ expect {
54
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response)
55
+ }.to raise_error "Cache-Control should include the '#{directive}' directive"
56
+ end
57
+ end
58
+
59
+ ["private", "no-cache", "no-store"].each do |directive|
60
+ it "raises an error when the Cache-Control header includes the #{directive} directive" do
61
+ response = generate_response
62
+ response["Cache-Control"] << ", #{directive}"
63
+ expect {
64
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response)
65
+ }.to raise_error "Cache-Control should not include the '#{directive}' directive"
66
+ end
67
+ end
68
+
69
+ it "raises an error when Date, Expires and Cache-Control:max-age are inconsistent" do
70
+ response = generate_response
71
+ response["Expires"] = DateTime.now.strftime(RFC822_DATE_FORMAT)
72
+ expect {
73
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response)
74
+ }.to raise_error "Age, Date, Expires and Cache-Control:max-age are inconsistent"
75
+ end
76
+
77
+ it "raises an error when Age, Date, Expires and Cache-Control:max-age are inconsistent" do
78
+ response = generate_response
79
+ response["Age"] = "5"
80
+ expect {
81
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response)
82
+ }.to raise_error "Age, Date, Expires and Cache-Control:max-age are inconsistent"
83
+ end
84
+
85
+ it "raises an error when the Pragma header includes the no-cache directive" do
86
+ response = generate_response
87
+ response["Pragma"] = "no-cache"
88
+ expect {
89
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response)
90
+ }.to raise_error "Pragma should not include the 'no-cache' directive"
91
+ end
92
+
93
+ end
94
+
95
+ context "with application-level requirements" do
96
+
97
+ it "raises an error when the cache duration is higher than the expected duration" do
98
+ response = generate_response(3600)
99
+ expect {
100
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response, duration: 1800)
101
+ }.to raise_error "Cache duration is 3600s but expected no more than 1800s"
102
+ end
103
+
104
+ it "raises an error when the cache duration is lower than the expected duration" do
105
+ response = generate_response(900)
106
+ expect {
107
+ Cucumber::Rest::Caching.ensure_response_is_publicly_cacheable(response: response, duration: 1800)
108
+ }.to raise_error "Cache duration is 900s but expected at least 1800s"
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ context "#ensure_response_is_privately_cacheable" do
116
+
117
+ def generate_response(duration = 3600, date = DateTime.now)
118
+ response = MockResponse.new
119
+ response["Cache-Control"] = "private, max-age=#{duration}"
120
+ response["Date"] = date.strftime(RFC822_DATE_FORMAT)
121
+ response["Expires"] = date.strftime(RFC822_DATE_FORMAT)
122
+ response.body = "test"
123
+ response
124
+ end
125
+
126
+ context "with non-cacheable responses" do
127
+ it_behaves_like "a cache header inspector", :ensure_response_is_privately_cacheable
128
+
129
+ it "does not raise an error when the private cache headers are set correctly" do
130
+ duration = 3600
131
+ response = generate_response(duration)
132
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response, duration: duration)
133
+ end
134
+
135
+ it "does not raise an error when the private cache headers are set correctly, with Expires as -1" do
136
+ duration = 3600
137
+ response = generate_response(duration)
138
+ response["Expires"] = "-1" # invalid, but should be treated as in the past (i.e. already expired)
139
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response, duration: duration)
140
+ end
141
+
142
+ ["private", "max-age"].each do |directive|
143
+ it "raises an error when the Cache-Control header does not include the #{directive} directive" do
144
+ response = generate_response
145
+ response["Cache-Control"] = response["Cache-Control"].split(/\s*,\s*/).reject { |d| d =~ /^#{directive}($|=)/ }.join(", ")
146
+ expect {
147
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response)
148
+ }.to raise_error "Cache-Control should include the '#{directive}' directive"
149
+ end
150
+ end
151
+
152
+ ["public", "no-cache", "no-store"].each do |directive|
153
+ it "raises an error when the Cache-Control header includes the #{directive} directive" do
154
+ response = generate_response
155
+ response["Cache-Control"] << ", #{directive}"
156
+ expect {
157
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response)
158
+ }.to raise_error "Cache-Control should not include the '#{directive}' directive"
159
+ end
160
+ end
161
+
162
+ it "raises an error when Expires is a valid date later than Date" do
163
+ response = generate_response
164
+ response["Expires"] = (DateTime.now + 10).strftime(RFC822_DATE_FORMAT)
165
+ expect {
166
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response)
167
+ }.to raise_error "Expires should not be later than Date"
168
+ end
169
+
170
+ it "raises an error when the Pragma header includes the no-cache directive" do
171
+ response = generate_response
172
+ response["Pragma"] = "no-cache"
173
+ expect {
174
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response)
175
+ }.to raise_error "Pragma should not include the 'no-cache' directive"
176
+ end
177
+
178
+ end
179
+
180
+ context "with application-level requirements" do
181
+
182
+ it "raises an error when the cache duration is higher than the expected duration" do
183
+ response = generate_response(3600)
184
+ expect {
185
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response, duration: 1800)
186
+ }.to raise_error "Cache duration is 3600s but expected no more than 1800s"
187
+ end
188
+
189
+ it "raises an error when the cache duration is lower than the expected duration" do
190
+ response = generate_response(900)
191
+ expect {
192
+ Cucumber::Rest::Caching.ensure_response_is_privately_cacheable(response: response, duration: 1800)
193
+ }.to raise_error "Cache duration is 900s but expected at least 1800s"
194
+ end
195
+
196
+ end
197
+
198
+ end
199
+
200
+ context "#ensure_response_is_not_cacheable" do
201
+
202
+ def generate_response(date = DateTime.now)
203
+ response = MockResponse.new
204
+ response["Cache-Control"] = "no-store"
205
+ response["Date"] = date.strftime(RFC822_DATE_FORMAT)
206
+ response["Expires"] = date.strftime(RFC822_DATE_FORMAT)
207
+ response["Pragma"] = "no-cache"
208
+ response.body = "test"
209
+ response
210
+ end
211
+
212
+ context "with non-cacheable responses" do
213
+ it_behaves_like "a cache header inspector", :ensure_response_is_not_cacheable
214
+
215
+ it "does not raise an error when the prevent cache headers are set correctly" do
216
+ Cucumber::Rest::Caching.ensure_response_is_not_cacheable(response: generate_response)
217
+ end
218
+
219
+ it "does not raise an error when the public cache headers are set correctly, with Expires as -1" do
220
+ response = generate_response
221
+ response["Expires"] = "-1" # invalid, but should be treated as in the past (i.e. already expired)
222
+ Cucumber::Rest::Caching.ensure_response_is_not_cacheable(response: response)
223
+ end
224
+
225
+ ["no-store"].each do |directive|
226
+ it "raises an error when the Cache-Control header does not include the #{directive} directive" do
227
+ response = generate_response
228
+ response["Cache-Control"] = response["Cache-Control"].split(/\s*,\s*/).reject { |d| d =~ /^#{directive}($|=)/ }.join(", ")
229
+ expect {
230
+ Cucumber::Rest::Caching.ensure_response_is_not_cacheable(response: response)
231
+ }.to raise_error "Cache-Control should include the '#{directive}' directive"
232
+ end
233
+ end
234
+
235
+ ["public", "private", "max-age"].each do |directive|
236
+ it "raises an error when the Cache-Control header includes the #{directive} directive" do
237
+ response = generate_response
238
+ response["Cache-Control"] << ", #{directive}"
239
+ expect {
240
+ Cucumber::Rest::Caching.ensure_response_is_not_cacheable(response: response)
241
+ }.to raise_error "Cache-Control should not include the '#{directive}' directive"
242
+ end
243
+ end
244
+
245
+ it "raises an error when Expires is a valid date later than Date" do
246
+ response = generate_response
247
+ response["Expires"] = (DateTime.now + 10).strftime(RFC822_DATE_FORMAT)
248
+ expect {
249
+ Cucumber::Rest::Caching.ensure_response_is_not_cacheable(response: response)
250
+ }.to raise_error "Expires should not be later than Date"
251
+ end
252
+
253
+ it "raises an error when the Pragma header does not include the no-cache directive" do
254
+ response = generate_response
255
+ response["Pragma"] = nil
256
+ expect {
257
+ Cucumber::Rest::Caching.ensure_response_is_not_cacheable(response: response)
258
+ }.to raise_error "Pragma should include the 'no-cache' directive"
259
+ end
260
+
261
+ end
262
+
263
+ end
264
+
265
+ context "#parse_httpdate" do
266
+
267
+ it "doesn't raise an error when an RFC822 date is passed" do
268
+ date = DateTime.now.strftime(RFC822_DATE_FORMAT)
269
+ Cucumber::Rest::Caching.parse_httpdate(date)
270
+ end
271
+
272
+ it "doesn't raise an error when an ANCI C asctime format date is passed" do
273
+ date = DateTime.now.strftime(ANSI_C_DATE_FORMAT)
274
+ Cucumber::Rest::Caching.parse_httpdate(date)
275
+ end
276
+
277
+ it "doesn't raise an error when an RFC850 date is passed" do
278
+ date = DateTime.now.strftime(RFC850_DATE_FORMAT)
279
+ Cucumber::Rest::Caching.parse_httpdate(date)
280
+ end
281
+
282
+ it "raises an error when a non-English date is passed" do
283
+ date = "ma, 14 heinä 2014 16:08:33 GMT"
284
+ expect {
285
+ Cucumber::Rest::Caching.parse_httpdate(date)
286
+ }.to raise_error("invalid date")
287
+ end
288
+
289
+ it "raises an error when a non-GMT date is passed" do
290
+ date = "Mon, 14 Jul 2014 16:33:04 +0100"
291
+ expect {
292
+ Cucumber::Rest::Caching.parse_httpdate(date)
293
+ }.to raise_error("invalid date")
294
+ end
295
+
296
+ it "raises an error when empty date values are passed" do
297
+ expect {
298
+ Cucumber::Rest::Caching.parse_httpdate("")
299
+ }.to raise_error("Empty date value")
300
+ end
301
+
302
+ end
303
+
304
+ end
@@ -0,0 +1,40 @@
1
+ require "cucumber/rest/status"
2
+
3
+ shared_examples "a status class inspector" do |status_class, min, max|
4
+ def generate_response(status_code)
5
+ response = MockResponse.new
6
+ response.status = status_code
7
+ response
8
+ end
9
+
10
+ Rack::Utils::HTTP_STATUS_CODES.keys.keep_if { |code| code >= min && code <= max }.each do |status_code|
11
+ it "does not raise an error for status code #{status_code}" do
12
+ Cucumber::Rest::Status.ensure_status_class(status_class, response: generate_response(status_code))
13
+ end
14
+ end
15
+ Rack::Utils::HTTP_STATUS_CODES.keys.keep_if { |code| code < min || code > max }.each do |status_code|
16
+ it "raises an error for status code #{status_code}" do
17
+ expect {
18
+ Cucumber::Rest::Status.ensure_status_class(status_class, response: generate_response(status_code))
19
+ }.to raise_error
20
+ end
21
+ end
22
+ end
23
+
24
+ describe Cucumber::Rest::Status, :status do
25
+ context "#ensure_status_class(:informational)" do
26
+ it_behaves_like "a status class inspector", :informational, 100, 199
27
+ end
28
+ context "#ensure_status_class(:success)" do
29
+ it_behaves_like "a status class inspector", :success, 200, 299
30
+ end
31
+ context "#ensure_status_class(:redirection)" do
32
+ it_behaves_like "a status class inspector", :redirection, 300, 399
33
+ end
34
+ context "#ensure_status_class(:client_error)" do
35
+ it_behaves_like "a status class inspector", :client_error, 400, 499
36
+ end
37
+ context "#ensure_status_class(:server_error)" do
38
+ it_behaves_like "a status class inspector", :server_error, 500, 599
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ require "cucumber"
2
+
3
+ RFC822_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
4
+ RFC850_DATE_FORMAT = "%A, %d-%b-%y %H:%M:%S GMT"
5
+ ANSI_C_DATE_FORMAT = "%a %b %e %H:%M:%S %Y"
6
+
7
+ # A mock response class that looks like HttpCapture::Response
8
+ class MockResponse
9
+ include Enumerable
10
+
11
+ attr_accessor :status
12
+ attr_accessor :body
13
+
14
+ def initialize
15
+ @header = {}
16
+ end
17
+
18
+ # The default header accessor
19
+ def [](key)
20
+ @header[key]
21
+ end
22
+
23
+ # The default header accessor
24
+ def []=(key, value)
25
+ @header[key] = value
26
+ end
27
+
28
+ def each(&block)
29
+ @header.each(&block)
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,204 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cucumber-rest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - blinkbox books
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: cucumber
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: http_capture
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '0.0'
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: 0.0.4
93
+ type: :runtime
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ~>
98
+ - !ruby/object:Gem::Version
99
+ version: '0.0'
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: 0.0.4
103
+ - !ruby/object:Gem::Dependency
104
+ name: bundler
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '1.3'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ~>
115
+ - !ruby/object:Gem::Version
116
+ version: '1.3'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rake
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ~>
122
+ - !ruby/object:Gem::Version
123
+ version: '10.1'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ~>
129
+ - !ruby/object:Gem::Version
130
+ version: '10.1'
131
+ - !ruby/object:Gem::Dependency
132
+ name: cucumber_spinner
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ! '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ! '>='
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ description: A set of Cucumber step definitions and support functions which encapsulate
146
+ common RESTful functionality.
147
+ email: greg@blinkbox.com
148
+ executables: []
149
+ extensions: []
150
+ extra_rdoc_files:
151
+ - README.md
152
+ files:
153
+ - .gitignore
154
+ - .rspec
155
+ - .travis.yml
156
+ - CHANGELOG.md
157
+ - Gemfile
158
+ - LICENCE
159
+ - README.md
160
+ - Rakefile
161
+ - VERSION
162
+ - cucumber-rest.gemspec
163
+ - lib/cucumber/rest.rb
164
+ - lib/cucumber/rest/body.rb
165
+ - lib/cucumber/rest/caching.rb
166
+ - lib/cucumber/rest/status.rb
167
+ - lib/cucumber/rest/steps.rb
168
+ - lib/cucumber/rest/steps/caching.rb
169
+ - lib/cucumber/rest/steps/status.rb
170
+ - lib/cucumber/rest/version.rb
171
+ - spec/cucumber/rest/body_spec.rb
172
+ - spec/cucumber/rest/caching_spec.rb
173
+ - spec/cucumber/rest/status_spec.rb
174
+ - spec/spec_helper.rb
175
+ homepage: http://www.blinkboxbooks.com
176
+ licenses:
177
+ - MIT
178
+ metadata: {}
179
+ post_install_message: ':: Coded for blinkbox books :: Love books, love code? Get in
180
+ touch ::'
181
+ rdoc_options: []
182
+ require_paths:
183
+ - lib
184
+ required_ruby_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ! '>='
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ required_rubygems_version: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ! '>='
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ requirements: []
195
+ rubyforge_project:
196
+ rubygems_version: 2.4.5
197
+ signing_key:
198
+ specification_version: 4
199
+ summary: Cucumber steps and support for testing RESTful services.
200
+ test_files:
201
+ - spec/cucumber/rest/body_spec.rb
202
+ - spec/cucumber/rest/caching_spec.rb
203
+ - spec/cucumber/rest/status_spec.rb
204
+ - spec/spec_helper.rb