xenon 0.0.2 → 0.0.3
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/.codeclimate.yml +18 -0
 - data/.gitignore +2 -0
 - data/.travis.yml +6 -0
 - data/Gemfile +10 -0
 - data/Guardfile +0 -32
 - data/README.md +59 -5
 - data/examples/hello_world/config.ru +3 -0
 - data/examples/hello_world/hello_world.rb +17 -0
 - data/lib/xenon.rb +62 -49
 - data/lib/xenon/auth.rb +63 -0
 - data/lib/xenon/errors.rb +1 -0
 - data/lib/xenon/etag.rb +48 -0
 - data/lib/xenon/headers.rb +2 -4
 - data/lib/xenon/headers/accept.rb +3 -2
 - data/lib/xenon/headers/accept_charset.rb +5 -5
 - data/lib/xenon/headers/accept_encoding.rb +5 -5
 - data/lib/xenon/headers/accept_language.rb +5 -5
 - data/lib/xenon/headers/authorization.rb +7 -53
 - data/lib/xenon/headers/cache_control.rb +3 -3
 - data/lib/xenon/headers/content_type.rb +1 -1
 - data/lib/xenon/headers/if_match.rb +53 -0
 - data/lib/xenon/headers/if_modified_since.rb +22 -0
 - data/lib/xenon/headers/if_none_match.rb +53 -0
 - data/lib/xenon/headers/if_range.rb +45 -0
 - data/lib/xenon/headers/if_unmodified_since.rb +22 -0
 - data/lib/xenon/headers/user_agent.rb +65 -0
 - data/lib/xenon/headers/www_authenticate.rb +70 -0
 - data/lib/xenon/media_type.rb +24 -2
 - data/lib/xenon/parsers/basic_rules.rb +38 -7
 - data/lib/xenon/parsers/header_rules.rb +49 -3
 - data/lib/xenon/parsers/media_type.rb +4 -3
 - data/lib/xenon/quoted_string.rb +11 -1
 - data/lib/xenon/routing/directives.rb +14 -0
 - data/lib/xenon/routing/header_directives.rb +32 -0
 - data/lib/xenon/routing/method_directives.rb +26 -0
 - data/lib/xenon/routing/param_directives.rb +22 -0
 - data/lib/xenon/routing/path_directives.rb +37 -0
 - data/lib/xenon/routing/route_directives.rb +51 -0
 - data/lib/xenon/routing/security_directives.rb +20 -0
 - data/lib/xenon/version.rb +1 -1
 - data/spec/spec_helper.rb +3 -0
 - data/spec/xenon/etag_spec.rb +19 -0
 - data/spec/xenon/headers/if_match_spec.rb +73 -0
 - data/spec/xenon/headers/if_modified_since_spec.rb +19 -0
 - data/spec/xenon/headers/if_none_match_spec.rb +79 -0
 - data/spec/xenon/headers/if_range_spec.rb +45 -0
 - data/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
 - data/spec/xenon/headers/user_agent_spec.rb +67 -0
 - data/spec/xenon/headers/www_authenticate_spec.rb +43 -0
 - data/xenon.gemspec +4 -3
 - metadata +60 -10
 - data/lib/xenon/routing.rb +0 -133
 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/routing/route_directives'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Xenon
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Routing
         
     | 
| 
      
 5 
     | 
    
         
            +
                module MethodDirectives
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include RouteDirectives
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def request_method(method)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    extract_request do |request|
         
     | 
| 
      
 10 
     | 
    
         
            +
                      if request.request_method == method
         
     | 
| 
      
 11 
     | 
    
         
            +
                        yield
         
     | 
| 
      
 12 
     | 
    
         
            +
                      else
         
     | 
| 
      
 13 
     | 
    
         
            +
                        reject Rejection.new(:method, { supported: method })
         
     | 
| 
      
 14 
     | 
    
         
            +
                      end
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  %i(delete get head options patch post put).each do |method|
         
     | 
| 
      
 19 
     | 
    
         
            +
                    define_method(method) do |&inner|
         
     | 
| 
      
 20 
     | 
    
         
            +
                      request_method(method, &inner)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/routing/route_directives'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Xenon
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Routing
         
     | 
| 
      
 5 
     | 
    
         
            +
                module ParamDirectives
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include RouteDirectives
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def param_hash
         
     | 
| 
      
 9 
     | 
    
         
            +
                    extract_request do |request|
         
     | 
| 
      
 10 
     | 
    
         
            +
                      yield request.param_hash
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def params(*names)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    param_hash do |hash|
         
     | 
| 
      
 16 
     | 
    
         
            +
                      yield *hash.slice(*names).values
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/routing/route_directives'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Xenon
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Routing
         
     | 
| 
      
 5 
     | 
    
         
            +
                module PathDirectives
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include RouteDirectives
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def path_prefix(pattern)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    extract_request do |request|
         
     | 
| 
      
 10 
     | 
    
         
            +
                      match = request.unmatched_path.match(pattern)
         
     | 
| 
      
 11 
     | 
    
         
            +
                      if match && match.pre_match == ''
         
     | 
| 
      
 12 
     | 
    
         
            +
                        map_request unmatched_path: match.post_match do
         
     | 
| 
      
 13 
     | 
    
         
            +
                          yield *match.captures
         
     | 
| 
      
 14 
     | 
    
         
            +
                        end
         
     | 
| 
      
 15 
     | 
    
         
            +
                      else
         
     | 
| 
      
 16 
     | 
    
         
            +
                        reject nil # path rejections are nil to allow more specific rejections to be seen
         
     | 
| 
      
 17 
     | 
    
         
            +
                      end
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def path_end
         
     | 
| 
      
 22 
     | 
    
         
            +
                    path_prefix(/\Z/) do
         
     | 
| 
      
 23 
     | 
    
         
            +
                      yield
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def path(pattern)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    path_prefix(pattern) do |*captures|
         
     | 
| 
      
 29 
     | 
    
         
            +
                      path_end do
         
     | 
| 
      
 30 
     | 
    
         
            +
                        yield *captures
         
     | 
| 
      
 31 
     | 
    
         
            +
                      end
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rack/utils'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Xenon
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Routing
         
     | 
| 
      
 5 
     | 
    
         
            +
                module RouteDirectives
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def map_request(map)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    context.branch do
         
     | 
| 
      
 9 
     | 
    
         
            +
                      context.request = map.respond_to?(:call) ? map.call(context.request) : context.request.copy(map)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      yield
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def map_response(map)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    context.branch do
         
     | 
| 
      
 16 
     | 
    
         
            +
                      context.response = map.respond_to?(:call) ? map.call(context.response) : context.response.copy(map)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      yield
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def complete(status, body)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    map_response complete: true, status: Rack::Utils.status_code(status), body: body do
         
     | 
| 
      
 23 
     | 
    
         
            +
                      throw :complete
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def reject(rejection, info = {})
         
     | 
| 
      
 28 
     | 
    
         
            +
                    return if rejection.nil?
         
     | 
| 
      
 29 
     | 
    
         
            +
                    rejection = Rejection.new(rejection, info) unless rejection.is_a?(Rejection)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    context.rejections << rejection
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def fail(status, developer_message = nil)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    body = {
         
     | 
| 
      
 35 
     | 
    
         
            +
                      status: status,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      developer_message: developer_message || Rack::Utils::HTTP_STATUS_CODES[status]
         
     | 
| 
      
 37 
     | 
    
         
            +
                    }
         
     | 
| 
      
 38 
     | 
    
         
            +
                    complete status, body
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def extract(lambda)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    yield lambda.call(context)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def extract_request(lambda = nil)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    yield lambda ? lambda.call(context.request) : context.request
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/routing/route_directives'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Xenon
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Routing
         
     | 
| 
      
 5 
     | 
    
         
            +
                module SecurityDirectives
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include RouteDirectives
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def authenticate(authenticator)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    extract_request(authenticator) do |user|
         
     | 
| 
      
 10 
     | 
    
         
            +
                      if user
         
     | 
| 
      
 11 
     | 
    
         
            +
                        yield user
         
     | 
| 
      
 12 
     | 
    
         
            +
                      else
         
     | 
| 
      
 13 
     | 
    
         
            +
                        reject :unauthorized, { scheme: authenticator.scheme }.merge(authenticator.auth_params)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      end
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/xenon/version.rb
    CHANGED
    
    
    
        data/spec/spec_helper.rb
    CHANGED
    
    | 
         @@ -1,3 +1,6 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "codeclimate-test-reporter"
         
     | 
| 
      
 2 
     | 
    
         
            +
            CodeClimate::TestReporter.start
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
       1 
4 
     | 
    
         
             
            # This file was generated by the `rspec --init` command. Conventionally, all
         
     | 
| 
       2 
5 
     | 
    
         
             
            # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
         
     | 
| 
       3 
6 
     | 
    
         
             
            # The generated `.rspec` file contains `--require spec_helper` which will cause
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/etag'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Xenon::ETag do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              describe '::parse' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                it 'can parse a strong etag' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  etag = Xenon::ETag.parse('"xyzzy"')
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(etag.opaque_tag).to eq 'xyzzy'
         
     | 
| 
      
 9 
     | 
    
         
            +
                  expect(etag).to be_strong
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                it 'can parse a weak etag' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                  etag = Xenon::ETag.parse('W/"xyzzy"')
         
     | 
| 
      
 14 
     | 
    
         
            +
                  expect(etag.opaque_tag).to eq 'xyzzy'
         
     | 
| 
      
 15 
     | 
    
         
            +
                  expect(etag).to be_weak
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,73 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/headers/if_match'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Xenon::Headers::IfMatch do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              context '::parse' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                it 'can parse a single etag' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  header = Xenon::Headers::IfMatch.parse('"xyzzy"')
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(header.etags.size).to eq(1)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                it 'can parse multiple etags' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                  header = Xenon::Headers::IfMatch.parse('"xyzzy", "r2d2xxxx", "c3piozzzz"')
         
     | 
| 
      
 14 
     | 
    
         
            +
                  expect(header.etags.size).to eq(3)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
         
     | 
| 
      
 16 
     | 
    
         
            +
                  expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx'))
         
     | 
| 
      
 17 
     | 
    
         
            +
                  expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                it 'can parse a wildcard header' do
         
     | 
| 
      
 21 
     | 
    
         
            +
                  header = Xenon::Headers::IfMatch.parse('*')
         
     | 
| 
      
 22 
     | 
    
         
            +
                  expect(header.etags.size).to eq(0)
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              context '#merge' do
         
     | 
| 
      
 27 
     | 
    
         
            +
                it 'can merge two headers and maintain etag order' do
         
     | 
| 
      
 28 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfMatch.parse('"xyzzy", "r2d2xxxx"')
         
     | 
| 
      
 29 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfMatch.parse('"c3piozzzz"')
         
     | 
| 
      
 30 
     | 
    
         
            +
                  header = h1.merge(h2)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  expect(header.etags.size).to eq(3)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
         
     | 
| 
      
 33 
     | 
    
         
            +
                  expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx'))
         
     | 
| 
      
 34 
     | 
    
         
            +
                  expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                it 'raises a protocol error when trying to merge into a wildcard header' do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfMatch.parse('*')
         
     | 
| 
      
 39 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfMatch.parse('"c3piozzzz"')
         
     | 
| 
      
 40 
     | 
    
         
            +
                  expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                it 'raises a protocol error when trying to merge a wildcard into a header' do
         
     | 
| 
      
 44 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfMatch.parse('"xyzzy"')
         
     | 
| 
      
 45 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfMatch.parse('*')
         
     | 
| 
      
 46 
     | 
    
         
            +
                  expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                it 'raises a protocol error when trying to merge two wildcard headers' do
         
     | 
| 
      
 50 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfMatch.parse('*')
         
     | 
| 
      
 51 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfMatch.parse('*')
         
     | 
| 
      
 52 
     | 
    
         
            +
                  expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              context '#to_s' do
         
     | 
| 
      
 57 
     | 
    
         
            +
                it 'returns the string representation a single etag' do
         
     | 
| 
      
 58 
     | 
    
         
            +
                  header = Xenon::Headers::IfMatch.parse('"xyzzy"')
         
     | 
| 
      
 59 
     | 
    
         
            +
                  expect(header.to_s).to eq('"xyzzy"')
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                it 'returns the string representation of multiple etags' do
         
     | 
| 
      
 63 
     | 
    
         
            +
                  header = Xenon::Headers::IfMatch.parse('"xyzzy", "r2d2xxxx", "c3piozzzz"')
         
     | 
| 
      
 64 
     | 
    
         
            +
                  expect(header.to_s).to eq('"xyzzy", "r2d2xxxx", "c3piozzzz"')
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                it 'returns the string representation of a wildcard header' do
         
     | 
| 
      
 68 
     | 
    
         
            +
                  header = Xenon::Headers::IfMatch.wildcard
         
     | 
| 
      
 69 
     | 
    
         
            +
                  expect(header.to_s).to eq('*')
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/headers/if_modified_since'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Xenon::Headers::IfModifiedSince do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              context '::parse' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                it 'can parse an http date' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  header = Xenon::Headers::IfModifiedSince.parse('Sat, 29 Oct 1994 19:43:31 GMT')
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(header.date).to eq(Time.utc(1994, 10, 29, 19, 43, 31))
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              context '#to_s' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                it 'returns the http date format' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  header = Xenon::Headers::IfModifiedSince.new(Time.utc(1994, 10, 29, 19, 43, 31))
         
     | 
| 
      
 15 
     | 
    
         
            +
                  expect(header.to_s).to eq('Sat, 29 Oct 1994 19:43:31 GMT')
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/headers/if_none_match'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Xenon::Headers::IfNoneMatch do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              context '::parse' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                it 'can parse a single strong etag' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  header = Xenon::Headers::IfNoneMatch.parse('"xyzzy"')
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(header.etags.size).to eq(1)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                it 'can parse a single weak etag' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                  header = Xenon::Headers::IfNoneMatch.parse('W/"xyzzy"')
         
     | 
| 
      
 14 
     | 
    
         
            +
                  expect(header.etags.size).to eq(1)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy', weak: true))
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                it 'can parse multiple etags' do
         
     | 
| 
      
 19 
     | 
    
         
            +
                  header = Xenon::Headers::IfNoneMatch.parse('"xyzzy", W/"r2d2xxxx", "c3piozzzz"')
         
     | 
| 
      
 20 
     | 
    
         
            +
                  expect(header.etags.size).to eq(3)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
         
     | 
| 
      
 22 
     | 
    
         
            +
                  expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx', weak: true))
         
     | 
| 
      
 23 
     | 
    
         
            +
                  expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                it 'can parse a wildcard header' do
         
     | 
| 
      
 27 
     | 
    
         
            +
                  header = Xenon::Headers::IfNoneMatch.parse('*')
         
     | 
| 
      
 28 
     | 
    
         
            +
                  expect(header.etags.size).to eq(0)
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              context '#merge' do
         
     | 
| 
      
 33 
     | 
    
         
            +
                it 'can merge two headers and maintain etag order' do
         
     | 
| 
      
 34 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfNoneMatch.parse('"xyzzy", W/"r2d2xxxx"')
         
     | 
| 
      
 35 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfNoneMatch.parse('"c3piozzzz"')
         
     | 
| 
      
 36 
     | 
    
         
            +
                  header = h1.merge(h2)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  expect(header.etags.size).to eq(3)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  expect(header.etags[0]).to eq(Xenon::ETag.new('xyzzy'))
         
     | 
| 
      
 39 
     | 
    
         
            +
                  expect(header.etags[1]).to eq(Xenon::ETag.new('r2d2xxxx', weak: true))
         
     | 
| 
      
 40 
     | 
    
         
            +
                  expect(header.etags[2]).to eq(Xenon::ETag.new('c3piozzzz'))
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                it 'raises a protocol error when trying to merge into a wildcard header' do
         
     | 
| 
      
 44 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfNoneMatch.parse('*')
         
     | 
| 
      
 45 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfNoneMatch.parse('"c3piozzzz"')
         
     | 
| 
      
 46 
     | 
    
         
            +
                  expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                it 'raises a protocol error when trying to merge a wildcard into a header' do
         
     | 
| 
      
 50 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfNoneMatch.parse('"xyzzy"')
         
     | 
| 
      
 51 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfNoneMatch.parse('*')
         
     | 
| 
      
 52 
     | 
    
         
            +
                  expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                it 'raises a protocol error when trying to merge two wildcard headers' do
         
     | 
| 
      
 56 
     | 
    
         
            +
                  h1 = Xenon::Headers::IfNoneMatch.parse('*')
         
     | 
| 
      
 57 
     | 
    
         
            +
                  h2 = Xenon::Headers::IfNoneMatch.parse('*')
         
     | 
| 
      
 58 
     | 
    
         
            +
                  expect { h1.merge(h2) }.to raise_error(Xenon::ProtocolError)
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              context '#to_s' do
         
     | 
| 
      
 63 
     | 
    
         
            +
                it 'returns the string representation a single etag' do
         
     | 
| 
      
 64 
     | 
    
         
            +
                  header = Xenon::Headers::IfNoneMatch.parse('"xyzzy"')
         
     | 
| 
      
 65 
     | 
    
         
            +
                  expect(header.to_s).to eq('"xyzzy"')
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                it 'returns the string representation of multiple etags' do
         
     | 
| 
      
 69 
     | 
    
         
            +
                  header = Xenon::Headers::IfNoneMatch.parse('"xyzzy", W/"r2d2xxxx", "c3piozzzz"')
         
     | 
| 
      
 70 
     | 
    
         
            +
                  expect(header.to_s).to eq('"xyzzy", W/"r2d2xxxx", "c3piozzzz"')
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                it 'returns the string representation of a wildcard header' do
         
     | 
| 
      
 74 
     | 
    
         
            +
                  header = Xenon::Headers::IfNoneMatch.wildcard
         
     | 
| 
      
 75 
     | 
    
         
            +
                  expect(header.to_s).to eq('*')
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/headers/if_range'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Xenon::Headers::IfRange do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              context '::parse' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                it 'can parse an http date' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  header = Xenon::Headers::IfRange.parse('Sat, 29 Oct 1994 19:43:31 GMT')
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(header.date).to eq(Time.utc(1994, 10, 29, 19, 43, 31))
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                it 'can parse an obsolete RFC 850 date' do
         
     | 
| 
      
 12 
     | 
    
         
            +
                  header = Xenon::Headers::IfRange.parse('Sunday, 06-Nov-94 08:49:37 GMT')
         
     | 
| 
      
 13 
     | 
    
         
            +
                  expect(header.date).to eq(Time.utc(1994, 11, 6, 8, 49, 37))
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                it 'can parse an obsolete asctime date' do
         
     | 
| 
      
 17 
     | 
    
         
            +
                  header = Xenon::Headers::IfRange.parse('Sun Nov  6 08:49:37 1994')
         
     | 
| 
      
 18 
     | 
    
         
            +
                  expect(header.date).to eq(Time.utc(1994, 11, 6, 8, 49, 37))
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                it 'can parse a strong etag' do
         
     | 
| 
      
 22 
     | 
    
         
            +
                  header = Xenon::Headers::IfRange.parse('"xyzzy"')
         
     | 
| 
      
 23 
     | 
    
         
            +
                  expect(header.etag).to_not be_nil
         
     | 
| 
      
 24 
     | 
    
         
            +
                  expect(header.etag.opaque_tag).to eq 'xyzzy'
         
     | 
| 
      
 25 
     | 
    
         
            +
                  expect(header.etag).to be_strong
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                it 'should raise a ProtocolError if the etag is weak' do
         
     | 
| 
      
 29 
     | 
    
         
            +
                  expect { Xenon::Headers::IfRange.parse('W/"xyzzy"') }.to raise_error Xenon::ProtocolError
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              context '#to_s' do
         
     | 
| 
      
 34 
     | 
    
         
            +
                it 'returns the http date format for dates' do
         
     | 
| 
      
 35 
     | 
    
         
            +
                  header = Xenon::Headers::IfRange.new(Time.utc(1994, 10, 29, 19, 43, 31))
         
     | 
| 
      
 36 
     | 
    
         
            +
                  expect(header.to_s).to eq('Sat, 29 Oct 1994 19:43:31 GMT')
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                it 'returns the etag format for etags' do
         
     | 
| 
      
 40 
     | 
    
         
            +
                  header = Xenon::Headers::IfRange.new(Xenon::ETag.new('xyzzy'))
         
     | 
| 
      
 41 
     | 
    
         
            +
                  expect(header.to_s).to eq('"xyzzy"')
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/headers/if_unmodified_since'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Xenon::Headers::IfUnmodifiedSince do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              context '::parse' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                it 'can parse an http date' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  header = Xenon::Headers::IfUnmodifiedSince.parse('Sat, 29 Oct 1994 19:43:31 GMT')
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(header.date).to eq(Time.utc(1994, 10, 29, 19, 43, 31))
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              context '#to_s' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                it 'returns the http date format' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  header = Xenon::Headers::IfUnmodifiedSince.new(Time.utc(1994, 10, 29, 19, 43, 31))
         
     | 
| 
      
 15 
     | 
    
         
            +
                  expect(header.to_s).to eq('Sat, 29 Oct 1994 19:43:31 GMT')
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'xenon/headers/user_agent'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Xenon::Headers::UserAgent do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              context '::parse' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                it 'can parse a user agent with a product name' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                  header = Xenon::Headers::UserAgent.parse('Mozilla')
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(header.products.size).to eq(1)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  expect(header.products[0].name).to eq('Mozilla')
         
     | 
| 
      
 10 
     | 
    
         
            +
                  expect(header.products[0].version).to be_nil
         
     | 
| 
      
 11 
     | 
    
         
            +
                  expect(header.products[0].comment).to be_nil
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                it 'can parse a user agent with a product name and version' do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  header = Xenon::Headers::UserAgent.parse('Mozilla/5.0')
         
     | 
| 
      
 16 
     | 
    
         
            +
                  expect(header.products.size).to eq(1)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  expect(header.products[0].name).to eq('Mozilla')
         
     | 
| 
      
 18 
     | 
    
         
            +
                  expect(header.products[0].version).to eq('5.0')
         
     | 
| 
      
 19 
     | 
    
         
            +
                  expect(header.products[0].comment).to be_nil
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                it 'can parse a user agent with a product name and comment' do
         
     | 
| 
      
 23 
     | 
    
         
            +
                  header = Xenon::Headers::UserAgent.parse('Mozilla (Macintosh; Intel Mac OS X 10_10_2)')
         
     | 
| 
      
 24 
     | 
    
         
            +
                  expect(header.products.size).to eq(1)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  expect(header.products[0].name).to eq('Mozilla')
         
     | 
| 
      
 26 
     | 
    
         
            +
                  expect(header.products[0].version).to be_nil
         
     | 
| 
      
 27 
     | 
    
         
            +
                  expect(header.products[0].comment).to eq('Macintosh; Intel Mac OS X 10_10_2')
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                it 'can parse a user agent with a product name, version and comment' do
         
     | 
| 
      
 31 
     | 
    
         
            +
                  header = Xenon::Headers::UserAgent.parse('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2)')
         
     | 
| 
      
 32 
     | 
    
         
            +
                  expect(header.products.size).to eq(1)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  expect(header.products[0].name).to eq('Mozilla')
         
     | 
| 
      
 34 
     | 
    
         
            +
                  expect(header.products[0].version).to eq('5.0')
         
     | 
| 
      
 35 
     | 
    
         
            +
                  expect(header.products[0].comment).to eq('Macintosh; Intel Mac OS X 10_10_2')
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                it 'can parse a user agent with multiple comments' do
         
     | 
| 
      
 39 
     | 
    
         
            +
                  header = Xenon::Headers::UserAgent.parse('Mozilla/5.0 (Macintosh) (Intel Mac OS X 10_10_2)')
         
     | 
| 
      
 40 
     | 
    
         
            +
                  expect(header.products.size).to eq(2)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  expect(header.products[0].name).to eq('Mozilla')
         
     | 
| 
      
 42 
     | 
    
         
            +
                  expect(header.products[0].version).to eq('5.0')
         
     | 
| 
      
 43 
     | 
    
         
            +
                  expect(header.products[0].comment).to eq('Macintosh')
         
     | 
| 
      
 44 
     | 
    
         
            +
                  expect(header.products[1].name).to be_nil
         
     | 
| 
      
 45 
     | 
    
         
            +
                  expect(header.products[1].version).to be_nil
         
     | 
| 
      
 46 
     | 
    
         
            +
                  expect(header.products[1].comment).to eq('Intel Mac OS X 10_10_2')
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                it 'can parse a typical Chrome user agent' do
         
     | 
| 
      
 50 
     | 
    
         
            +
                  header = Xenon::Headers::UserAgent.parse('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36')
         
     | 
| 
      
 51 
     | 
    
         
            +
                  expect(header.products.size).to eq(4)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  expect(header.products[0].name).to eq('Mozilla')
         
     | 
| 
      
 53 
     | 
    
         
            +
                  expect(header.products[0].version).to eq('5.0')
         
     | 
| 
      
 54 
     | 
    
         
            +
                  expect(header.products[0].comment).to eq('Macintosh; Intel Mac OS X 10_10_2')
         
     | 
| 
      
 55 
     | 
    
         
            +
                  expect(header.products[1].name).to eq('AppleWebKit')
         
     | 
| 
      
 56 
     | 
    
         
            +
                  expect(header.products[1].version).to eq('537.36')
         
     | 
| 
      
 57 
     | 
    
         
            +
                  expect(header.products[1].comment).to eq('KHTML, like Gecko')
         
     | 
| 
      
 58 
     | 
    
         
            +
                  expect(header.products[2].name).to eq('Chrome')
         
     | 
| 
      
 59 
     | 
    
         
            +
                  expect(header.products[2].version).to eq('41.0.2272.89')
         
     | 
| 
      
 60 
     | 
    
         
            +
                  expect(header.products[2].comment).to be_nil
         
     | 
| 
      
 61 
     | 
    
         
            +
                  expect(header.products[3].name).to eq('Safari')
         
     | 
| 
      
 62 
     | 
    
         
            +
                  expect(header.products[3].version).to eq('537.36')
         
     | 
| 
      
 63 
     | 
    
         
            +
                  expect(header.products[3].comment).to be_nil
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     |