akamai_rspec 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: []