majoun 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +37 -0
- data/.rspec +4 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +17 -0
- data/CONTRIBUTING.md +11 -0
- data/Changelog.md +1 -0
- data/Gemfile +12 -0
- data/Gemfile.devtools +71 -0
- data/Guardfile +18 -0
- data/LICENSE +20 -0
- data/README.md +24 -0
- data/Rakefile +6 -0
- data/TODO.md +1 -0
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +110 -0
- data/config/rubocop.yml +78 -0
- data/config/yardstick.yml +2 -0
- data/lib/cookie.rb +89 -0
- data/lib/cookie/header.rb +70 -0
- data/lib/cookie/header/attribute.rb +172 -0
- data/lib/cookie/registry.rb +53 -0
- data/lib/cookie/version.rb +8 -0
- data/majoun.gemspec +25 -0
- data/spec/integration/public_api_spec.rb +186 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/cookie/decrypt_spec.rb +19 -0
- data/spec/unit/cookie/encrypt_spec.rb +19 -0
- data/spec/unit/cookie/header/attribute/set/each_spec.rb +22 -0
- data/spec/unit/cookie/header/attribute/set/empty/merge_spec.rb +13 -0
- data/spec/unit/cookie/header/attribute/set/empty/to_s_spec.rb +11 -0
- data/spec/unit/cookie/version_spec.rb +7 -0
- metadata +149 -0
data/lib/cookie.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
require 'concord'
|
7
|
+
require 'adamantium'
|
8
|
+
require 'abstract_type'
|
9
|
+
|
10
|
+
# Models an HTTP Cookie
|
11
|
+
class Cookie
|
12
|
+
|
13
|
+
# An empty frozen array
|
14
|
+
EMPTY_ARRAY = [].freeze
|
15
|
+
|
16
|
+
# An empty frozen hash
|
17
|
+
EMPTY_HASH = {}.freeze
|
18
|
+
|
19
|
+
# An empty frozen string
|
20
|
+
EMPTY_STRING = ''.freeze
|
21
|
+
|
22
|
+
# Separates the cookie name from its value
|
23
|
+
NAME_VALUE_SEPARATOR = '='.freeze
|
24
|
+
|
25
|
+
# Separates cookies
|
26
|
+
COOKIE_SEPARATOR = '; '.freeze
|
27
|
+
|
28
|
+
# Separates ruby class names in a FQN
|
29
|
+
DOUBLE_COLON = '::'.freeze
|
30
|
+
|
31
|
+
# Helper for cookie deletion
|
32
|
+
class Empty < self
|
33
|
+
def initialize(name)
|
34
|
+
super(name, nil)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Namespace for cookie encoders
|
39
|
+
module Encoder
|
40
|
+
Base64 = ->(string) { ::Base64.urlsafe_encode64(string) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Namespace for cookie decoders
|
44
|
+
module Decoder
|
45
|
+
Base64 = ->(string) { ::Base64.urlsafe_decode64(string) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Cookie error base class
|
49
|
+
Error = Class.new(StandardError)
|
50
|
+
|
51
|
+
include Concord::Public.new(:name, :value)
|
52
|
+
include Adamantium::Flat
|
53
|
+
|
54
|
+
def self.coerce(string)
|
55
|
+
new(*string.split(NAME_VALUE_SEPARATOR, 2))
|
56
|
+
end
|
57
|
+
|
58
|
+
def encode(encoder = Encoder::Base64)
|
59
|
+
new(encoder.call(value))
|
60
|
+
end
|
61
|
+
|
62
|
+
def decode(decoder = Decoder::Base64)
|
63
|
+
new(decoder.call(value))
|
64
|
+
end
|
65
|
+
|
66
|
+
def encrypt(box)
|
67
|
+
new(box.encrypt(value))
|
68
|
+
end
|
69
|
+
|
70
|
+
def decrypt(box)
|
71
|
+
new(box.decrypt(value))
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
"#{name}=#{value}"
|
76
|
+
end
|
77
|
+
memoize :to_s
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def new(new_value)
|
82
|
+
self.class.new(name, new_value)
|
83
|
+
end
|
84
|
+
|
85
|
+
end # class Cookie
|
86
|
+
|
87
|
+
require 'cookie/header'
|
88
|
+
require 'cookie/header/attribute'
|
89
|
+
require 'cookie/registry'
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Cookie
|
4
|
+
|
5
|
+
# Models a transient, new cookie on the server that can be serialized
|
6
|
+
# into an HTTP 'Set-Cookie' header
|
7
|
+
class Header
|
8
|
+
|
9
|
+
include Equalizer.new(:cookie, :attributes)
|
10
|
+
include Adamantium::Flat
|
11
|
+
|
12
|
+
attr_reader :cookie
|
13
|
+
protected :cookie
|
14
|
+
|
15
|
+
attr_reader :attributes
|
16
|
+
protected :attributes
|
17
|
+
|
18
|
+
def self.build(name, value, attributes)
|
19
|
+
new(Cookie.new(name, value), attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(cookie, attributes = Attribute::Set::EMPTY)
|
23
|
+
@cookie, @attributes = cookie, attributes
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_domain(domain)
|
27
|
+
with_attribute(Attribute::Domain.new(domain))
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_path(path)
|
31
|
+
with_attribute(Attribute::Path.new(path))
|
32
|
+
end
|
33
|
+
|
34
|
+
def with_max_age(seconds)
|
35
|
+
with_attribute(Attribute::MaxAge.new(seconds))
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_expires(time)
|
39
|
+
with_attribute(Attribute::Expires.new(time))
|
40
|
+
end
|
41
|
+
|
42
|
+
def secure
|
43
|
+
with_attribute(Attribute::Secure.instance)
|
44
|
+
end
|
45
|
+
|
46
|
+
def http_only
|
47
|
+
with_attribute(Attribute::HttpOnly.instance)
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete
|
51
|
+
new(Empty.new(cookie.name), attributes.merge(Attribute::Expired))
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#{cookie}#{attributes}"
|
56
|
+
end
|
57
|
+
memoize :to_s
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def with_attribute(attribute)
|
62
|
+
new(cookie, attributes.merge(attribute))
|
63
|
+
end
|
64
|
+
|
65
|
+
def new(cookie, attributes)
|
66
|
+
self.class.new(cookie, attributes)
|
67
|
+
end
|
68
|
+
|
69
|
+
end # class Header
|
70
|
+
end # class Cookie
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Cookie
|
4
|
+
class Header
|
5
|
+
|
6
|
+
# Baseclass for cookie attributes
|
7
|
+
class Attribute
|
8
|
+
|
9
|
+
include Concord::Public.new(:name)
|
10
|
+
include Adamantium
|
11
|
+
|
12
|
+
REGISTRY = {}
|
13
|
+
|
14
|
+
def self.coerce(name, value)
|
15
|
+
REGISTRY.fetch(name.to_sym).build(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.register_as(name)
|
19
|
+
REGISTRY[name.to_sym] = self
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.attribute_name
|
23
|
+
name.split(DOUBLE_COLON).last
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
30
|
+
# Models a set of attributes used within a {Serializable} cookie
|
31
|
+
class Set
|
32
|
+
|
33
|
+
include Concord.new(:attributes)
|
34
|
+
include Enumerable
|
35
|
+
include Adamantium
|
36
|
+
|
37
|
+
def self.coerce(attributes)
|
38
|
+
new(attributes.each_with_object({}) { |(name, value), hash|
|
39
|
+
attribute = Attribute.coerce(name, value)
|
40
|
+
hash[attribute.name] = attribute if attribute
|
41
|
+
})
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
return to_enum unless block
|
46
|
+
attributes.each_value(&block)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def merge(attribute)
|
51
|
+
Set.new(attributes.merge(attribute.name => attribute))
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#{COOKIE_SEPARATOR}#{map(&:to_s).join(COOKIE_SEPARATOR)}"
|
56
|
+
end
|
57
|
+
memoize :to_s
|
58
|
+
|
59
|
+
# An empty {Set} to be serialized to {EMPTY_STRING}
|
60
|
+
class Empty < self
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
super(EMPTY_HASH)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
EMPTY_STRING
|
68
|
+
end
|
69
|
+
|
70
|
+
end # class Empty
|
71
|
+
|
72
|
+
EMPTY = Empty.new
|
73
|
+
|
74
|
+
end # class Set
|
75
|
+
|
76
|
+
# Abstract baseclass for attributes that have no value
|
77
|
+
#
|
78
|
+
# @abstract
|
79
|
+
class Unary < self
|
80
|
+
|
81
|
+
include AbstractType
|
82
|
+
|
83
|
+
CACHE = {}
|
84
|
+
|
85
|
+
def self.build(value)
|
86
|
+
value ? instance : nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.instance
|
90
|
+
CACHE.fetch(attribute_name) {
|
91
|
+
CACHE[attribute_name] = new(attribute_name)
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# Abstract baseclass for attributes that consist of a name-value
|
98
|
+
# pair
|
99
|
+
#
|
100
|
+
# @abstract
|
101
|
+
class Binary < self
|
102
|
+
|
103
|
+
include AbstractType
|
104
|
+
include Equalizer.new(:name, :value)
|
105
|
+
|
106
|
+
attr_reader :value
|
107
|
+
protected :value
|
108
|
+
|
109
|
+
def self.build(value)
|
110
|
+
new(value)
|
111
|
+
end
|
112
|
+
|
113
|
+
def initialize(value)
|
114
|
+
super(self.class.attribute_name)
|
115
|
+
@value = value
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
"#{name}=#{serialized_value}"
|
120
|
+
end
|
121
|
+
memoize :to_s
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def serialized_value
|
126
|
+
value
|
127
|
+
end
|
128
|
+
|
129
|
+
end # class Binary
|
130
|
+
|
131
|
+
# The Domain attribute
|
132
|
+
class Domain < Binary
|
133
|
+
register_as :domain
|
134
|
+
end
|
135
|
+
|
136
|
+
# The Path attribute
|
137
|
+
class Path < Binary
|
138
|
+
register_as :path
|
139
|
+
end
|
140
|
+
|
141
|
+
# The Max-Age attribute
|
142
|
+
class MaxAge < Binary
|
143
|
+
register_as :max_age
|
144
|
+
end
|
145
|
+
|
146
|
+
# The Expires attribute
|
147
|
+
class Expires < Binary
|
148
|
+
register_as :expires
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def serialized_value
|
153
|
+
super.dup.gmtime.rfc2822
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# The Secure attribute
|
158
|
+
class Secure < Unary
|
159
|
+
register_as :secure
|
160
|
+
end
|
161
|
+
|
162
|
+
# The HttpOnly attribute
|
163
|
+
class HttpOnly < Unary
|
164
|
+
register_as :http_only
|
165
|
+
end
|
166
|
+
|
167
|
+
# Already expired {Expires} attribute useful for cookie deletion
|
168
|
+
Expired = Expires.new(Time.at(0))
|
169
|
+
|
170
|
+
end # class Attribute
|
171
|
+
end # class Header
|
172
|
+
end # class Cookie
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Cookie
|
4
|
+
|
5
|
+
# Models a registry of {Cookie} instances
|
6
|
+
class Registry
|
7
|
+
|
8
|
+
include Enumerable
|
9
|
+
include Equalizer.new(:entries)
|
10
|
+
include Adamantium::Flat
|
11
|
+
|
12
|
+
# Message for {UnknownCookieError}
|
13
|
+
UNKNOWN_COOKIE_MSG = 'No cookie named %s is registered'.freeze
|
14
|
+
|
15
|
+
# Raised when trying to {#fetch} an unknown {Cookie}
|
16
|
+
UnknownCookieError = Class.new(Cookie::Error)
|
17
|
+
|
18
|
+
def self.coerce(header)
|
19
|
+
new(cookie_hash(header))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.cookie_hash(header)
|
23
|
+
header.split(COOKIE_SEPARATOR).each_with_object({}) { |string, hash|
|
24
|
+
cookie = Cookie.coerce(string)
|
25
|
+
hash[cookie.name] = cookie
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
private_class_method :cookie_hash
|
30
|
+
|
31
|
+
attr_reader :entries
|
32
|
+
protected :entries
|
33
|
+
|
34
|
+
def initialize(entries = EMPTY_HASH)
|
35
|
+
@entries = entries
|
36
|
+
end
|
37
|
+
|
38
|
+
def each(&block)
|
39
|
+
return to_enum unless block
|
40
|
+
entries.each(&block)
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch(name)
|
45
|
+
get(name) or raise UnknownCookieError, UNKNOWN_COOKIE_MSG % name.inspect
|
46
|
+
end
|
47
|
+
|
48
|
+
def get(name)
|
49
|
+
@entries[name]
|
50
|
+
end
|
51
|
+
|
52
|
+
end # class Registry
|
53
|
+
end # class Cookie
|
data/majoun.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/cookie/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "majoun"
|
7
|
+
gem.version = Cookie::VERSION.dup
|
8
|
+
gem.authors = [ "Martin Gamsjaeger (snusnu)" ]
|
9
|
+
gem.email = [ "gamsnjaga@gmail.com" ]
|
10
|
+
gem.description = "An HTTP cookie implemented in ruby"
|
11
|
+
gem.summary = "Support for sending and receiving HTTP cookies on ruby servers"
|
12
|
+
gem.homepage = "https://github.com/snusnu/cookie"
|
13
|
+
|
14
|
+
gem.require_paths = [ "lib" ]
|
15
|
+
gem.files = `git ls-files`.split("\n")
|
16
|
+
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
17
|
+
gem.extra_rdoc_files = %w[LICENSE README.md TODO.md]
|
18
|
+
gem.license = 'MIT'
|
19
|
+
|
20
|
+
gem.add_dependency 'adamantium', '~> 0.2.0'
|
21
|
+
gem.add_dependency 'abstract_type', '~> 0.0.7'
|
22
|
+
gem.add_dependency 'concord', '~> 0.1.4'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'bundler', '~> 1.6.1'
|
25
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
module Spec
|
6
|
+
class CryptoBox
|
7
|
+
def encrypt(string)
|
8
|
+
string
|
9
|
+
end
|
10
|
+
|
11
|
+
def decrypt(string)
|
12
|
+
string
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Encoder
|
17
|
+
Noop = ->(string) { string }
|
18
|
+
end
|
19
|
+
|
20
|
+
module Decoder
|
21
|
+
Noop = ->(string) { string }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
shared_context 'integration specs' do
|
26
|
+
let(:cookie) { Cookie.new(name, value) }
|
27
|
+
let(:name) { 'SID' }
|
28
|
+
let(:value) { '{"id": 11}' } # triggers padding char '=' when base64 encoded
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Cookie do
|
32
|
+
|
33
|
+
include_context 'integration specs'
|
34
|
+
|
35
|
+
let(:encoder) { Spec::Encoder::Noop }
|
36
|
+
let(:decoder) { Spec::Decoder::Noop }
|
37
|
+
let(:box) { Spec::CryptoBox.new }
|
38
|
+
|
39
|
+
it 'supports coercion from string to a Cookie instance' do
|
40
|
+
c = cookie
|
41
|
+
expect(Cookie.coerce(c.to_s)).to eql(cookie)
|
42
|
+
|
43
|
+
c = cookie.encode
|
44
|
+
expect(Cookie.coerce(c.to_s)).to eql(cookie.encode)
|
45
|
+
|
46
|
+
c = c.decode
|
47
|
+
expect(Cookie.coerce(c.to_s)).to eql(cookie)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'supports #name and #value' do
|
51
|
+
expect(cookie.name).to be(name)
|
52
|
+
expect(cookie.value).to be(value)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'supports #to_s' do
|
56
|
+
expect(cookie.to_s).to eql("#{cookie.name}=#{cookie.value}")
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'encoding and decoding' do
|
60
|
+
it 'defaults to base64' do
|
61
|
+
encoded = cookie.encode
|
62
|
+
expect(encoded).to eql(Cookie.new(name, Base64.urlsafe_encode64(cookie.value)))
|
63
|
+
|
64
|
+
decoded = encoded.decode
|
65
|
+
expect(decoded).to eql(Cookie.new(name, Base64.urlsafe_decode64(encoded.value)))
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'supports custom encoders and decoders' do
|
69
|
+
encoded = cookie.encode(encoder)
|
70
|
+
expect(encoded).to eql(Cookie.new(name, encoder.call(cookie.value)))
|
71
|
+
|
72
|
+
decoded = encoded.decode(decoder)
|
73
|
+
expect(decoded).to eql(Cookie.new(name, decoder.call(encoded.value)))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'encryption and decryption' do
|
78
|
+
it 'supports crypto boxes which provide #encrypt(msg) and #decrypt(msg)' do
|
79
|
+
encrypted = cookie.encrypt(box)
|
80
|
+
expect(encrypted).to eql(Cookie.new(name, box.encrypt(cookie.value)))
|
81
|
+
|
82
|
+
decrypted = encrypted.decrypt(box)
|
83
|
+
expect(decrypted).to eql(Cookie.new(name, box.decrypt(encrypted.value)))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe Cookie::Header do
|
89
|
+
|
90
|
+
include_context 'integration specs'
|
91
|
+
|
92
|
+
let(:definition) { Cookie::Header.new(cookie) }
|
93
|
+
|
94
|
+
it 'supports all cookie attributes' do
|
95
|
+
d = definition
|
96
|
+
expect(d.to_s).to eql('SID={"id": 11}')
|
97
|
+
|
98
|
+
d = definition.with_domain('.foo.bar')
|
99
|
+
expect(d.to_s).to eql('SID={"id": 11}; Domain=.foo.bar')
|
100
|
+
|
101
|
+
d = d.with_path('/foo')
|
102
|
+
expect(d.to_s).to eql('SID={"id": 11}; Domain=.foo.bar; Path=/foo')
|
103
|
+
|
104
|
+
d = d.with_expires(Time.at(42))
|
105
|
+
expect(d.to_s).to eql('SID={"id": 11}; Domain=.foo.bar; Path=/foo; Expires=Thu, 01 Jan 1970 00:00:42 -0000')
|
106
|
+
|
107
|
+
d = d.with_max_age(42)
|
108
|
+
expect(d.to_s).to eql('SID={"id": 11}; Domain=.foo.bar; Path=/foo; Expires=Thu, 01 Jan 1970 00:00:42 -0000; MaxAge=42')
|
109
|
+
|
110
|
+
d = d.secure
|
111
|
+
expect(d.to_s).to eql('SID={"id": 11}; Domain=.foo.bar; Path=/foo; Expires=Thu, 01 Jan 1970 00:00:42 -0000; MaxAge=42; Secure')
|
112
|
+
|
113
|
+
d = d.http_only
|
114
|
+
expect(d.to_s).to eql('SID={"id": 11}; Domain=.foo.bar; Path=/foo; Expires=Thu, 01 Jan 1970 00:00:42 -0000; MaxAge=42; Secure; HttpOnly')
|
115
|
+
|
116
|
+
d = definition.secure
|
117
|
+
expect(d.to_s).to eql('SID={"id": 11}; Secure')
|
118
|
+
|
119
|
+
d = d.http_only
|
120
|
+
expect(d.to_s).to eql('SID={"id": 11}; Secure; HttpOnly')
|
121
|
+
|
122
|
+
d = definition.http_only
|
123
|
+
expect(d.to_s).to eql('SID={"id": 11}; HttpOnly')
|
124
|
+
|
125
|
+
d = d.secure
|
126
|
+
expect(d.to_s).to eql('SID={"id": 11}; HttpOnly; Secure')
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'overwrites previously defined attributes' do
|
130
|
+
d = definition.with_max_age(0).with_max_age(42)
|
131
|
+
expect(d.to_s).to eql('SID={"id": 11}; MaxAge=42')
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'supports deleting cookies on the client' do
|
135
|
+
d = definition.delete
|
136
|
+
expect(d.to_s).to eql('SID=; Expires=Thu, 01 Jan 1970 00:00:00 -0000')
|
137
|
+
|
138
|
+
d = definition.with_domain('.foo.bar').with_path('/foo')
|
139
|
+
d = d.delete
|
140
|
+
expect(d.to_s).to eql('SID=; Domain=.foo.bar; Path=/foo; Expires=Thu, 01 Jan 1970 00:00:00 -0000')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe Cookie::Registry do
|
145
|
+
|
146
|
+
include_context 'integration specs'
|
147
|
+
|
148
|
+
let(:registry) { Cookie::Registry.coerce(header) }
|
149
|
+
let(:header) { cookie.to_s }
|
150
|
+
|
151
|
+
it 'contains no entries after #initialize' do
|
152
|
+
expect(Cookie::Registry.new.count).to be(0)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'supports coercion from a Set-Cookie header' do
|
156
|
+
expect(registry.get(name)).to eql(cookie)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'supports #get(name)' do
|
160
|
+
expect(registry.get(name)).to eql(cookie)
|
161
|
+
expect(registry.get(:foo)).to be(nil)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'supports #fetch(name)' do
|
165
|
+
expect(registry.fetch(name)).to eql(cookie)
|
166
|
+
expect { registry.fetch(:foo) }.to raise_error(Cookie::Registry::UnknownCookieError)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'is an Enumerable' do
|
170
|
+
expect(registry).to be_kind_of(Enumerable)
|
171
|
+
end
|
172
|
+
|
173
|
+
context '#each' do
|
174
|
+
it 'returns self when a block is given' do
|
175
|
+
expect(registry.each { |_| }).to be(registry)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'returns an enumerator when no block is given' do
|
179
|
+
expect(registry.each).to be_instance_of(Enumerator)
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'yields all cookies' do
|
183
|
+
expect { |block| registry.each(&block) }.to yield_successive_args([name, cookie])
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|