akamai_rspec 0.2.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 06d8b35c313b71c7d4d8723af6c0f6fac7884ec6
4
+ data.tar.gz: acddb3b50759171eb123010e287a1cf842cc1bb8
5
+ SHA512:
6
+ metadata.gz: e9c998292a3c9b68c4e125b43e318b062019903408159916cdc994fc63bf7b58e432190b7ab7dda0f3c3f1d66ef073c8d4c298a87c3749536b2b78a9cfc559d0
7
+ data.tar.gz: 57043ea5fa5f880a19bd2d964adcbb4970adc28b7bbe9812157186c32e76e6f0fe840703c0b4670743c5c4fbed1548e62150a2a7183ccc029c307b1a0f118357
@@ -0,0 +1,7 @@
1
+ module AkamaiHeaders
2
+ def 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
@@ -0,0 +1,54 @@
1
+ require 'rspec'
2
+ require 'securerandom'
3
+
4
+ RSpec::Matchers.define :be_cacheable do
5
+ match do |url|
6
+ response = RestClient::Request.responsify url
7
+ x_check_cacheable(response, 'YES')
8
+ response.code == 200
9
+ end
10
+ end
11
+
12
+ module RSpec::Matchers
13
+ alias_method :be_cachable, :be_cacheable
14
+ end
15
+
16
+ RSpec::Matchers.define :have_no_cache_set do
17
+ match do |url|
18
+ response = RestClient::Request.responsify url
19
+ cache_control = response.headers[:cache_control]
20
+ fail('Cache-Control has been set') unless cache_control == 'no-cache'
21
+ true
22
+ end
23
+ end
24
+
25
+ RSpec::Matchers.define :not_be_cached do
26
+ match do |url|
27
+ response = RestClient::Request.responsify url
28
+ x_check_cacheable(response, 'NO')
29
+ response = RestClient::Request.responsify response.args[:url] # again to prevent spurious cache miss
30
+
31
+ not_cached = response.headers[:x_cache] =~ /TCP(\w+)?_MISS/
32
+ unless not_cached
33
+ fail("x_cache header does not indicate an origin hit: '#{response.headers[:x_cache]}'")
34
+ end
35
+ response.code == 200 && not_cached
36
+ end
37
+ end
38
+
39
+ RSpec::Matchers.define :be_tier_distributed do
40
+ match do |url|
41
+ response = RestClient::Request.request_cache_miss(url)
42
+ tiered = !response.headers[:x_cache_remote].nil?
43
+ fail('No X-Cache-Remote header in response') unless tiered
44
+ response.code == 200 && tiered
45
+ end
46
+ end
47
+
48
+ def x_check_cacheable(response, should_be_cacheable)
49
+ x_check_cacheable = response.headers[:x_check_cacheable]
50
+ fail('No X-Check-Cacheable header?') if x_check_cacheable.nil?
51
+ unless (x_check_cacheable == should_be_cacheable)
52
+ fail("X-Check-Cacheable header is: #{x_check_cacheable} expected #{should_be_cacheable}")
53
+ end
54
+ end
@@ -0,0 +1,124 @@
1
+ require 'rspec'
2
+ require 'set'
3
+ require 'time'
4
+ require 'uri'
5
+
6
+ RSpec::Matchers.define :honour_origin_cache_headers do |origin, headers|
7
+ header_options = [:cache_control, :expires, :both]
8
+ headers ||= :both
9
+ fail("Headers must be one of: #{header_options}") unless header_options.include? headers
10
+
11
+ match do |url|
12
+ akamai_response = RestClient::Request.responsify url
13
+ origin_response = origin_response(origin)
14
+ check_cache_control(origin_response, akamai_response, headers)
15
+ check_expires(origin_response, akamai_response, headers)
16
+ true
17
+ end
18
+ end
19
+
20
+ def fix_date_header(origin_response)
21
+ origin_response.headers[:date] = Time.now.httpdate unless origin_response.headers[:date]
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
@@ -0,0 +1,40 @@
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
+ include AkamaiHeaders
8
+
9
+ RSpec::Matchers.define :x_cache_key_contains do |contents|
10
+ match do |url|
11
+ response = RestClient::Request.responsify url
12
+ fail 'No X-Cache-Key header' if response.headers[:x_cache_key].nil?
13
+ unless response.headers[:x_cache_key].include?(contents)
14
+ fail("x_cache_key has value '#{response.headers[:x_cache_key]}' which doesn't include '#{contents}'")
15
+ end
16
+ response.code == 200 && response.headers[:x_cache_key].include?(contents)
17
+ end
18
+ end
19
+
20
+ module RSpec::Matchers
21
+ alias_method :be_served_from_origin, :x_cache_key_contains
22
+ alias_method :have_cp_code, :x_cache_key_contains
23
+ end
24
+
25
+ RSpec::Matchers.define :be_forwarded_to_index do |channel|
26
+ match do |url|
27
+ response = RestClient.get(url, akamai_debug_headers)
28
+
29
+ session_info = response.raw_headers['x-akamai-session-info']
30
+ if session_info.nil?
31
+ fail("x-akamai-session-info not found in the headers '#{response.raw_headers}'")
32
+ end
33
+ outcome_attribute = session_info.find { |header| header.include? 'AKA_PM_FWD_URL' }
34
+ if outcome_attribute.nil?
35
+ fail("AKA_PM_FWD_URL not found in the x-akamai-session-info header '#{session_info}'")
36
+ end
37
+ outcome_url = outcome_attribute.split('value=')[1]
38
+ response.code == 200 && outcome_url == "#{channel}"
39
+ end
40
+ end
@@ -0,0 +1,67 @@
1
+ require 'rspec'
2
+ require 'socket'
3
+ require 'openssl'
4
+
5
+ def check_ssl_serial(addr, port, url, serial)
6
+ cert_serial = ssl_cert(addr, port, url).serial.to_s(16).upcase
7
+ fail("Incorrect S/N of: #{cert_serial}") unless cert_serial == serial.upcase
8
+ end
9
+
10
+ def ssl_cert(addr, port, url)
11
+ ssl_client = ssl_client_for_verify_cert(TCPSocket.new(addr, port), addr, url)
12
+ # We get this after the request as we have layer 7 routing in Akamai
13
+ cert = OpenSSL::X509::Certificate.new(ssl_client.peer_cert)
14
+ ssl_client.sysclose
15
+ cert
16
+ end
17
+
18
+ def dummy_request(url, addr)
19
+ "GET #{url} HTTP/1.1\r\n" \
20
+ 'User-Agent: Akamai-Regression-Framework\r\n' \
21
+ "Host: #{addr}\r\n" \
22
+ 'Accept: */*\r\n'
23
+ end
24
+
25
+ def ssl_client_for_verify_cert(tcp_client, addr, url)
26
+ ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client)
27
+ ssl_client.sync_close = true
28
+ ssl_client.connect
29
+ ssl_client.puts(dummy_request(url, addr))
30
+ ssl_client
31
+ end
32
+
33
+ RSpec::Matchers.define :be_successful do
34
+ match do |url|
35
+ response = RestClient::Request.responsify url
36
+ fail('Response was not successful') unless response.code == 200
37
+ true
38
+ end
39
+ end
40
+
41
+ RSpec::Matchers.define :be_verifiably_secure do (verify = OpenSSL::SSL::VERIFY_PEER)
42
+ match do |url|
43
+ begin
44
+ RestClient::Request.execute(method: :get, url: url, verify_ssl: verify)
45
+ true
46
+ rescue => e
47
+ raise("#{url} could not be verified as secure, :sad_panda: #{e.message}")
48
+ end
49
+ end
50
+ end
51
+
52
+ RSpec::Matchers.define :be_gzipped do
53
+ match do |response_or_url|
54
+ response = RestClient::Request.responsify response_or_url
55
+ response.headers[:content_encoding] == 'gzip'
56
+ end
57
+ end
58
+
59
+ RSpec::Matchers.define :have_cookie do |cookie|
60
+ match do |response_or_url|
61
+ response = RestClient::Request.responsify response_or_url
62
+ unless response.cookies[cookie]
63
+ fail("Cookie #{cookie} not in #{response.cookies}")
64
+ end
65
+ response.cookies[cookie]
66
+ end
67
+ end
@@ -0,0 +1,28 @@
1
+ require 'rspec'
2
+
3
+ RSpec::Matchers.define :be_permanently_redirected_to do |expected_location|
4
+ match do |url|
5
+ redirect(url, expected_location, 301)
6
+ end
7
+ end
8
+
9
+ RSpec::Matchers.define :be_temporarily_redirected_to do |expected_location|
10
+ match do |url|
11
+ redirect(url, expected_location, 302)
12
+ end
13
+ end
14
+
15
+ RSpec::Matchers.define :be_temporarily_redirected_with_trailing_slash do
16
+ match do |url|
17
+ redirect(url, url + '/', 302)
18
+ end
19
+ end
20
+
21
+ def redirect(url, expected_location, expected_response_code)
22
+ response = RestClient.get(url) { |response, _, _| response }
23
+ fail "response was #{response.code}" unless response.code == expected_response_code
24
+ unless response.headers[:location] == expected_location
25
+ fail "redirect location was #{response.headers[:location]} (expected #{expected_location})"
26
+ end
27
+ true
28
+ end
@@ -0,0 +1,89 @@
1
+ require 'rest-client'
2
+ require 'akamai_rspec'
3
+
4
+ module RestClient
5
+ class Request
6
+ @@akamai_network = 'prod'
7
+ @@akamai_stg_domain = 'overwrite me'
8
+ @@akamai_prod_domain = 'overwrite me'
9
+
10
+ def self.domain
11
+ env = @@akamai_network
12
+ case env.downcase
13
+ when 'staging'
14
+ @@akamai_stg_domain
15
+ else
16
+ @@akamai_prod_domain
17
+ end
18
+ end
19
+
20
+ def self.akamai_network(env)
21
+ @@akamai_network = env
22
+ end
23
+
24
+ def self.stg_domain(domain)
25
+ @@akamai_stg_domain = domain
26
+ end
27
+
28
+ def self.prod_domain(domain)
29
+ @@akamai_prod_domain = domain
30
+ end
31
+
32
+ def self.http_url(url)
33
+ url = "/#{url}" unless url.start_with?('/')
34
+ "http://#{domain}#{url}"
35
+ end
36
+
37
+ def self.https_url(url)
38
+ url = "/#{url}" unless url.start_with?('/')
39
+ "https://#{domain}#{url}"
40
+ end
41
+
42
+ # Define the Host header and join the Akamai headers
43
+ def self.options
44
+ akamai_debug_headers
45
+ end
46
+
47
+ # Make requests to the right network
48
+ def self.http_get(url, options, cookies = {})
49
+ do_get(url, options, cookies, false)
50
+ end
51
+
52
+ def self.https_get(url, options, cookies = {})
53
+ do_get(url, options, cookies, true)
54
+ end
55
+
56
+ def self.do_get(url, options, cookies = {}, is_secure)
57
+ if is_secure
58
+ base_url = https_url(url)
59
+ else
60
+ base_url = http_url(url)
61
+ end
62
+ headers = options.merge(akamai_debug_headers).merge(cookies)
63
+ do_get_no_ssl(base_url, headers) { |response, _, _| response }
64
+ end
65
+
66
+ def self.do_get_no_ssl(url, additional_headers = {}, &block)
67
+ headers = (options[:headers] || {}).merge(additional_headers)
68
+ RestClient::Request.execute(options.merge(
69
+ method: :get,
70
+ url: url,
71
+ verify_ssl: false,
72
+ headers: headers), &(block || @block))
73
+ end
74
+
75
+ def self.responsify(maybe_a_url)
76
+ if maybe_a_url.is_a? RestClient::Response
77
+ maybe_a_url
78
+ else
79
+ RestClient.get(maybe_a_url, akamai_debug_headers)
80
+ end
81
+ end
82
+
83
+ def self.request_cache_miss(url)
84
+ url += url.include?('?') ? '&' : '?'
85
+ url += SecureRandom.hex
86
+ RestClient.get(url, akamai_debug_headers)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ require 'akamai_rspec/akamai_headers'
2
+ require 'akamai_rspec/matchers/matchers'
3
+ require 'akamai_rspec/request'
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: akamai_rspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Bianca Gibson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ description:
56
+ email: bianca.gibson@rea-group.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - lib/akamai_rspec.rb
62
+ - lib/akamai_rspec/akamai_headers.rb
63
+ - lib/akamai_rspec/matchers/caching.rb
64
+ - lib/akamai_rspec/matchers/honour_origin_headers.rb
65
+ - lib/akamai_rspec/matchers/matchers.rb
66
+ - lib/akamai_rspec/matchers/non_akamai.rb
67
+ - lib/akamai_rspec/matchers/redirects.rb
68
+ - lib/akamai_rspec/request.rb
69
+ homepage:
70
+ licenses: []
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.1.11
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Test your akamai configuration with rspec
92
+ test_files: []