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
|