xenon-routing 0.0.4

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +18 -0
  3. data/.gitignore +25 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +20 -0
  7. data/Guardfile +16 -0
  8. data/LICENSE +22 -0
  9. data/README.md +116 -0
  10. data/Rakefile +40 -0
  11. data/VERSION +1 -0
  12. data/examples/hello_world/config.ru +3 -0
  13. data/examples/hello_world/hello_world.rb +27 -0
  14. data/xenon-http/lib/xenon/auth.rb +63 -0
  15. data/xenon-http/lib/xenon/errors.rb +5 -0
  16. data/xenon-http/lib/xenon/etag.rb +48 -0
  17. data/xenon-http/lib/xenon/headers.rb +112 -0
  18. data/xenon-http/lib/xenon/headers/accept.rb +34 -0
  19. data/xenon-http/lib/xenon/headers/accept_charset.rb +59 -0
  20. data/xenon-http/lib/xenon/headers/accept_encoding.rb +63 -0
  21. data/xenon-http/lib/xenon/headers/accept_language.rb +59 -0
  22. data/xenon-http/lib/xenon/headers/authorization.rb +50 -0
  23. data/xenon-http/lib/xenon/headers/cache_control.rb +56 -0
  24. data/xenon-http/lib/xenon/headers/content_type.rb +23 -0
  25. data/xenon-http/lib/xenon/headers/if_match.rb +53 -0
  26. data/xenon-http/lib/xenon/headers/if_modified_since.rb +22 -0
  27. data/xenon-http/lib/xenon/headers/if_none_match.rb +53 -0
  28. data/xenon-http/lib/xenon/headers/if_range.rb +45 -0
  29. data/xenon-http/lib/xenon/headers/if_unmodified_since.rb +22 -0
  30. data/xenon-http/lib/xenon/headers/user_agent.rb +65 -0
  31. data/xenon-http/lib/xenon/headers/www_authenticate.rb +71 -0
  32. data/xenon-http/lib/xenon/http.rb +7 -0
  33. data/xenon-http/lib/xenon/http_version.rb +3 -0
  34. data/xenon-http/lib/xenon/media_type.rb +162 -0
  35. data/xenon-http/lib/xenon/parsers/basic_rules.rb +86 -0
  36. data/xenon-http/lib/xenon/parsers/header_rules.rb +60 -0
  37. data/xenon-http/lib/xenon/parsers/media_type.rb +53 -0
  38. data/xenon-http/lib/xenon/quoted_string.rb +20 -0
  39. data/xenon-http/spec/spec_helper.rb +94 -0
  40. data/xenon-http/spec/xenon/etag_spec.rb +19 -0
  41. data/xenon-http/spec/xenon/headers/accept_charset_spec.rb +31 -0
  42. data/xenon-http/spec/xenon/headers/accept_encoding_spec.rb +40 -0
  43. data/xenon-http/spec/xenon/headers/accept_language_spec.rb +33 -0
  44. data/xenon-http/spec/xenon/headers/accept_spec.rb +54 -0
  45. data/xenon-http/spec/xenon/headers/authorization_spec.rb +47 -0
  46. data/xenon-http/spec/xenon/headers/cache_control_spec.rb +64 -0
  47. data/xenon-http/spec/xenon/headers/if_match_spec.rb +73 -0
  48. data/xenon-http/spec/xenon/headers/if_modified_since_spec.rb +19 -0
  49. data/xenon-http/spec/xenon/headers/if_none_match_spec.rb +79 -0
  50. data/xenon-http/spec/xenon/headers/if_range_spec.rb +45 -0
  51. data/xenon-http/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
  52. data/xenon-http/spec/xenon/headers/user_agent_spec.rb +67 -0
  53. data/xenon-http/spec/xenon/headers/www_authenticate_spec.rb +43 -0
  54. data/xenon-http/spec/xenon/media_type_spec.rb +267 -0
  55. data/xenon-http/xenon-http.gemspec +25 -0
  56. data/xenon-routing/lib/xenon/api.rb +118 -0
  57. data/xenon-routing/lib/xenon/marshallers.rb +48 -0
  58. data/xenon-routing/lib/xenon/request.rb +40 -0
  59. data/xenon-routing/lib/xenon/response.rb +29 -0
  60. data/xenon-routing/lib/xenon/routing.rb +6 -0
  61. data/xenon-routing/lib/xenon/routing/context.rb +35 -0
  62. data/xenon-routing/lib/xenon/routing/directives.rb +14 -0
  63. data/xenon-routing/lib/xenon/routing/header_directives.rb +32 -0
  64. data/xenon-routing/lib/xenon/routing/method_directives.rb +26 -0
  65. data/xenon-routing/lib/xenon/routing/param_directives.rb +22 -0
  66. data/xenon-routing/lib/xenon/routing/path_directives.rb +37 -0
  67. data/xenon-routing/lib/xenon/routing/route_directives.rb +51 -0
  68. data/xenon-routing/lib/xenon/routing/security_directives.rb +34 -0
  69. data/xenon-routing/lib/xenon/routing_version.rb +3 -0
  70. data/xenon-routing/spec/spec_helper.rb +94 -0
  71. data/xenon-routing/xenon-routing.gemspec +25 -0
  72. data/xenon.gemspec +26 -0
  73. metadata +145 -0
@@ -0,0 +1,54 @@
1
+ require 'xenon/headers/accept'
2
+
3
+ describe Xenon::Headers::Accept do
4
+
5
+ context '::parse' do
6
+ it 'can parse a basic accept range' do
7
+ header = Xenon::Headers::Accept.parse('text/plain; q=0.5')
8
+ expect(header.media_ranges.size).to eq(1)
9
+ expect(header.media_ranges[0].to_s).to eq('text/plain; q=0.5')
10
+ end
11
+
12
+ it 'can parse the first example from RFC 7231 § 5.3.2 with the right precedence' do
13
+ header = Xenon::Headers::Accept.parse('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')
14
+ expect(header.media_ranges.size).to eq(4)
15
+ expect(header.media_ranges[0].to_s).to eq('text/html')
16
+ expect(header.media_ranges[1].to_s).to eq('text/x-c')
17
+ expect(header.media_ranges[2].to_s).to eq('text/x-dvi; q=0.8')
18
+ expect(header.media_ranges[3].to_s).to eq('text/plain; q=0.5')
19
+ end
20
+
21
+ it 'can parse the second example from RFC 7231 § 5.3.2 with the right precedence' do
22
+ header = Xenon::Headers::Accept.parse('text/*, text/plain, text/plain;format=flowed, */*')
23
+ expect(header.media_ranges.size).to eq(4)
24
+ expect(header.media_ranges[0].to_s).to eq('text/plain; format=flowed')
25
+ expect(header.media_ranges[1].to_s).to eq('text/plain')
26
+ expect(header.media_ranges[2].to_s).to eq('text/*')
27
+ expect(header.media_ranges[3].to_s).to eq('*/*')
28
+ end
29
+
30
+ it 'can parse the third example from RFC 7231 § 5.3.2 with the right precedence' do
31
+ header = Xenon::Headers::Accept.parse('text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
32
+ expect(header.media_ranges.size).to eq(5)
33
+ expect(header.media_ranges[0].to_s).to eq('text/html; level=1')
34
+ expect(header.media_ranges[1].to_s).to eq('text/html; level=2; q=0.4')
35
+ expect(header.media_ranges[2].to_s).to eq('text/html; q=0.7')
36
+ expect(header.media_ranges[3].to_s).to eq('text/*; q=0.3')
37
+ expect(header.media_ranges[4].to_s).to eq('*/*; q=0.5')
38
+ end
39
+ end
40
+
41
+ context '#merge' do
42
+ it 'can merge two headers with the right precedence' do
43
+ h1 = Xenon::Headers::Accept.parse('text/plain; q=0.5, text/html')
44
+ h2 = Xenon::Headers::Accept.parse('text/x-c, text/x-dvi; q=0.8')
45
+ header = h1.merge(h2)
46
+ expect(header.media_ranges.size).to eq(4)
47
+ expect(header.media_ranges[0].to_s).to eq('text/html')
48
+ expect(header.media_ranges[1].to_s).to eq('text/x-c')
49
+ expect(header.media_ranges[2].to_s).to eq('text/x-dvi; q=0.8')
50
+ expect(header.media_ranges[3].to_s).to eq('text/plain; q=0.5')
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,47 @@
1
+ require 'xenon/headers/authorization'
2
+
3
+ describe Xenon::Headers::Authorization do
4
+
5
+ context '::parse' do
6
+ it 'can parse Basic credentials' do
7
+ header = Xenon::Headers::Authorization.parse('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==')
8
+ expect(header.credentials.class).to eq(Xenon::BasicCredentials)
9
+ expect(header.credentials.username).to eq('Aladdin')
10
+ expect(header.credentials.password).to eq('open sesame')
11
+ end
12
+
13
+ it 'can parse Digest credentials' do
14
+ header = Xenon::Headers::Authorization.parse('Digest username="Mufasa"' +
15
+ ', realm="testrealm@host.com"' +
16
+ ', nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"' +
17
+ ', uri="/dir/index.html"' +
18
+ ', qop=auth' +
19
+ ', nc=00000001' +
20
+ ', cnonce="0a4f113b"' +
21
+ ', response="6629fae49393a05397450978507c4ef1"' +
22
+ ', opaque="5ccc069c403ebaf9f0171e9517f40e41"')
23
+ expect(header.credentials.class).to eq(Xenon::GenericCredentials)
24
+ expect(header.credentials.scheme).to eq('Digest')
25
+ expect(header.credentials.token).to eq(nil)
26
+ expect(header.credentials.params).to eq(
27
+ 'username' => 'Mufasa',
28
+ 'realm' => 'testrealm@host.com',
29
+ 'nonce' => 'dcd98b7102dd2f0e8b11d0f600bfb0c093',
30
+ 'uri' => '/dir/index.html',
31
+ 'qop' => 'auth',
32
+ 'nc' => '00000001',
33
+ 'cnonce' => '0a4f113b',
34
+ 'response' => '6629fae49393a05397450978507c4ef1',
35
+ 'opaque' => '5ccc069c403ebaf9f0171e9517f40e41')
36
+ end
37
+
38
+ it 'can parse Bearer credentials' do
39
+ header = Xenon::Headers::Authorization.parse('Bearer eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.')
40
+ expect(header.credentials.class).to eq(Xenon::GenericCredentials)
41
+ expect(header.credentials.scheme).to eq('Bearer')
42
+ expect(header.credentials.token).to eq('eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.')
43
+ expect(header.credentials.params).to eq({})
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,64 @@
1
+ require 'xenon/headers/cache_control'
2
+
3
+ describe Xenon::Headers::CacheControl do
4
+
5
+ context '::parse' do
6
+ ['max-age', 'max-stale', 'min-fresh', 's-maxage'].each do |dir|
7
+ it "can parse the #{dir} directive" do
8
+ header = Xenon::Headers::CacheControl.parse("#{dir}=5")
9
+ expect(header.directives.size).to eq(1)
10
+ expect(header.directives[0].to_s).to eq("#{dir}=5")
11
+ end
12
+
13
+ it "can parse the #{dir} directive with a quoted value" do # should not be sent by clients but is permitted
14
+ header = Xenon::Headers::CacheControl.parse("#{dir}=\"5\"")
15
+ expect(header.directives.size).to eq(1)
16
+ expect(header.directives[0].to_s).to eq("#{dir}=5")
17
+ end
18
+ end
19
+
20
+ ['no-cache', 'no-store', 'no-transform', 'only-if-cached', 'must-revalidate', 'public', 'proxy-revalidate'].each do |dir|
21
+ it "can parse the #{dir} directive" do
22
+ header = Xenon::Headers::CacheControl.parse("#{dir}")
23
+ expect(header.directives.size).to eq(1)
24
+ expect(header.directives[0].to_s).to eq("#{dir}")
25
+ end
26
+ end
27
+
28
+ it "can parse the private directive with no field names" do
29
+ header = Xenon::Headers::CacheControl.parse('private')
30
+ expect(header.directives.size).to eq(1)
31
+ expect(header.directives[0].to_s).to eq('private')
32
+ end
33
+
34
+ # TODO: private directive with field names
35
+
36
+ it 'can parse extension directives with quoted string values' do
37
+ header = Xenon::Headers::CacheControl.parse('ext="hello \"world\""')
38
+ expect(header.directives.size).to eq(1)
39
+ expect(header.directives[0].name).to eq('ext')
40
+ expect(header.directives[0].value).to eq("hello \"world\"")
41
+ end
42
+
43
+ it 'can parse more complex directives' do
44
+ header = Xenon::Headers::CacheControl.parse('public, max-age=3600, must-revalidate')
45
+ expect(header.directives.size).to eq(3)
46
+ expect(header.directives[0].to_s).to eq('public')
47
+ expect(header.directives[1].to_s).to eq('max-age=3600')
48
+ expect(header.directives[2].to_s).to eq('must-revalidate')
49
+ end
50
+ end
51
+
52
+ context '#merge' do
53
+ it 'can merge two headers and maintain directive order' do
54
+ h1 = Xenon::Headers::CacheControl.parse('public, max-age=3600')
55
+ h2 = Xenon::Headers::CacheControl.parse('must-revalidate')
56
+ header = h1.merge(h2)
57
+ expect(header.directives.size).to eq(3)
58
+ expect(header.directives[0].to_s).to eq('public')
59
+ expect(header.directives[1].to_s).to eq('max-age=3600')
60
+ expect(header.directives[2].to_s).to eq('must-revalidate')
61
+ end
62
+ end
63
+
64
+ 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