rocketamf_pure 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/README.rdoc +47 -0
  2. data/Rakefile +9 -0
  3. data/benchmark.rb +73 -0
  4. data/lib/rocketamf.rb +212 -0
  5. data/lib/rocketamf/class_mapping.rb +237 -0
  6. data/lib/rocketamf/constants.rb +46 -0
  7. data/lib/rocketamf/ext.rb +28 -0
  8. data/lib/rocketamf/extensions.rb +22 -0
  9. data/lib/rocketamf/pure.rb +24 -0
  10. data/lib/rocketamf/pure/deserializer.rb +417 -0
  11. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  12. data/lib/rocketamf/pure/remoting.rb +117 -0
  13. data/lib/rocketamf/pure/serializer.rb +474 -0
  14. data/lib/rocketamf/remoting.rb +196 -0
  15. data/lib/rocketamf/values/messages.rb +212 -0
  16. data/lib/rocketamf/values/typed_hash.rb +13 -0
  17. data/lib/rocketamf_pure.rb +1 -0
  18. data/spec/class_mapping_spec.rb +110 -0
  19. data/spec/deserializer_spec.rb +423 -0
  20. data/spec/fast_class_mapping_spec.rb +144 -0
  21. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  22. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  23. data/spec/fixtures/objects/amf0-date.bin +0 -0
  24. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  25. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  26. data/spec/fixtures/objects/amf0-null.bin +1 -0
  27. data/spec/fixtures/objects/amf0-number.bin +0 -0
  28. data/spec/fixtures/objects/amf0-object.bin +0 -0
  29. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  30. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  31. data/spec/fixtures/objects/amf0-string.bin +0 -0
  32. data/spec/fixtures/objects/amf0-time.bin +0 -0
  33. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  34. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  35. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  36. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  37. data/spec/fixtures/objects/amf3-0.bin +0 -0
  38. data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
  39. data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
  40. data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
  41. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  42. data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
  43. data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
  44. data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
  45. data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
  46. data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
  47. data/spec/fixtures/objects/amf3-date.bin +0 -0
  48. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  49. data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
  50. data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
  51. data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
  52. data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
  53. data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
  54. data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
  55. data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
  56. data/spec/fixtures/objects/amf3-false.bin +1 -0
  57. data/spec/fixtures/objects/amf3-float.bin +0 -0
  58. data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
  59. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  60. data/spec/fixtures/objects/amf3-large-max.bin +0 -0
  61. data/spec/fixtures/objects/amf3-large-min.bin +0 -0
  62. data/spec/fixtures/objects/amf3-max.bin +1 -0
  63. data/spec/fixtures/objects/amf3-min.bin +0 -0
  64. data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
  65. data/spec/fixtures/objects/amf3-null.bin +1 -0
  66. data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
  67. data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
  68. data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
  69. data/spec/fixtures/objects/amf3-string.bin +1 -0
  70. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  71. data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
  72. data/spec/fixtures/objects/amf3-true.bin +1 -0
  73. data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
  74. data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
  75. data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
  76. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  77. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  78. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  79. data/spec/fixtures/request/blaze-response.bin +0 -0
  80. data/spec/fixtures/request/commandMessage.bin +0 -0
  81. data/spec/fixtures/request/flex-request.bin +0 -0
  82. data/spec/fixtures/request/multiple-simple-request.bin +0 -0
  83. data/spec/fixtures/request/remotingMessage.bin +0 -0
  84. data/spec/fixtures/request/simple-request.bin +0 -0
  85. data/spec/fixtures/request/simple-response.bin +0 -0
  86. data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
  87. data/spec/messages_spec.rb +39 -0
  88. data/spec/remoting_spec.rb +196 -0
  89. data/spec/serializer_spec.rb +503 -0
  90. data/spec/spec_helper.rb +55 -0
  91. metadata +148 -0
@@ -0,0 +1,196 @@
1
+ module RocketAMF
2
+ # Container for the AMF request/response.
3
+ class Envelope
4
+ attr_reader :amf_version, :headers, :messages
5
+
6
+ def initialize props={}
7
+ @amf_version = props[:amf_version] || 0
8
+ @headers = props[:headers] || {}
9
+ @messages = props[:messages] || []
10
+ end
11
+
12
+ # Populates the envelope from the given stream or string using the given
13
+ # class mapper, or creates a new one. Returns self for easy chaining.
14
+ #
15
+ # Example:
16
+ #
17
+ # req = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
18
+ #--
19
+ # Implemented in pure/remoting.rb RocketAMF::Pure::Envelope
20
+ def populate_from_stream stream, class_mapper=nil
21
+ raise AMFError, 'Must load "rocketamf/pure"'
22
+ end
23
+
24
+ # Creates the appropriate message and adds it to <tt>messages</tt> to call
25
+ # the given target using the standard (old) remoting APIs. You can call multiple
26
+ # targets in the same request, unlike with the flex remotings APIs.
27
+ #
28
+ # Example:
29
+ #
30
+ # req = RocketAMF::Envelope.new
31
+ # req.call 'test', "arg_1", ["args", "args"]
32
+ # req.call 'Controller.action'
33
+ def call target, *args
34
+ raise "Cannot use different call types" unless @call_type.nil? || @call_type == :simple
35
+ @call_type = :simple
36
+
37
+ msg_num = messages.length+1
38
+ @messages << RocketAMF::Message.new(target, "/#{msg_num}", args)
39
+ end
40
+
41
+ # Creates the appropriate message and adds it to <tt>messages</tt> using the
42
+ # new flex (RemoteObject) remoting APIs. You can only make one flex remoting
43
+ # call per envelope, and the AMF version must be set to 3.
44
+ #
45
+ # Example:
46
+ #
47
+ # req = RocketAMF::Envelope.new :amf_version => 3
48
+ # req.call_flex 'Controller.action', "arg_1", ["args", "args"]
49
+ def call_flex target, *args
50
+ raise "Can only call one flex target per request" if @call_type == :flex
51
+ raise "Cannot use different call types" if @call_type == :simple
52
+ raise "Cannot use flex remoting calls with AMF0" if @amf_version != 3
53
+ @call_type = :flex
54
+
55
+ flex_msg = RocketAMF::Values::RemotingMessage.new
56
+ target_parts = target.split(".")
57
+ flex_msg.operation = target_parts.pop # Use pop so that a missing source is possible without issues
58
+ flex_msg.source = target_parts.pop
59
+ flex_msg.body = args
60
+ @messages << RocketAMF::Message.new('null', '/2', flex_msg) # /2 because it always sends a command message before
61
+ end
62
+
63
+ # Serializes the envelope to a string using the given class mapper, or creates
64
+ # a new one, and returns the result
65
+ #--
66
+ # Implemented in pure/remoting.rb RocketAMF::Pure::Envelope
67
+ def serialize class_mapper=nil
68
+ raise AMFError, 'Must load "rocketamf/pure"'
69
+ end
70
+
71
+ # Builds response from the request, iterating over each method call and using
72
+ # the return value as the method call's return value. Marks as envelope as
73
+ # constructed after running.
74
+ #--
75
+ # Iterate over all the sent messages. If they're somthing we can handle, like
76
+ # a command message, then simply add the response message ourselves. If it's
77
+ # a method call, then call the block with the method and args, catching errors
78
+ # for handling. Then create the appropriate response message using the return
79
+ # value of the block as the return value for the method call.
80
+ def each_method_call request, &block
81
+ raise 'Response already constructed' if @constructed
82
+
83
+ # Set version from response
84
+ # Can't just copy version because FMS sends version as 1
85
+ @amf_version = request.amf_version == 3 ? 3 : 0
86
+
87
+ request.messages.each do |m|
88
+ # What's the request body?
89
+ case m.data
90
+ when Values::CommandMessage
91
+ # Pings should be responded to with an AcknowledgeMessage built using the ping
92
+ # Everything else is unsupported
93
+ command_msg = m.data
94
+ if command_msg.operation == Values::CommandMessage::CLIENT_PING_OPERATION
95
+ response_value = Values::AcknowledgeMessage.new(command_msg)
96
+ else
97
+ e = Exception.new("CommandMessage #{command_msg.operation} not implemented")
98
+ e.set_backtrace ["RocketAMF::Envelope each_method_call"]
99
+ response_value = Values::ErrorMessage.new(command_msg, e)
100
+ end
101
+ when Values::RemotingMessage
102
+ # Using RemoteObject style message calls
103
+ remoting_msg = m.data
104
+ acknowledge_msg = Values::AcknowledgeMessage.new(remoting_msg)
105
+ method_base = remoting_msg.source.to_s.empty? ? '' : remoting_msg.source+'.'
106
+ body = dispatch_call :method => method_base+remoting_msg.operation, :args => remoting_msg.body, :source => remoting_msg, :block => block
107
+
108
+ # Response should be the bare ErrorMessage if there was an error
109
+ if body.is_a?(Values::ErrorMessage)
110
+ response_value = body
111
+ else
112
+ acknowledge_msg.body = body
113
+ response_value = acknowledge_msg
114
+ end
115
+ else
116
+ # Standard response message
117
+ response_value = dispatch_call :method => m.target_uri, :args => m.data, :source => m, :block => block
118
+ end
119
+
120
+ target_uri = m.response_uri
121
+ target_uri += response_value.is_a?(Values::ErrorMessage) ? '/onStatus' : '/onResult'
122
+ @messages << ::RocketAMF::Message.new(target_uri, '', response_value)
123
+ end
124
+
125
+ @constructed = true
126
+ end
127
+
128
+ # Returns the result of a response envelope, or an array of results if there
129
+ # are multiple action call messages. It automatically unwraps flex-style
130
+ # RemoteObject response messages, where the response result is inside a
131
+ # RocketAMF::Values::AcknowledgeMessage.
132
+ #
133
+ # Example:
134
+ #
135
+ # req = RocketAMF::Envelope.new
136
+ # req.call('TestController.test', 'first_arg', 'second_arg')
137
+ # res = RocketAMF::Envelope.new
138
+ # res.each_method_call req do |method, args|
139
+ # ['a', 'b']
140
+ # end
141
+ # res.result #=> ['a', 'b']
142
+ def result
143
+ results = []
144
+ messages.each do |msg|
145
+ if msg.data.is_a?(Values::AcknowledgeMessage)
146
+ results << msg.data.body
147
+ else
148
+ results << msg.data
149
+ end
150
+ end
151
+ results.length > 1 ? results : results[0]
152
+ end
153
+
154
+ # Whether or not the response has been constructed. Can be used to prevent
155
+ # serialization when no processing has taken place.
156
+ def constructed?
157
+ @constructed
158
+ end
159
+
160
+ # Return the serialized envelope as a string
161
+ def to_s
162
+ serialize
163
+ end
164
+
165
+ def dispatch_call p #:nodoc:
166
+ begin
167
+ p[:block].call(p[:method], p[:args])
168
+ rescue Exception => e
169
+ # Create ErrorMessage object using the source message as the base
170
+ Values::ErrorMessage.new(p[:source], e)
171
+ end
172
+ end
173
+ end
174
+
175
+ # RocketAMF::Envelope header
176
+ class Header
177
+ attr_accessor :name, :must_understand, :data
178
+
179
+ def initialize name, must_understand, data
180
+ @name = name
181
+ @must_understand = must_understand
182
+ @data = data
183
+ end
184
+ end
185
+
186
+ # RocketAMF::Envelope message
187
+ class Message
188
+ attr_accessor :target_uri, :response_uri, :data
189
+
190
+ def initialize target_uri, response_uri, data
191
+ @target_uri = target_uri
192
+ @response_uri = response_uri
193
+ @data = data
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,212 @@
1
+ module RocketAMF
2
+ module Values #:nodoc:
3
+ # Base class for all special AS3 response messages. Maps to
4
+ # <tt>flex.messaging.messages.AbstractMessage</tt>.
5
+ class AbstractMessage
6
+ EXTERNALIZABLE_FIELDS = [
7
+ %w[ body clientId destination headers messageId timestamp timeToLive ],
8
+ %w[ clientIdBytes messageIdBytes ]
9
+ ]
10
+ attr_accessor :clientId
11
+ attr_accessor :destination
12
+ attr_accessor :messageId
13
+ attr_accessor :timestamp
14
+ attr_accessor :timeToLive
15
+ attr_accessor :headers
16
+ attr_accessor :body
17
+
18
+ def clientIdBytes= bytes
19
+ @clientId = pretty_uuid(bytes) unless bytes.nil?
20
+ end
21
+
22
+ def messageIdBytes= bytes
23
+ @messageId = pretty_uuid(bytes) unless bytes.nil?
24
+ end
25
+
26
+ def read_external des
27
+ read_external_fields des, EXTERNALIZABLE_FIELDS
28
+ end
29
+
30
+ private
31
+ def rand_uuid
32
+ [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s
33
+ end
34
+
35
+ def rand_hex_3(l)
36
+ "%0#{l}x" % rand(1 << l*4)
37
+ end
38
+
39
+ def pretty_uuid bytes
40
+ "%08x-%04x-%04x-%04x-%08x%04x" % bytes.string.unpack("NnnnNn")
41
+ end
42
+
43
+ def read_external_fields des, fields
44
+ # Read flags
45
+ flags = []
46
+ loop do
47
+ flags << des.source.read(1).unpack('C').first
48
+ break if flags.last < 128
49
+ end
50
+
51
+ # Read fields and any remaining unmapped fields in a byte-set
52
+ fields.each_with_index do |list, i|
53
+ list.each_with_index do |name, j|
54
+ if flags[i] & 2**j != 0
55
+ send("#{name}=", des.read_object)
56
+ end
57
+ end
58
+
59
+ # Read remaining flags even though we don't recognize them
60
+ # Zero out high bit, as it's the has-next-field marker
61
+ f = (flags[i] & ~128) >> list.length
62
+ while f > 0
63
+ des.read_object if (f & 1) != 0
64
+ f >>= 1
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
71
+ class RemotingMessage < AbstractMessage
72
+ # The name of the service to be called including package name
73
+ attr_accessor :source
74
+
75
+ # The name of the method to be called
76
+ attr_accessor :operation
77
+
78
+ def initialize
79
+ @clientId = nil
80
+ @destination = nil
81
+ @messageId = rand_uuid
82
+ @timestamp = Time.new.to_i*100
83
+ @timeToLive = 0
84
+ @headers = {}
85
+ @body = nil
86
+ end
87
+ end
88
+
89
+ # Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
90
+ class AsyncMessage < AbstractMessage
91
+ EXTERNALIZABLE_FIELDS = [
92
+ %w[ correlationId correlationIdBytes]
93
+ ]
94
+ attr_accessor :correlationId
95
+
96
+ def correlationIdBytes= bytes
97
+ @correlationId = pretty_uuid(bytes) unless bytes.nil?
98
+ end
99
+
100
+ def read_external des
101
+ super des
102
+ read_external_fields des, EXTERNALIZABLE_FIELDS
103
+ end
104
+ end
105
+
106
+ class AsyncMessageExt < AsyncMessage #:nodoc:
107
+ end
108
+
109
+ # Maps to <tt>flex.messaging.messages.CommandMessage</tt>
110
+ class CommandMessage < AsyncMessage
111
+ SUBSCRIBE_OPERATION = 0
112
+ UNSUSBSCRIBE_OPERATION = 1
113
+ POLL_OPERATION = 2
114
+ CLIENT_SYNC_OPERATION = 4
115
+ CLIENT_PING_OPERATION = 5
116
+ CLUSTER_REQUEST_OPERATION = 7
117
+ LOGIN_OPERATION = 8
118
+ LOGOUT_OPERATION = 9
119
+ SESSION_INVALIDATE_OPERATION = 10
120
+ MULTI_SUBSCRIBE_OPERATION = 11
121
+ DISCONNECT_OPERATION = 12
122
+ UNKNOWN_OPERATION = 10000
123
+
124
+ EXTERNALIZABLE_FIELDS = [
125
+ %w[ operation ]
126
+ ]
127
+ attr_accessor :operation
128
+
129
+ def initialize
130
+ @operation = UNKNOWN_OPERATION
131
+ end
132
+
133
+ def read_external des
134
+ super des
135
+ read_external_fields des, EXTERNALIZABLE_FIELDS
136
+ end
137
+ end
138
+
139
+ class CommandMessageExt < CommandMessage #:nodoc:
140
+ end
141
+
142
+ # Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
143
+ class AcknowledgeMessage < AsyncMessage
144
+ EXTERNALIZABLE_FIELDS = [[]]
145
+
146
+ def initialize message=nil
147
+ @clientId = rand_uuid
148
+ @destination = nil
149
+ @messageId = rand_uuid
150
+ @timestamp = Time.new.to_i*100
151
+ @timeToLive = 0
152
+ @headers = {}
153
+ @body = nil
154
+
155
+ if message.is_a?(AbstractMessage)
156
+ @correlationId = message.messageId
157
+ end
158
+ end
159
+
160
+ def read_external des
161
+ super des
162
+ read_external_fields des, EXTERNALIZABLE_FIELDS
163
+ end
164
+ end
165
+
166
+ class AcknowledgeMessageExt < AcknowledgeMessage #:nodoc:
167
+ end
168
+
169
+ # Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
170
+ class ErrorMessage < AcknowledgeMessage
171
+ # Extended data that will facilitate custom error processing on the client
172
+ attr_accessor :extendedData
173
+
174
+ # The fault code for the error, which defaults to the class name of the
175
+ # causing exception
176
+ attr_accessor :faultCode
177
+
178
+ # Detailed description of what caused the error
179
+ attr_accessor :faultDetail
180
+
181
+ # A simple description of the error
182
+ attr_accessor :faultString
183
+
184
+ # Optional "root cause" of the error
185
+ attr_accessor :rootCause
186
+
187
+ def initialize message=nil, exception=nil
188
+ super message
189
+
190
+ unless exception.nil?
191
+ @e = exception
192
+ @faultCode = @e.class.name
193
+ @faultDetail = @e.backtrace.join("\n")
194
+ @faultString = @e.message
195
+ end
196
+ end
197
+
198
+ def encode_amf serializer
199
+ if serializer.version == 0
200
+ data = {
201
+ :faultCode => @faultCode,
202
+ :faultDetail => @faultDetail,
203
+ :faultString => @faultString
204
+ }
205
+ serializer.write_object(data)
206
+ else
207
+ serializer.write_object(self)
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,13 @@
1
+ module RocketAMF
2
+ module Values #:nodoc:
3
+ # Hash-like object that can store a type string. Used to preserve type information
4
+ # for unmapped objects after deserialization.
5
+ class TypedHash < Hash
6
+ attr_reader :type
7
+
8
+ def initialize type
9
+ @type = type
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1 @@
1
+ require "rocketamf"
@@ -0,0 +1,110 @@
1
+ require "spec_helper.rb"
2
+
3
+ describe RocketAMF::ClassMapping do
4
+ before :each do
5
+ RocketAMF::ClassMapping.reset
6
+ RocketAMF::ClassMapping.define do |m|
7
+ m.map :as => 'ASClass', :ruby => 'ClassMappingTest'
8
+ end
9
+ @mapper = RocketAMF::ClassMapping.new
10
+ end
11
+
12
+ describe "class name mapping" do
13
+ it "should allow resetting of mappings back to defaults" do
14
+ @mapper.get_as_class_name('ClassMappingTest').should_not be_nil
15
+ RocketAMF::ClassMapping.reset
16
+ @mapper = RocketAMF::ClassMapping.new
17
+ @mapper.get_as_class_name('ClassMappingTest').should be_nil
18
+ @mapper.get_as_class_name('RocketAMF::Values::AcknowledgeMessage').should_not be_nil
19
+ end
20
+
21
+ it "should return AS class name for ruby objects" do
22
+ @mapper.get_as_class_name(ClassMappingTest.new).should == 'ASClass'
23
+ @mapper.get_as_class_name('ClassMappingTest').should == 'ASClass'
24
+ @mapper.get_as_class_name(RocketAMF::Values::TypedHash.new('ClassMappingTest')).should == 'ASClass'
25
+ @mapper.get_as_class_name('BadClass').should be_nil
26
+ end
27
+
28
+ it "should instantiate a ruby class" do
29
+ @mapper.get_ruby_obj('ASClass').should be_a(ClassMappingTest)
30
+ end
31
+
32
+ it "should properly instantiate namespaced classes" do
33
+ RocketAMF::ClassMapping.mappings.map :as => 'ASClass', :ruby => 'ANamespace::TestRubyClass'
34
+ @mapper = RocketAMF::ClassMapping.new
35
+ @mapper.get_ruby_obj('ASClass').should be_a(ANamespace::TestRubyClass)
36
+ end
37
+
38
+ it "should return a hash with original type if not mapped" do
39
+ obj = @mapper.get_ruby_obj('UnmappedClass')
40
+ obj.should be_a(RocketAMF::Values::TypedHash)
41
+ obj.type.should == 'UnmappedClass'
42
+ end
43
+
44
+ it "should map special classes from AS by default" do
45
+ as_classes = [
46
+ 'flex.messaging.messages.AcknowledgeMessage',
47
+ 'flex.messaging.messages.CommandMessage',
48
+ 'flex.messaging.messages.RemotingMessage'
49
+ ]
50
+
51
+ as_classes.each do |as_class|
52
+ @mapper.get_ruby_obj(as_class).should_not be_a(RocketAMF::Values::TypedHash)
53
+ end
54
+ end
55
+
56
+ it "should map special classes from ruby by default" do
57
+ ruby_classes = [
58
+ 'RocketAMF::Values::AcknowledgeMessage',
59
+ 'RocketAMF::Values::ErrorMessage'
60
+ ]
61
+
62
+ ruby_classes.each do |obj|
63
+ @mapper.get_as_class_name(obj).should_not be_nil
64
+ end
65
+ end
66
+
67
+ it "should allow config modification" do
68
+ RocketAMF::ClassMapping.mappings.map :as => 'SecondClass', :ruby => 'ClassMappingTest'
69
+ @mapper = RocketAMF::ClassMapping.new
70
+ @mapper.get_as_class_name(ClassMappingTest.new).should == 'SecondClass'
71
+ end
72
+ end
73
+
74
+ describe "ruby object populator" do
75
+ it "should populate a ruby class" do
76
+ obj = @mapper.populate_ruby_obj ClassMappingTest.new, {:prop_a => 'Data'}
77
+ obj.prop_a.should == 'Data'
78
+ end
79
+
80
+ it "should populate a typed hash" do
81
+ obj = @mapper.populate_ruby_obj RocketAMF::Values::TypedHash.new('UnmappedClass'), {:prop_a => 'Data'}
82
+ obj[:prop_a].should == 'Data'
83
+ end
84
+ end
85
+
86
+ describe "property extractor" do
87
+ it "should extract hash properties" do
88
+ hash = {:a => 'test1', 'b' => 'test2'}
89
+ props = @mapper.props_for_serialization(hash)
90
+ props.should == {'a' => 'test1', 'b' => 'test2'}
91
+ end
92
+
93
+ it "should extract object properties" do
94
+ obj = ClassMappingTest.new
95
+ obj.prop_a = 'Test A'
96
+
97
+ hash = @mapper.props_for_serialization obj
98
+ hash.should == {'prop_a' => 'Test A', 'prop_b' => nil}
99
+ end
100
+
101
+ it "should extract inherited object properties" do
102
+ obj = ClassMappingTest2.new
103
+ obj.prop_a = 'Test A'
104
+ obj.prop_c = 'Test C'
105
+
106
+ hash = @mapper.props_for_serialization obj
107
+ hash.should == {'prop_a' => 'Test A', 'prop_b' => nil, 'prop_c' => 'Test C'}
108
+ end
109
+ end
110
+ end