hoodoo 1.0.4 → 1.0.5
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 +8 -8
- data/lib/hoodoo/client/headers.rb +33 -0
- data/lib/hoodoo/services/middleware/middleware.rb +172 -3
- data/lib/hoodoo/version.rb +1 -1
- data/spec/client/client_spec.rb +66 -22
- data/spec/client/headers_spec.rb +29 -0
- data/spec/services/middleware/middleware_assumed_identity_spec.rb +651 -0
- data/spec/services/middleware/middleware_multi_local_spec.rb +145 -13
- data/spec/services/middleware/middleware_multi_remote_spec.rb +145 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzEwZWNjMTA5OTJhODlhN2VkZGQxMDQ0NzNhOGVjZjUxMTM2ZmM1Nw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OTA1MzJjZTZlYzFhY2FiNWUxZWM3MDBiN2QwYzEwNDYyNDM2Y2ZjOA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDQ4YmVkYmQ3YWEwMWY1NGU1ZDZlYTA2NjAxNzM2NTFiYzk5NjI1NTNmZWE0
|
10
|
+
NGYwYjhlMmIxZmY0YTViZjk4Zjg4ODgzODRjNGQ5MDhmNjEwZjVmZDJhM2Yx
|
11
|
+
ODQzZjliM2NhYTE1NTQ2ZjdjNzk1YTZmNmE3NTM2MGQ2NWFhNjE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ODhiYjgzMTFmYTZhOGFhZGM4NDM2OWU3N2MwNjE3OGVhMTk0MGFkNGFhZTg5
|
14
|
+
Y2E5Mzg0YjI3OTBkNzcyNjJhZGI5MjYyN2M5YTYxOWYyYjVjMTgxMGRiM2Ix
|
15
|
+
MGQ4YTBiNWY4MzMzNTI3NWJkODI3ZTE0YWJmYjlkYjI3YWQ4MTg=
|
@@ -35,6 +35,29 @@ module Hoodoo
|
|
35
35
|
#
|
36
36
|
UUID_HEADER_PROC = -> ( value ) { value }
|
37
37
|
|
38
|
+
# Used by HEADER_TO_PROPERTY; this Proc when called with some non-nil
|
39
|
+
# value from an HTTP header containing URL-encoded simple key/value
|
40
|
+
# pair data returns a decoded Hash of key/value pairs. Use URL encoding
|
41
|
+
# in the HTTP header value as per:
|
42
|
+
#
|
43
|
+
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
44
|
+
#
|
45
|
+
# Invalid input will produce unusual results, e.g. an empty Hash or a
|
46
|
+
# Hash where certain keys may have empty string values.
|
47
|
+
#
|
48
|
+
KVP_PROPERTY_PROC = -> ( value ) {
|
49
|
+
Hash[ URI.decode_www_form( value ) ]
|
50
|
+
}
|
51
|
+
|
52
|
+
# Used by HEADER_TO_PROPERTY; this Proc when called with some non-nested
|
53
|
+
# Hash evaluates to a URL-encoded form data String as per:
|
54
|
+
#
|
55
|
+
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
56
|
+
#
|
57
|
+
KVP_HEADER_PROC = -> ( value ) {
|
58
|
+
URI.encode_www_form( value )
|
59
|
+
}
|
60
|
+
|
38
61
|
# Used by HEADER_TO_PROPERTY; this Proc when called with some non-nil
|
39
62
|
# value from an HTTP header representing a Date/Time in a supported
|
40
63
|
# format, evaluates to either a parsed DateTime instance or +nil+ if the
|
@@ -152,6 +175,16 @@ module Hoodoo
|
|
152
175
|
:secured => true,
|
153
176
|
},
|
154
177
|
|
178
|
+
'HTTP_X_ASSUME_IDENTITY_OF' => {
|
179
|
+
:property => :assume_identity_of,
|
180
|
+
:property_proc => KVP_PROPERTY_PROC,
|
181
|
+
:header => 'X-Assume-Identity-Of',
|
182
|
+
:header_proc => KVP_HEADER_PROC,
|
183
|
+
|
184
|
+
:secured => true,
|
185
|
+
:auto_transfer => true,
|
186
|
+
},
|
187
|
+
|
155
188
|
'HTTP_X_DATED_AT' => {
|
156
189
|
:property => :dated_at,
|
157
190
|
:property_proc => DATETIME_IN_PAST_ONLY_PROPERTY_PROC,
|
@@ -758,6 +758,10 @@ module Hoodoo; module Services
|
|
758
758
|
|
759
759
|
return add_local_errors.call() if local_response.halt_processing?
|
760
760
|
|
761
|
+
deal_with_x_assume_identity_of( local_interaction )
|
762
|
+
|
763
|
+
return add_local_errors.call() if local_response.halt_processing?
|
764
|
+
|
761
765
|
# Construct the local request details.
|
762
766
|
|
763
767
|
local_request.uri_path_components = upc
|
@@ -1186,7 +1190,7 @@ module Hoodoo; module Services
|
|
1186
1190
|
end
|
1187
1191
|
|
1188
1192
|
if secure == false || level == :error
|
1189
|
-
body =
|
1193
|
+
body = String.new
|
1190
1194
|
rack_data[ 2 ].each { | thing | body << thing.to_s }
|
1191
1195
|
|
1192
1196
|
if interaction.context.response.halt_processing?
|
@@ -1547,10 +1551,13 @@ module Hoodoo; module Services
|
|
1547
1551
|
|
1548
1552
|
# Load the session and then, in the context of a loaded session, process
|
1549
1553
|
# any remaining extension ("X-...") HTTP headers, checking up on secured
|
1550
|
-
# headers in passing.
|
1554
|
+
# headers in passing. There's special handling for X-Assume-Identity-Of,
|
1555
|
+
# which may update the session data loaded into 'interaction' with new
|
1556
|
+
# identity information.
|
1551
1557
|
|
1552
1558
|
load_session_into( interaction )
|
1553
1559
|
deal_with_x_headers( interaction )
|
1560
|
+
deal_with_x_assume_identity_of( interaction )
|
1554
1561
|
|
1555
1562
|
return nil
|
1556
1563
|
end
|
@@ -1934,6 +1941,167 @@ module Hoodoo; module Services
|
|
1934
1941
|
end
|
1935
1942
|
end
|
1936
1943
|
|
1944
|
+
# The X-Assume-Identity-Of secured HTTP header allows a caller to specify
|
1945
|
+
# values for parts of their session's "identity" section, based upon
|
1946
|
+
# permitted values described in their session's "scoping" section. This
|
1947
|
+
# method assumes that the permission to use the header in the first place
|
1948
|
+
# has already been established by #deal_with_x_headers and, as a result,
|
1949
|
+
# relevant property information has been written into the request object.
|
1950
|
+
#
|
1951
|
+
# The header's value is parsed and checked against the session scoping
|
1952
|
+
# data. If everything looks good, the loaded session's identity is
|
1953
|
+
# updated accordingly. If there are any problems, one or more errors will
|
1954
|
+
# be added to the interaction's context's response object.
|
1955
|
+
#
|
1956
|
+
# +interaction+:: Hoodoo::Services::Middleware::Interaction instance
|
1957
|
+
# describing the current interaction. Updated on exit.
|
1958
|
+
#
|
1959
|
+
def deal_with_x_assume_identity_of( interaction )
|
1960
|
+
|
1961
|
+
# Header not in use? Exit now.
|
1962
|
+
#
|
1963
|
+
return if interaction.context.request.assume_identity_of.nil?
|
1964
|
+
|
1965
|
+
input_hash = interaction.context.request.assume_identity_of
|
1966
|
+
rules_hash = interaction.context.session.scoping.authorised_identities rescue {}
|
1967
|
+
|
1968
|
+
if ( input_hash.empty? )
|
1969
|
+
interaction.context.response.errors.add_error(
|
1970
|
+
'generic.malformed',
|
1971
|
+
{
|
1972
|
+
:message => "X-Assume-Identity-Of header value is malformed",
|
1973
|
+
:reference => { :header_value => ( interaction.context.request.assume_identity_of rescue 'unknown' ) }
|
1974
|
+
}
|
1975
|
+
)
|
1976
|
+
end
|
1977
|
+
|
1978
|
+
return if interaction.context.response.halt_processing?
|
1979
|
+
|
1980
|
+
identity_overrides = validate_x_assume_identity_of( interaction, input_hash, rules_hash )
|
1981
|
+
|
1982
|
+
return if interaction.context.response.halt_processing?
|
1983
|
+
|
1984
|
+
identity_overrides.each do | key, value |
|
1985
|
+
interaction.context.session.identity.send( "#{ key }=", value )
|
1986
|
+
end
|
1987
|
+
end
|
1988
|
+
|
1989
|
+
# Back-end to #deal_with_x_assume_identity_of which recursively processes
|
1990
|
+
# a rule set against a value from the X-Assume-Identity-Of HTTP header and
|
1991
|
+
# either updates the interaction's context's response object with error
|
1992
|
+
# details if anything is wrong, or returns a flat Hash of keys and values
|
1993
|
+
# to (over-)write in the session's identity section.
|
1994
|
+
#
|
1995
|
+
# +interaction+:: Hoodoo::Services::Middleware::Interaction instance
|
1996
|
+
# describing the current interaction. Will be updated on
|
1997
|
+
# exit if errors occur.
|
1998
|
+
#
|
1999
|
+
# +input_hash+:: Header value for X-Assume-Identity-Of processed into a
|
2000
|
+
# flat Hash of String keys and String values.
|
2001
|
+
#
|
2002
|
+
# +rules_hash+:: Rules Hash from the session scoping data - usually its
|
2003
|
+
# "authorised_identities" key - or a sub-hash from nested
|
2004
|
+
# data during recursive calls.
|
2005
|
+
#
|
2006
|
+
# +recursive+:: Top-level callers MUST omit this parameter. Internal
|
2007
|
+
# recursive callers MUST set this to +true+.
|
2008
|
+
#
|
2009
|
+
def validate_x_assume_identity_of( interaction, input_hash, rules_hash, recursive = false )
|
2010
|
+
identity_overrides = {}
|
2011
|
+
|
2012
|
+
unless rules_hash.is_a?( Hash )
|
2013
|
+
interaction.context.response.errors.add_error(
|
2014
|
+
'generic.malformed',
|
2015
|
+
:message => "X-Assume-Identity-Of header cannot be processed because of malformed scoping rules in Session's associated Caller",
|
2016
|
+
)
|
2017
|
+
|
2018
|
+
return nil
|
2019
|
+
end
|
2020
|
+
|
2021
|
+
rules_hash.each do | rules_key, rules_value |
|
2022
|
+
|
2023
|
+
next unless input_hash.has_key?( rules_key )
|
2024
|
+
input_value = input_hash[ rules_key ]
|
2025
|
+
|
2026
|
+
unless input_value.is_a?( String )
|
2027
|
+
raise "Internal error - internal validation input value for X-Assume-Identity-Of is not a String"
|
2028
|
+
end
|
2029
|
+
|
2030
|
+
if rules_value.is_a?( Array )
|
2031
|
+
if rules_value.include?( input_value )
|
2032
|
+
identity_overrides[ rules_key ] = input_value
|
2033
|
+
else
|
2034
|
+
interaction.context.response.errors.add_error(
|
2035
|
+
'platform.forbidden',
|
2036
|
+
{
|
2037
|
+
:message => "X-Assume-Identity-Of header value requests a prohibited identity quantity",
|
2038
|
+
:reference =>
|
2039
|
+
{
|
2040
|
+
:name => rules_key,
|
2041
|
+
:value => input_value
|
2042
|
+
}
|
2043
|
+
}
|
2044
|
+
)
|
2045
|
+
return nil
|
2046
|
+
end
|
2047
|
+
|
2048
|
+
elsif rules_value.is_a?( Hash )
|
2049
|
+
if rules_value.has_key?( input_value )
|
2050
|
+
identity_overrides[ rules_key ] = input_value
|
2051
|
+
|
2052
|
+
nested_identity_overrides = validate_x_assume_identity_of(
|
2053
|
+
interaction,
|
2054
|
+
input_hash,
|
2055
|
+
rules_value[ input_value ],
|
2056
|
+
true
|
2057
|
+
)
|
2058
|
+
|
2059
|
+
return if nested_identity_overrides.nil?
|
2060
|
+
identity_overrides.merge!( nested_identity_overrides )
|
2061
|
+
|
2062
|
+
else
|
2063
|
+
interaction.context.response.errors.add_error(
|
2064
|
+
'platform.forbidden',
|
2065
|
+
{
|
2066
|
+
:message => "X-Assume-Identity-Of header value requests a prohibited identity quantity",
|
2067
|
+
:reference =>
|
2068
|
+
{
|
2069
|
+
:name => rules_key,
|
2070
|
+
:value => input_value
|
2071
|
+
}
|
2072
|
+
}
|
2073
|
+
)
|
2074
|
+
return nil
|
2075
|
+
|
2076
|
+
end
|
2077
|
+
|
2078
|
+
else
|
2079
|
+
interaction.context.response.errors.add_error(
|
2080
|
+
'generic.malformed',
|
2081
|
+
:message => "X-Assume-Identity-Of header cannot be processed because of malformed scoping rules in Session's associated Caller",
|
2082
|
+
)
|
2083
|
+
return nil
|
2084
|
+
|
2085
|
+
end
|
2086
|
+
end
|
2087
|
+
|
2088
|
+
unless recursive || ( input_hash.keys - identity_overrides.keys ).empty?
|
2089
|
+
interaction.context.response.errors.add_error(
|
2090
|
+
'platform.forbidden',
|
2091
|
+
{
|
2092
|
+
:message => "X-Assume-Identity-Of header value requests prohibited identity name(s)",
|
2093
|
+
:reference =>
|
2094
|
+
{
|
2095
|
+
:names => ( input_hash.keys - identity_overrides.keys ).sort().join( ',' )
|
2096
|
+
}
|
2097
|
+
}
|
2098
|
+
)
|
2099
|
+
return nil
|
2100
|
+
end
|
2101
|
+
|
2102
|
+
return identity_overrides
|
2103
|
+
end
|
2104
|
+
|
1937
2105
|
# Preprocessing stage that sets up common headers required in any response.
|
1938
2106
|
# May vary according to inbound content type requested. If processing was
|
1939
2107
|
# aborted early (e.g. missing inbound Content-Type) we may fall to defaults.
|
@@ -1941,7 +2109,8 @@ module Hoodoo; module Services
|
|
1941
2109
|
# (At the time of writing, platform documentations say we're JSON only - but
|
1942
2110
|
# there's an strong chance of e.g. XML representation being demanded later).
|
1943
2111
|
#
|
1944
|
-
# +
|
2112
|
+
# +interaction+:: Hoodoo::Services::Middleware::Interaction instance
|
2113
|
+
# describing the current interaction. Updated on exit.
|
1945
2114
|
#
|
1946
2115
|
def set_common_response_headers( interaction )
|
1947
2116
|
interaction.context.response.add_header( 'X-Interaction-ID', interaction.interaction_id )
|
data/lib/hoodoo/version.rb
CHANGED
data/spec/client/client_spec.rb
CHANGED
@@ -146,20 +146,22 @@ class RSpecClientTestTargetImplementation < Hoodoo::Services::Implementation
|
|
146
146
|
# if adding things.
|
147
147
|
|
148
148
|
{
|
149
|
-
'id'
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
'created_at'
|
154
|
-
'kind'
|
155
|
-
'language'
|
156
|
-
|
157
|
-
'embeds'
|
158
|
-
'body_hash'
|
159
|
-
'dated_at'
|
160
|
-
'dated_from'
|
161
|
-
'resource_uuid'
|
162
|
-
'deja_vu'
|
149
|
+
'id' => context.request.ident ||
|
150
|
+
context.request.body.try( :[], 'id' ) ||
|
151
|
+
Hoodoo::UUID.generate(),
|
152
|
+
|
153
|
+
'created_at' => Time.now.utc.iso8601,
|
154
|
+
'kind' => 'RSpecClientTestTarget',
|
155
|
+
'language' => context.request.locale,
|
156
|
+
|
157
|
+
'embeds' => context.request.embeds,
|
158
|
+
'body_hash' => context.request.body,
|
159
|
+
'dated_at' => context.request.dated_at.nil? ? nil : Hoodoo::Utilities.nanosecond_iso8601( context.request.dated_at ),
|
160
|
+
'dated_from' => context.request.dated_from.nil? ? nil : Hoodoo::Utilities.nanosecond_iso8601( context.request.dated_from ),
|
161
|
+
'resource_uuid' => context.request.resource_uuid,
|
162
|
+
'deja_vu' => context.request.deja_vu,
|
163
|
+
'assume_identity_of' => context.request.assume_identity_of,
|
164
|
+
'actual_identity' => ( context.session.identity.to_h rescue nil ),
|
163
165
|
}
|
164
166
|
end
|
165
167
|
end
|
@@ -213,6 +215,8 @@ describe Hoodoo::Client do
|
|
213
215
|
@old_test_session = Hoodoo::Services::Middleware.test_session()
|
214
216
|
@port = spec_helper_start_svc_app_in_thread_for( RSpecClientTestService )
|
215
217
|
@https_port = spec_helper_start_svc_app_in_thread_for( RSpecClientTestService, true )
|
218
|
+
@authorised_identities = { 'member_id' => [ '23', '24' ] }
|
219
|
+
@example_authorised_identity = { 'member_id' => '23' }
|
216
220
|
end
|
217
221
|
|
218
222
|
after :all do
|
@@ -249,15 +253,17 @@ describe Hoodoo::Client do
|
|
249
253
|
# "def option_based_expectations" later in this file. Be careful
|
250
254
|
# to follow the naming convention evident below if adding things.
|
251
255
|
|
252
|
-
@expected_dated_at
|
253
|
-
@expected_dated_from
|
254
|
-
@expected_resource_uuid
|
255
|
-
@
|
256
|
+
@expected_dated_at = @dated_at.nil? ? nil : Hoodoo::Utilities.nanosecond_iso8601( @dated_at )
|
257
|
+
@expected_dated_from = @dated_from.nil? ? nil : Hoodoo::Utilities.nanosecond_iso8601( @dated_from )
|
258
|
+
@expected_resource_uuid = @resource_uuid
|
259
|
+
@expected_assume_identity_of = @assume_identity_of
|
260
|
+
@expected_deja_vu = @deja_vu != true ? nil : true
|
256
261
|
|
257
|
-
endpoint_opts[ :dated_at
|
258
|
-
endpoint_opts[ :dated_from
|
259
|
-
endpoint_opts[ :resource_uuid
|
260
|
-
endpoint_opts[ :
|
262
|
+
endpoint_opts[ :dated_at ] = @dated_at unless @dated_at.nil?
|
263
|
+
endpoint_opts[ :dated_from ] = @dated_from unless @dated_from.nil?
|
264
|
+
endpoint_opts[ :resource_uuid ] = @resource_uuid unless @resource_uuid.nil?
|
265
|
+
endpoint_opts[ :assume_identity_of ] = @assume_identity_of unless @assume_identity_of.nil?
|
266
|
+
endpoint_opts[ :deja_vu ] = @deja_vu if @deja_vu == true
|
261
267
|
|
262
268
|
if rand( 2 ) == 0
|
263
269
|
override_locale = SecureRandom.urlsafe_base64( 2 )
|
@@ -508,6 +514,8 @@ describe Hoodoo::Client do
|
|
508
514
|
case property
|
509
515
|
when :resource_uuid
|
510
516
|
@resource_uuid = Hoodoo::UUID.generate
|
517
|
+
when :assume_identity_of
|
518
|
+
@assume_identity_of = @example_authorised_identity
|
511
519
|
else
|
512
520
|
raise "Update client_spec.rb with new secured properties for test"
|
513
521
|
end
|
@@ -725,8 +733,10 @@ describe Hoodoo::Client do
|
|
725
733
|
context 'and with secured option' do
|
726
734
|
before :each do
|
727
735
|
test_session = @old_test_session.dup
|
736
|
+
test_session.identity = OpenStruct.new
|
728
737
|
test_session.scoping = @old_test_session.scoping.dup
|
729
738
|
test_session.scoping.authorised_http_headers = []
|
739
|
+
test_session.scoping.authorised_identities = @authorised_identities
|
730
740
|
|
731
741
|
Hoodoo::Client::Headers::HEADER_TO_PROPERTY.each do | rack_header, description |
|
732
742
|
next unless description[ :secured ] == true
|
@@ -750,6 +760,8 @@ describe Hoodoo::Client do
|
|
750
760
|
case property
|
751
761
|
when :resource_uuid
|
752
762
|
@resource_uuid = Hoodoo::UUID.generate
|
763
|
+
when :assume_identity_of
|
764
|
+
@assume_identity_of = @example_authorised_identity
|
753
765
|
else
|
754
766
|
raise "Update client_spec.rb with new secured properties for test"
|
755
767
|
end
|
@@ -763,6 +775,8 @@ describe Hoodoo::Client do
|
|
763
775
|
result = @endpoint.show( mock_ident )
|
764
776
|
|
765
777
|
expect( result.platform_errors.has_errors? ).to eq( false )
|
778
|
+
|
779
|
+
option_based_expectations( result )
|
766
780
|
end
|
767
781
|
end
|
768
782
|
|
@@ -781,6 +795,36 @@ describe Hoodoo::Client do
|
|
781
795
|
expect( result.platform_errors.has_errors? ).to eq( false )
|
782
796
|
expect( result[ 'id' ] ).to eq( @resource_uuid )
|
783
797
|
end
|
798
|
+
|
799
|
+
context "'assume_identity_of' in use" do
|
800
|
+
it 'but invalid' do
|
801
|
+
@assume_identity_of = { 'invalid' => 'Hoodoo::UUID.generate' }
|
802
|
+
|
803
|
+
set_vars_for(
|
804
|
+
base_uri: "http://localhost:#{ @port }",
|
805
|
+
auto_session: false
|
806
|
+
)
|
807
|
+
|
808
|
+
result = @endpoint.create( { 'hello' => 'world' } )
|
809
|
+
|
810
|
+
expect( result.platform_errors.has_errors? ).to eq( true )
|
811
|
+
expect( result.platform_errors.errors[ 0 ][ 'code' ] ).to eq( 'platform.forbidden' )
|
812
|
+
end
|
813
|
+
|
814
|
+
it 'and valid' do
|
815
|
+
@assume_identity_of = @example_authorised_identity
|
816
|
+
|
817
|
+
set_vars_for(
|
818
|
+
base_uri: "http://localhost:#{ @port }",
|
819
|
+
auto_session: false
|
820
|
+
)
|
821
|
+
|
822
|
+
result = @endpoint.create( { 'hello' => 'world' } )
|
823
|
+
|
824
|
+
expect( result.platform_errors.has_errors? ).to eq( false )
|
825
|
+
expect( result[ 'actual_identity' ] ).to eq( @example_authorised_identity )
|
826
|
+
end
|
827
|
+
end
|
784
828
|
end
|
785
829
|
end
|
786
830
|
|
data/spec/client/headers_spec.rb
CHANGED
@@ -23,6 +23,35 @@ describe Hoodoo::Client::Headers do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
context 'for URL encoded data' do
|
27
|
+
before :each do
|
28
|
+
@test_hash =
|
29
|
+
{
|
30
|
+
'foo' => "hello, world; this & that = foo! \r\t",
|
31
|
+
'bar' => "foo;bar=baz & this + UTF-8 / emoji 😀"
|
32
|
+
}
|
33
|
+
|
34
|
+
@test_string = URI::encode_www_form( @test_hash )
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'KVP_PROPERTY_PROC' do
|
38
|
+
it 'converts valid values' do
|
39
|
+
expect( described_class::KVP_PROPERTY_PROC.call( @test_string ) ).to eq( @test_hash )
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not raise exceptions for invalid values' do
|
43
|
+
expect( described_class::KVP_PROPERTY_PROC.call( '' ) ).to eq( {} )
|
44
|
+
expect( described_class::KVP_PROPERTY_PROC.call( 'hello' ) ).to eq( { 'hello' => '' } )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'KVP_HEADER_PROC' do
|
49
|
+
it 'converts values' do
|
50
|
+
expect( described_class::KVP_HEADER_PROC.call( @test_hash ) ).to eq( @test_string )
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
26
55
|
context 'DATETIME_IN_PAST_ONLY_PROPERTY_PROC' do
|
27
56
|
it 'converts valid values' do
|
28
57
|
date_time = DateTime.now - 10.seconds
|