akamai_rspec 0.4.0 → 1.0.0
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 +4 -4
- data/lib/akamai_rspec.rb +4 -2
- data/lib/akamai_rspec/helpers/cache_headers.rb +17 -0
- data/lib/akamai_rspec/helpers/chainable_redirect.rb +42 -0
- data/lib/akamai_rspec/matchers.rb +15 -0
- data/lib/akamai_rspec/matchers/caching.rb +59 -35
- data/lib/akamai_rspec/matchers/forward_to_index.rb +20 -0
- data/lib/akamai_rspec/matchers/honour_origin_headers.rb +125 -117
- data/lib/akamai_rspec/matchers/non_akamai.rb +63 -62
- data/lib/akamai_rspec/matchers/redirects.rb +46 -21
- data/lib/akamai_rspec/matchers/x_cache_headers.rb +26 -20
- data/lib/akamai_rspec/request.rb +31 -9
- data/lib/akamai_rspec/response.rb +25 -14
- metadata +7 -4
- data/lib/akamai_rspec/akamai_headers.rb +0 -7
- data/lib/akamai_rspec/matchers/matchers.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6ca676a572b65fda62bd771e10b8472cdcb1704
|
4
|
+
data.tar.gz: e782f1e731582d5feb4270fbace5d6fef0eb4b46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f58ba2ed53d2f7edcb81f449b9e658940f69c6a7aa1cd9e938d34ad6ab3a48597325791f2b95fe157c57b62b5913668fc4e5091bc619a65b7bf4fa24b8d140f
|
7
|
+
data.tar.gz: 371fbd6072b3a66a3e2db22d1b6186558a898f2448e799bfad310a9983939c79e7956e9d28ab37b14ef830276c3bbc8de2443da9d52efc5d1e692343c6049f82
|
data/lib/akamai_rspec.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
module AkamaiRSpec
|
3
|
+
module Helpers
|
4
|
+
module CacheHeaders
|
5
|
+
X_CACHE_HEADERS = [:x_true_cache_key, :x_cache_key]
|
6
|
+
|
7
|
+
def x_cache_headers
|
8
|
+
X_CACHE_HEADERS
|
9
|
+
end
|
10
|
+
|
11
|
+
def cache_headers
|
12
|
+
x_cache_headers.map {|key| @response.headers[key] }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
module AkamaiRSpec
|
3
|
+
module Helpers
|
4
|
+
module ChainableRedirect
|
5
|
+
def self.included(other)
|
6
|
+
other.chain :then do |matcher|
|
7
|
+
(@and_then_matchers ||= []).push(matcher)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_and_without_tls(url)
|
12
|
+
url = "http://#{url}" unless URI(url).scheme
|
13
|
+
url = url.gsub(/^https:/i, 'http:')
|
14
|
+
secure = url.gsub(/^http:/i, 'https:')
|
15
|
+
return secure, url
|
16
|
+
end
|
17
|
+
|
18
|
+
def redirect(url, expected_location, expected_response_code)
|
19
|
+
response = AkamaiRSpec::Request.get(url)
|
20
|
+
fail "Response was #{response.inspect}, expected code #{expected_response_code}" unless response.code == expected_response_code
|
21
|
+
unless expected_location === response.headers[:location]
|
22
|
+
fail "redirect location was #{response.headers[:location]} (expected #{expected_location})"
|
23
|
+
end
|
24
|
+
|
25
|
+
if @and_then_matchers
|
26
|
+
begin
|
27
|
+
@and_then_matchers.each {|matcher| expect(response.headers[:location]).to matcher}
|
28
|
+
rescue Exception => e
|
29
|
+
@and_then_error = e
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def failure_message
|
38
|
+
@and_then_error || super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
module AkamaiRSpec
|
5
|
+
module Matchers
|
6
|
+
extend RSpec::Matchers::DSL
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'matchers/redirects'
|
11
|
+
require_relative 'matchers/caching'
|
12
|
+
require_relative 'matchers/non_akamai'
|
13
|
+
require_relative 'matchers/honour_origin_headers'
|
14
|
+
require_relative 'matchers/x_cache_headers'
|
15
|
+
require_relative 'matchers/forward_to_index'
|
@@ -1,44 +1,68 @@
|
|
1
1
|
require 'rspec'
|
2
2
|
require 'securerandom'
|
3
|
-
require 'akamai_rspec/
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
require 'akamai_rspec/request'
|
4
|
+
|
5
|
+
module AkamaiRSpec
|
6
|
+
module Matchers
|
7
|
+
define :be_cacheable do |request_count: 4, headers: {}, allow_refresh: false|
|
8
|
+
match do |url|
|
9
|
+
@responses = (1..request_count).map {
|
10
|
+
AkamaiRSpec::Request.get url, headers
|
11
|
+
}
|
12
|
+
|
13
|
+
@responses.any? do |response|
|
14
|
+
fail("Error fetching #{url}: #{response}") if response.code != 200
|
15
|
+
return allow_refresh if refresh_hit? response
|
16
|
+
hit?(response)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def refresh_hit?(response)
|
21
|
+
response.headers[:x_cache] =~ /TCP_REFRESH/
|
22
|
+
end
|
23
|
+
|
24
|
+
def hit?(response)
|
25
|
+
response.headers[:x_cache] =~ /TCP(\w+)?_HIT/
|
26
|
+
end
|
27
|
+
|
28
|
+
def suggest_allow_refresh?(allow_refresh)
|
29
|
+
!allow_refresh &&
|
30
|
+
!@responses.any?(&method(:hit?)) &&
|
31
|
+
@responses.any?(&method(:refresh_hit?))
|
32
|
+
end
|
33
|
+
|
34
|
+
description do
|
35
|
+
msg = "to be cacheable (got #{cache_headers} from #{@responses.length} requests)"
|
36
|
+
msg += ". Try setting 'allow_refresh: true'." if suggest_allow_refresh?(allow_refresh)
|
37
|
+
msg
|
38
|
+
end
|
39
|
+
|
40
|
+
def cache_headers
|
41
|
+
@responses.map {|response| response.headers[:x_cache] }
|
42
|
+
end
|
15
43
|
end
|
16
|
-
end
|
17
|
-
end
|
18
44
|
|
19
|
-
|
20
|
-
|
21
|
-
alias_method :be_cached, :be_cacheable
|
22
|
-
define_negated_matcher :not_be_cached, :be_cached
|
23
|
-
end
|
45
|
+
alias_method :be_cachable, :be_cacheable
|
46
|
+
alias_method :be_cached, :be_cacheable
|
24
47
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
48
|
+
define :have_no_cache_set do
|
49
|
+
match do |url|
|
50
|
+
response = AkamaiRSpec::Request.get url
|
51
|
+
cache_control = response.headers[:cache_control]
|
52
|
+
return cache_control == 'no-cache'
|
53
|
+
end
|
54
|
+
end
|
32
55
|
|
56
|
+
define :be_tier_distributed do
|
57
|
+
match do |url|
|
58
|
+
response = AkamaiRSpec::Request.get_cache_miss(url)
|
59
|
+
@tiered = !response.headers[:x_cache_remote].empty?
|
60
|
+
response.code == 200 && @tiered
|
61
|
+
end
|
33
62
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
response.code == 200 && @tiered
|
39
|
-
end
|
40
|
-
description do
|
41
|
-
"be tier distributed (as indicated by the presence of an X-Cache-Remote header in response)"
|
63
|
+
description do
|
64
|
+
"be tier distributed (as indicated by the presence of an X-Cache-Remote header in response)"
|
65
|
+
end
|
66
|
+
end
|
42
67
|
end
|
43
68
|
end
|
44
|
-
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module AkamaiRSpec
|
3
|
+
module Matchers
|
4
|
+
define :be_forwarded_to_index do |channel|
|
5
|
+
match do |url|
|
6
|
+
response = Request.get(url)
|
7
|
+
session_info = response.headers[:x_akamai_session_info]
|
8
|
+
if session_info.nil?
|
9
|
+
fail("x-akamai-session-info not found in the headers '#{response.headers}'")
|
10
|
+
end
|
11
|
+
outcome_attribute = session_info.find { |header| header.include? 'AKA_PM_FWD_URL' }
|
12
|
+
if outcome_attribute.nil?
|
13
|
+
fail("AKA_PM_FWD_URL not found in the x-akamai-session-info header '#{session_info}'")
|
14
|
+
end
|
15
|
+
outcome_url = outcome_attribute.split('value=')[1]
|
16
|
+
response.code == 200 && outcome_url == "#{channel}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,122 +3,130 @@ require 'set'
|
|
3
3
|
require 'time'
|
4
4
|
require 'uri'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
module AkamaiRSpec
|
7
|
+
module Matchers
|
8
|
+
|
9
|
+
define :honour_origin_cache_headers do |origin, headers=:both|
|
10
|
+
header_options = [:cache_control, :expires, :both]
|
11
|
+
fail("Headers must be one of: #{header_options}") unless header_options.include? headers
|
12
|
+
|
13
|
+
match do |url|
|
14
|
+
akamai_response = AkamaiRSpec::Request.get url
|
15
|
+
url = "http://" + url unless url =~ /^http/
|
16
|
+
origin_url = URI(url)
|
17
|
+
origin_url.host = URI(origin).hostname || origin
|
18
|
+
origin_response = origin_response(origin_url.to_s)
|
19
|
+
check_cache_control(origin_response, akamai_response, headers)
|
20
|
+
check_expires(origin_response, akamai_response, headers)
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def fix_date_header(origin_response)
|
25
|
+
origin_response.headers[:date] ||= Time.now.httpdate
|
26
|
+
origin_response
|
27
|
+
end
|
28
|
+
|
29
|
+
def origin_response(origin)
|
30
|
+
fix_date_header(RestClient::Request.execute(method: :get, url: origin, verify_ssl: false))
|
31
|
+
end
|
32
|
+
|
33
|
+
def clean_cc_directives(origin_response, akamai_response)
|
34
|
+
origin_cc_directives = origin_response.headers[:cache_control].split(/[, ]+/).to_set
|
35
|
+
akamai_cc_directives = akamai_response.headers[:cache_control].split(/[, ]+/).to_set
|
36
|
+
|
37
|
+
origin_cc_directives.delete 'must-revalidate' # as Akamai does no pass it on
|
38
|
+
return origin_cc_directives, akamai_cc_directives
|
39
|
+
end
|
40
|
+
|
41
|
+
def cc_directives(origin_response, akamai_response)
|
42
|
+
origin_cc, akamai_cc = clean_cc_directives(origin_response, akamai_response)
|
43
|
+
check_cc(origin_cc, akamai_cc) unless (origin_cc & ['no-store', 'no-cache']).empty?
|
44
|
+
return origin_cc, akamai_cc
|
45
|
+
end
|
46
|
+
|
47
|
+
def check_and_clean_header(origin_cc, akamai_cc, expected)
|
48
|
+
unless akamai_cc.include? expected
|
49
|
+
fail "Akamai was expected to, but did not, add 'Cache-Control: #{expected}' as Origin sent 'no-store' or 'no-cache'"
|
50
|
+
end
|
51
|
+
akamai_cc.delete expected unless origin_cc.include? expected
|
52
|
+
akamai_cc
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_cc(origin_cc, akamai_cc)
|
56
|
+
['no-store', 'max-age=0'].each do |expected|
|
57
|
+
akamai_cc = check_and_clean_header(origin_cc, akamai_cc, expected)
|
58
|
+
end
|
59
|
+
return origin_cc, akamai_cc
|
60
|
+
end
|
61
|
+
|
62
|
+
def max_age(cc_directives)
|
63
|
+
cc_directives.detect { |d| d.start_with? 'max-age=' }
|
64
|
+
end
|
65
|
+
|
66
|
+
def max_age_to_num(max_age)
|
67
|
+
max_age.split('=').last.to_i
|
68
|
+
end
|
69
|
+
|
70
|
+
def clean_max_age(cc_directives)
|
71
|
+
max_age = max_age(cc_directives)
|
72
|
+
cc_directives.delete max_age if max_age
|
73
|
+
return max_age_to_num(max_age), cc_directives
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_max_age(origin_cc_directives, akamai_cc_directives)
|
77
|
+
origin_max_age, origin_cc_directives = clean_max_age(origin_cc_directives)
|
78
|
+
akamai_max_age, akamai_cc_directives = clean_max_age(akamai_cc_directives)
|
79
|
+
if akamai_max_age > origin_max_age
|
80
|
+
fail "Akamai sent a max-age greater than Origin's: #{akamai_max_age} > #{origin_max_age}"
|
81
|
+
end
|
82
|
+
return origin_cc_directives, akamai_cc_directives
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_akamai_dropped(origin_cc, akamai_cc)
|
86
|
+
dropped = origin_cc - akamai_cc
|
87
|
+
unless dropped.empty?
|
88
|
+
fail "Origin sent 'Cache-Control: #{dropped.to_a.join ','}', but Akamai did not."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_akamai_added(origin_cc, akamai_cc)
|
93
|
+
added = akamai_cc - origin_cc
|
94
|
+
unless added.empty?
|
95
|
+
fail "Akamai unexpectedly added 'Cache-Control: #{added.to_a.join ','}'"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def check_cache_control(origin_response, akamai_response, headers)
|
100
|
+
if [:both, :cache_control].include? headers
|
101
|
+
origin_cc, akamai_cc = cc_directives(origin_response, akamai_response)
|
102
|
+
origin_cc, akamai_cc = check_max_age(origin_cc, akamai_cc)
|
103
|
+
validate_akamai_dropped(origin_cc, akamai_cc)
|
104
|
+
validate_akamai_added(origin_cc, akamai_cc)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def check_expires(origin_response, akamai_response, headers)
|
109
|
+
if [:both, :expires].include? headers
|
110
|
+
origin_expires, akamai_expires = expires(origin_response, akamai_response)
|
111
|
+
validate_expires(origin_expires, akamai_expires)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def validate_expires(origin, akamai)
|
116
|
+
unless akamai.to_i == origin.to_i
|
117
|
+
fail "Origin sent 'Expires: #{origin}' but Akamai sent 'Expires: #{akamai}'"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def expires(origin_response, akamai_response)
|
122
|
+
return origin_expires(origin_response), Time.httpdate(akamai_response.headers[:expires])
|
123
|
+
end
|
124
|
+
|
125
|
+
def origin_expires(origin_response)
|
126
|
+
expires = origin_response.headers[:expires]
|
127
|
+
expires == '0' ? Time.httpdate(origin_response.headers[:date]) : Time.httpdate(expires)
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
17
131
|
end
|
18
132
|
end
|
19
|
-
|
20
|
-
def fix_date_header(origin_response)
|
21
|
-
origin_response.headers[:date] ||= Time.now.httpdate
|
22
|
-
origin_response
|
23
|
-
end
|
24
|
-
|
25
|
-
def origin_response(origin)
|
26
|
-
fix_date_header(RestClient::Request.execute(method: :get, url: origin, verify_ssl: false))
|
27
|
-
end
|
28
|
-
|
29
|
-
def clean_cc_directives(origin_response, akamai_response)
|
30
|
-
origin_cc_directives = origin_response.headers[:cache_control].split(/[, ]+/).to_set
|
31
|
-
akamai_cc_directives = akamai_response.headers[:cache_control].split(/[, ]+/).to_set
|
32
|
-
|
33
|
-
origin_cc_directives.delete 'must-revalidate' # as Akamai does no pass it on
|
34
|
-
return origin_cc_directives, akamai_cc_directives
|
35
|
-
end
|
36
|
-
|
37
|
-
def cc_directives(origin_response, akamai_response)
|
38
|
-
origin_cc, akamai_cc = clean_cc_directives(origin_response, akamai_response)
|
39
|
-
check_cc(origin_cc, akamai_cc) unless (origin_cc & ['no-store', 'no-cache']).empty?
|
40
|
-
return origin_cc, akamai_cc
|
41
|
-
end
|
42
|
-
|
43
|
-
def check_and_clean_header(origin_cc, akamai_cc, expected)
|
44
|
-
unless akamai_cc.include? expected
|
45
|
-
fail "Akamai was expected to, but did not, add 'Cache-Control: #{expected}' as Origin sent 'no-store' or 'no-cache'"
|
46
|
-
end
|
47
|
-
akamai_cc.delete expected unless origin_cc.include? expected
|
48
|
-
akamai_cc
|
49
|
-
end
|
50
|
-
|
51
|
-
def check_cc(origin_cc, akamai_cc)
|
52
|
-
['no-store', 'max-age=0'].each do |expected|
|
53
|
-
akamai_cc = check_and_clean_header(origin_cc, akamai_cc, expected)
|
54
|
-
end
|
55
|
-
return origin_cc, akamai_cc
|
56
|
-
end
|
57
|
-
|
58
|
-
def max_age(cc_directives)
|
59
|
-
cc_directives.detect { |d| d.start_with? 'max-age=' }
|
60
|
-
end
|
61
|
-
|
62
|
-
def max_age_to_num(max_age)
|
63
|
-
max_age.split('=').last.to_i
|
64
|
-
end
|
65
|
-
|
66
|
-
def clean_max_age(cc_directives)
|
67
|
-
max_age = max_age(cc_directives)
|
68
|
-
cc_directives.delete max_age if max_age
|
69
|
-
return max_age_to_num(max_age), cc_directives
|
70
|
-
end
|
71
|
-
|
72
|
-
def check_max_age(origin_cc_directives, akamai_cc_directives)
|
73
|
-
origin_max_age, origin_cc_directives = clean_max_age(origin_cc_directives)
|
74
|
-
akamai_max_age, akamai_cc_directives = clean_max_age(akamai_cc_directives)
|
75
|
-
if akamai_max_age > origin_max_age
|
76
|
-
fail "Akamai sent a max-age greater than Origin's: #{akamai_max_age} > #{origin_max_age}"
|
77
|
-
end
|
78
|
-
return origin_cc_directives, akamai_cc_directives
|
79
|
-
end
|
80
|
-
|
81
|
-
def validate_akamai_dropped(origin_cc, akamai_cc)
|
82
|
-
dropped = origin_cc - akamai_cc
|
83
|
-
unless dropped.empty?
|
84
|
-
fail "Origin sent 'Cache-Control: #{dropped.to_a.join ','}', but Akamai did not."
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def validate_akamai_added(origin_cc, akamai_cc)
|
89
|
-
added = akamai_cc - origin_cc
|
90
|
-
unless added.empty?
|
91
|
-
fail "Akamai unexpectedly added 'Cache-Control: #{added.to_a.join ','}'"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def check_cache_control(origin_response, akamai_response, headers)
|
96
|
-
if [:both, :cache_control].include? headers
|
97
|
-
origin_cc, akamai_cc = cc_directives(origin_response, akamai_response)
|
98
|
-
origin_cc, akamai_cc = check_max_age(origin_cc, akamai_cc)
|
99
|
-
validate_akamai_dropped(origin_cc, akamai_cc)
|
100
|
-
validate_akamai_added(origin_cc, akamai_cc)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def check_expires(origin_response, akamai_response, headers)
|
105
|
-
if [:both, :expires].include? headers
|
106
|
-
origin_expires, akamai_expires = expires(origin_response, akamai_response)
|
107
|
-
validate_expires(origin_expires, akamai_expires)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def validate_expires(origin, akamai)
|
112
|
-
unless akamai.to_i == origin.to_i
|
113
|
-
fail "Origin sent 'Expires: #{origin}' but Akamai sent 'Expires: #{akamai}'"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def expires(origin_response, akamai_response)
|
118
|
-
return origin_expires(origin_response), Time.httpdate(akamai_response.headers[:expires])
|
119
|
-
end
|
120
|
-
|
121
|
-
def origin_expires(origin_response)
|
122
|
-
expires = origin_response.headers[:expires]
|
123
|
-
expires == '0' ? Time.httpdate(origin_response.headers[:date]) : Time.httpdate(expires)
|
124
|
-
end
|
@@ -3,76 +3,77 @@ require 'socket'
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'uri'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# We get this after the request as we have layer 7 routing in Akamai
|
14
|
-
cert = OpenSSL::X509::Certificate.new(ssl_client.peer_cert)
|
15
|
-
ssl_client.sysclose
|
16
|
-
cert
|
17
|
-
end
|
6
|
+
module AkamaiRSpec
|
7
|
+
module Matchers
|
8
|
+
define :be_successful do |response_codes=(200..299)|
|
9
|
+
match do |url|
|
10
|
+
@response = AkamaiRSpec::Request.get url
|
11
|
+
response_codes === @response.code
|
12
|
+
end
|
18
13
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
'Accept: */*\r\n'
|
24
|
-
end
|
14
|
+
failure_message do |url|
|
15
|
+
"Response #{@response} was not successful for #{url}"
|
16
|
+
end
|
17
|
+
end
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
define :respond_with_headers do |headers|
|
20
|
+
match do |url|
|
21
|
+
@response = AkamaiRSpec::Request.get url
|
22
|
+
headers.each do |k, v|
|
23
|
+
fail "Expected header #{k} to be #{v}, got #{@response.headers[k]}" unless @response.headers[k] == v
|
24
|
+
end
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
define :be_verifiably_secure do
|
30
|
+
match do |url|
|
31
|
+
return false if URI(url).scheme == "http"
|
32
|
+
url = "https://#{url}" unless URI(url).scheme
|
33
|
+
begin
|
34
|
+
# Avoid AkamaiRspec::Request as it turns off SSL checking
|
35
|
+
@response = RestClient::Request.execute(method: :get, url: url, verify_ssl: OpenSSL::SSL::VERIFY_PEER)
|
36
|
+
return true
|
37
|
+
rescue => e
|
38
|
+
@error = e
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
url = "https://#{url}" unless URI(url).scheme
|
46
|
-
begin
|
47
|
-
RestClient::Request.execute(method: :get, url: url, verify_ssl: verify)
|
48
|
-
return true
|
49
|
-
rescue => e
|
50
|
-
return false
|
43
|
+
failure_message do |url|
|
44
|
+
"got error #{@error} fetching #{@response}"
|
45
|
+
end
|
51
46
|
end
|
52
|
-
end
|
53
|
-
end
|
54
47
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
48
|
+
define :be_gzipped do
|
49
|
+
match do |url|
|
50
|
+
@response = AkamaiRSpec::Request.get_decode url
|
51
|
+
@response.headers[:content_encoding] == 'gzip'
|
52
|
+
end
|
53
|
+
failure_message do |url|
|
54
|
+
"Expected #{url} to be gzipped (got #{@response})"
|
55
|
+
end
|
56
|
+
end
|
61
57
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
58
|
+
define :have_cookie do |cookie, value=nil|
|
59
|
+
match do |response_or_url|
|
60
|
+
response = AkamaiRSpec::Request.get response_or_url
|
61
|
+
unless response.cookies[cookie]
|
62
|
+
fail("Cookie #{cookie} not in #{response.cookies}")
|
63
|
+
end
|
64
|
+
if value && response.cookies[cookie] != value
|
65
|
+
fail("Cookie #{cookie} was set to #{response.cookies[cookie]}, expected #{value}")
|
66
|
+
end
|
67
|
+
!!response.cookies[cookie]
|
68
|
+
end
|
67
69
|
end
|
68
|
-
response.cookies[cookie]
|
69
|
-
end
|
70
|
-
end
|
71
70
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
define :be_forbidden do
|
72
|
+
match do |url|
|
73
|
+
response = AkamaiRSpec::Request.get url
|
74
|
+
fail("Response #{response} was not forbidden") unless response.code == 403
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
77
78
|
end
|
78
79
|
end
|
@@ -1,28 +1,53 @@
|
|
1
1
|
require 'rspec'
|
2
|
+
require 'uri'
|
3
|
+
require 'akamai_rspec/helpers/chainable_redirect'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
module AkamaiRSpec
|
6
|
+
module Matchers
|
7
|
+
define :be_permanently_redirected_to do |expected_location|
|
8
|
+
include AkamaiRSpec::Helpers::ChainableRedirect
|
9
|
+
match do |url|
|
10
|
+
redirect(url, expected_location, 301)
|
11
|
+
end
|
12
|
+
end
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
+
define :be_temporarily_redirected_to do |expected_location|
|
15
|
+
include AkamaiRSpec::Helpers::ChainableRedirect
|
16
|
+
match do |url|
|
17
|
+
redirect(url, expected_location, 302)
|
18
|
+
end
|
19
|
+
end
|
14
20
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
define :redirect_http_to_https do |with: 301|
|
22
|
+
include AkamaiRSpec::Helpers::ChainableRedirect
|
23
|
+
match do |url|
|
24
|
+
secure, url = with_and_without_tls(url)
|
25
|
+
redirect(url, secure, with)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
define :redirect_https_to_http do |with: 301|
|
30
|
+
include AkamaiRSpec::Helpers::ChainableRedirect
|
31
|
+
match do |url|
|
32
|
+
secure, url = with_and_without_tls(url)
|
33
|
+
redirect(secure, url, with)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
define :redirect_to_add_trailing_slash do |with: 301|
|
38
|
+
include AkamaiRSpec::Helpers::ChainableRedirect
|
39
|
+
match do |url|
|
40
|
+
without_trailing_slash = url.gsub(/\/+$/, '')
|
41
|
+
with_trailing_slash = without_trailing_slash + "/"
|
42
|
+
redirect(without_trailing_slash, with_trailing_slash, with)
|
43
|
+
end
|
44
|
+
end
|
20
45
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
46
|
+
define :be_temporarily_redirected_with_trailing_slash do
|
47
|
+
include AkamaiRSpec::Helpers::ChainableRedirect
|
48
|
+
match do |url|
|
49
|
+
redirect(url, url + '/', 302)
|
50
|
+
end
|
51
|
+
end
|
26
52
|
end
|
27
|
-
true
|
28
53
|
end
|
@@ -1,29 +1,35 @@
|
|
1
1
|
require 'rspec'
|
2
|
+
require 'akamai_rspec/helpers/cache_headers'
|
2
3
|
|
3
4
|
module AkamaiRSpec
|
4
|
-
module
|
5
|
-
|
5
|
+
module Matchers
|
6
|
+
define :be_served_from_origin do |contents|
|
7
|
+
include AkamaiRSpec::Helpers::CacheHeaders
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
+
match do |url|
|
10
|
+
@response = AkamaiRSpec::Request.get url
|
11
|
+
cache_headers.any? {|h| h.split("/").include? contents}
|
12
|
+
end
|
13
|
+
|
14
|
+
failure_message do |actual|
|
15
|
+
"expected \"#{actual}\" to be served from origin \"#{contents}\"; got #{@response} and the cache headers indicated '#{cache_headers.inspect}'"
|
16
|
+
end
|
9
17
|
end
|
10
|
-
end
|
11
|
-
end
|
12
18
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
19
|
+
define :have_cp_code do |contents|
|
20
|
+
include AkamaiRSpec::Helpers::CacheHeaders
|
21
|
+
match do |url|
|
22
|
+
@response = AkamaiRSpec::Request.get url
|
23
|
+
@response.code == 200 && cache_headers.any? do |value|
|
24
|
+
value.to_s.split("/").include? contents
|
25
|
+
end
|
26
|
+
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
failure_message do |url|
|
29
|
+
headers = {}
|
30
|
+
x_cache_headers.each {|h| headers[h] = @response.headers[h] }
|
31
|
+
"Expected #{url} to have cp_code #{contents} but responded with #{headers.to_json}"
|
32
|
+
end
|
33
|
+
end
|
28
34
|
end
|
29
35
|
end
|
data/lib/akamai_rspec/request.rb
CHANGED
@@ -20,26 +20,42 @@ module AkamaiRSpec
|
|
20
20
|
@@env = env
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.get(url)
|
24
|
-
new.get(url)
|
23
|
+
def self.get(url, headers={})
|
24
|
+
new.get(url, headers.merge(debug_headers))
|
25
25
|
end
|
26
26
|
|
27
|
-
def self.
|
28
|
-
new.get(url,
|
27
|
+
def self.get_without_debug_headers(url, headers={})
|
28
|
+
new.get(url, headers)
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.get_cache_miss(url)
|
32
32
|
url += url.include?('?') ? '&' : '?'
|
33
33
|
url += SecureRandom.hex
|
34
|
-
|
34
|
+
get(url)
|
35
35
|
end
|
36
36
|
|
37
37
|
def self.get_decode(url)
|
38
|
-
response = new.get(url,
|
38
|
+
response = new.get(url, debug_headers.merge({'Accept-Encoding' => 'gzip'}))
|
39
39
|
RestClient::Request.decode(response.headers[:content_encoding], response.body)
|
40
40
|
response
|
41
41
|
end
|
42
42
|
|
43
|
+
def self.debug_headers
|
44
|
+
{
|
45
|
+
pragma: [
|
46
|
+
'akamai-x-cache-on',
|
47
|
+
'akamai-x-cache-remote-on',
|
48
|
+
'akamai-x-check-cacheable',
|
49
|
+
'akamai-x-get-cache-key',
|
50
|
+
'akamai-x-get-extracted-values',
|
51
|
+
'akamai-x-get-nonces',
|
52
|
+
'akamai-x-get-ssl-client-session-id',
|
53
|
+
'akamai-x-get-true-cache-key',
|
54
|
+
'akamai-x-serial-no'
|
55
|
+
].join(", ")
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
43
59
|
def initialize
|
44
60
|
@@env ||= 'prod'
|
45
61
|
|
@@ -47,7 +63,7 @@ module AkamaiRSpec
|
|
47
63
|
when 'staging'
|
48
64
|
if @@akamai_stg_domain.nil?
|
49
65
|
raise ArgumentError.new(
|
50
|
-
"You must set the
|
66
|
+
"You must set the staging domain: AkamaiRSpec::Request.stg_domain = 'www.example.com.edgesuite.net'"
|
51
67
|
)
|
52
68
|
end
|
53
69
|
|
@@ -70,12 +86,18 @@ module AkamaiRSpec
|
|
70
86
|
delegate [:parse_url_with_auth, :stringify_headers] => :@rest_client
|
71
87
|
|
72
88
|
def get(url, headers = {})
|
73
|
-
|
89
|
+
# DO NOT USE url.is_a? here - some versions of
|
90
|
+
# the JSON gem monkey patch String which causes it to match.
|
91
|
+
|
92
|
+
if url.class.ancestors.include? RestClient::Response
|
74
93
|
return AkamaiRSpec::Response.new(url)
|
75
94
|
end
|
76
95
|
|
77
|
-
|
96
|
+
if url.class.ancestors.include? AkamaiRSpec::Response
|
97
|
+
return url
|
98
|
+
end
|
78
99
|
|
100
|
+
uri = parse_url_with_auth(url)
|
79
101
|
req = build_request(uri, stringify_headers(headers))
|
80
102
|
|
81
103
|
req['Host'] = uri.hostname
|
@@ -6,6 +6,7 @@ module AkamaiRSpec
|
|
6
6
|
|
7
7
|
def headers
|
8
8
|
headers = Hash[@response.to_hash.map{ |k, v| [k.gsub(/-/,'_').downcase.to_sym, v] }]
|
9
|
+
headers.default = ""
|
9
10
|
headers.each do |k, v|
|
10
11
|
if v.is_a?(Array) && v.size == 1
|
11
12
|
headers[k] = v.first
|
@@ -20,26 +21,36 @@ module AkamaiRSpec
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def cookies
|
23
|
-
|
24
|
-
if cookie_header
|
25
|
-
if cookie_header.is_a?(Array)
|
26
|
-
cookies_string = cookie_header.collect do |header_value|
|
27
|
-
header_value.split('; ')
|
28
|
-
end
|
29
|
-
cookies_string.flatten!
|
30
|
-
cookies_array = cookies_string.collect { |c| c.split('=') }
|
31
|
-
else
|
32
|
-
cookies_array = [cookie_header.split('=')]
|
33
|
-
end
|
24
|
+
cookies = {}
|
34
25
|
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
[headers[:set_cookie]].flatten.each do |cookie|
|
27
|
+
name, value = cookie.split(/=/, 2)
|
28
|
+
cookies[name] = value
|
38
29
|
end
|
30
|
+
cookies
|
39
31
|
end
|
40
32
|
|
41
33
|
def method_missing(method, *args)
|
42
34
|
@response.send(method, *args)
|
43
35
|
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
case code
|
39
|
+
when 0..99
|
40
|
+
"Invalid status code #{code}"
|
41
|
+
when 100..199
|
42
|
+
"Informational: #{code}"
|
43
|
+
when 200..299
|
44
|
+
"Success: #{code}"
|
45
|
+
when 300..399
|
46
|
+
"Redirect #{code} to #{headers[:location]}"
|
47
|
+
when 400..499
|
48
|
+
"Client error #{code}"
|
49
|
+
when 500..599
|
50
|
+
"Server error #{code}"
|
51
|
+
else
|
52
|
+
"Unknown status code #{code}"
|
53
|
+
end
|
54
|
+
end
|
44
55
|
end
|
45
56
|
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akamai_rspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bianca Gibson
|
8
|
+
- Daniel Heath
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
12
|
+
date: 2016-10-28 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rest-client
|
@@ -59,10 +60,12 @@ extensions: []
|
|
59
60
|
extra_rdoc_files: []
|
60
61
|
files:
|
61
62
|
- lib/akamai_rspec.rb
|
62
|
-
- lib/akamai_rspec/
|
63
|
+
- lib/akamai_rspec/helpers/cache_headers.rb
|
64
|
+
- lib/akamai_rspec/helpers/chainable_redirect.rb
|
65
|
+
- lib/akamai_rspec/matchers.rb
|
63
66
|
- lib/akamai_rspec/matchers/caching.rb
|
67
|
+
- lib/akamai_rspec/matchers/forward_to_index.rb
|
64
68
|
- lib/akamai_rspec/matchers/honour_origin_headers.rb
|
65
|
-
- lib/akamai_rspec/matchers/matchers.rb
|
66
69
|
- lib/akamai_rspec/matchers/non_akamai.rb
|
67
70
|
- lib/akamai_rspec/matchers/redirects.rb
|
68
71
|
- lib/akamai_rspec/matchers/x_cache_headers.rb
|
@@ -1,7 +0,0 @@
|
|
1
|
-
module AkamaiHeaders
|
2
|
-
def self.akamai_debug_headers
|
3
|
-
{
|
4
|
-
pragma: 'akamai-x-cache-on, akamai-x-cache-remote-on, akamai-x-check-cacheable, akamai-x-get-cache-key, akamai-x-get-extracted-values, akamai-x-get-nonces, akamai-x-get-ssl-client-session-id, akamai-x-get-true-cache-key, akamai-x-serial-no'
|
5
|
-
}
|
6
|
-
end
|
7
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
require 'rspec'
|
3
|
-
require_relative 'redirects'
|
4
|
-
require_relative 'caching'
|
5
|
-
require_relative 'non_akamai'
|
6
|
-
require_relative 'honour_origin_headers'
|
7
|
-
require_relative 'x_cache_headers'
|
8
|
-
include AkamaiHeaders
|
9
|
-
|
10
|
-
RSpec::Matchers.define :be_forwarded_to_index do |channel|
|
11
|
-
match do |url|
|
12
|
-
response = RestClient.get(url, akamai_debug_headers)
|
13
|
-
|
14
|
-
session_info = response.raw_headers['x-akamai-session-info']
|
15
|
-
if session_info.nil?
|
16
|
-
fail("x-akamai-session-info not found in the headers '#{response.raw_headers}'")
|
17
|
-
end
|
18
|
-
outcome_attribute = session_info.find { |header| header.include? 'AKA_PM_FWD_URL' }
|
19
|
-
if outcome_attribute.nil?
|
20
|
-
fail("AKA_PM_FWD_URL not found in the x-akamai-session-info header '#{session_info}'")
|
21
|
-
end
|
22
|
-
outcome_url = outcome_attribute.split('value=')[1]
|
23
|
-
response.code == 200 && outcome_url == "#{channel}"
|
24
|
-
end
|
25
|
-
end
|