rocketamf_pure 1.0.0

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 (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