jimmyz-happymapper 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe HappyMapper::Attribute do
4
+ describe "initialization" do
5
+ before do
6
+ @attr = HappyMapper::Attribute.new(:foo, String)
7
+ end
8
+
9
+ it 'should know that it is an attribute' do
10
+ @attr.attribute?.should be_true
11
+ end
12
+
13
+ it 'should know that it is NOT an element' do
14
+ @attr.element?.should be_false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe HappyMapper::Element do
4
+ describe "initialization" do
5
+ before do
6
+ @attr = HappyMapper::Element.new(:foo, String)
7
+ end
8
+
9
+ it 'should know that it is an element' do
10
+ @attr.element?.should be_true
11
+ end
12
+
13
+ it 'should know that it is NOT an attribute' do
14
+ @attr.attribute?.should be_false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,94 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe HappyMapper::Item do
4
+
5
+ describe "new instance" do
6
+ before do
7
+ @item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
8
+ end
9
+
10
+ it "should accept a name" do
11
+ @item.name.should == 'foo'
12
+ end
13
+
14
+ it 'should accept a type' do
15
+ @item.type.should == String
16
+ end
17
+
18
+ it 'should accept :tag as an option' do
19
+ @item.tag.should == 'foobar'
20
+ end
21
+
22
+ it "should have a method_name" do
23
+ @item.method_name.should == 'foo'
24
+ end
25
+ end
26
+
27
+ describe "#method_name" do
28
+ it "should convert dashes to underscores" do
29
+ item = HappyMapper::Item.new(:'foo-bar', String, :tag => 'foobar')
30
+ item.method_name.should == 'foo_bar'
31
+ end
32
+ end
33
+
34
+ describe "#xpath" do
35
+ it "should default to tag" do
36
+ item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
37
+ item.xpath.should == 'foobar'
38
+ end
39
+
40
+ it "should prepend with .// if options[:deep] true" do
41
+ item = HappyMapper::Item.new(:foo, String, :tag => 'foobar', :deep => true)
42
+ item.xpath.should == './/foobar'
43
+ end
44
+
45
+ it "should prepend namespace if namespace exists" do
46
+ item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
47
+ item.namespace = 'v2'
48
+ item.xpath.should == 'v2:foobar'
49
+ end
50
+ end
51
+
52
+ describe "typecasting" do
53
+ it "should work with Strings" do
54
+ item = HappyMapper::Item.new(:foo, String)
55
+ [21, '21'].each do |a|
56
+ item.typecast(a).should == '21'
57
+ end
58
+ end
59
+
60
+ it "should work with Integers" do
61
+ item = HappyMapper::Item.new(:foo, Integer)
62
+ [21, 21.0, '21'].each do |a|
63
+ item.typecast(a).should == 21
64
+ end
65
+ end
66
+
67
+ it "should work with Floats" do
68
+ item = HappyMapper::Item.new(:foo, Float)
69
+ [21, 21.0, '21'].each do |a|
70
+ item.typecast(a).should == 21.0
71
+ end
72
+ end
73
+
74
+ it "should work with Times" do
75
+ item = HappyMapper::Item.new(:foo, Time)
76
+ item.typecast('2000-01-01 01:01:01.123456').should == Time.local(2000, 1, 1, 1, 1, 1, 123456)
77
+ end
78
+
79
+ it "should work with Dates" do
80
+ item = HappyMapper::Item.new(:foo, Date)
81
+ item.typecast('2000-01-01').should == Date.new(2000, 1, 1)
82
+ end
83
+
84
+ it "should work with DateTimes" do
85
+ item = HappyMapper::Item.new(:foo, DateTime)
86
+ item.typecast('2000-01-01 00:00:00').should == DateTime.new(2000, 1, 1, 0, 0, 0)
87
+ end
88
+
89
+ it "should work with Boolean" do
90
+ item = HappyMapper::Item.new(:foo, Boolean)
91
+ item.typecast('false').should == false
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,666 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/xml_helper.rb'
3
+ require 'pp'
4
+ require 'uri'
5
+
6
+ class Address
7
+ include HappyMapper
8
+
9
+ tag 'address'
10
+ element :street, String
11
+ element :postcode, String
12
+ element :housenumber, String
13
+ element :city, String
14
+ element :country, String
15
+ end
16
+
17
+ class Feature
18
+ include HappyMapper
19
+ element :name, String, :tag => '.|.//text()'
20
+ end
21
+
22
+ class FeatureBullet
23
+ include HappyMapper
24
+
25
+ tag 'features_bullets'
26
+ has_many :features, Feature
27
+ element :bug, String
28
+ end
29
+
30
+ class Product
31
+ include HappyMapper
32
+
33
+ element :title, String
34
+ has_one :feature_bullets, FeatureBullet
35
+ has_one :address, Address
36
+ end
37
+
38
+ module FamilySearch
39
+ class AlternateIds
40
+ include HappyMapper
41
+
42
+ namespace 'fsapi-v1' => 'http://api.familysearch.org/v1'
43
+ tag 'alternateIds'
44
+ has_many :ids, String, :tag => 'id'
45
+ end
46
+
47
+ class Information
48
+ include HappyMapper
49
+
50
+ namespace 'fsapi-v1' => 'http://api.familysearch.org/v1'
51
+ has_one :alternateIds, AlternateIds
52
+ element :gender, String
53
+ element :living, Boolean
54
+ end
55
+
56
+ class Person
57
+ include HappyMapper
58
+
59
+ namespace_url 'http://api.familysearch.org/familytree/v1'
60
+ attribute :version, String
61
+ attribute :modified, Time
62
+ attribute :id, String
63
+ has_one :information, Information
64
+ end
65
+
66
+ class Persons
67
+ include HappyMapper
68
+
69
+ namespace_url 'http://api.familysearch.org/familytree/v1'
70
+ has_many :person, Person
71
+ end
72
+
73
+ class FamilyTree
74
+ include HappyMapper
75
+
76
+ tag 'familytree'
77
+ namespace_url 'http://api.familysearch.org/familytree/v1'
78
+ attribute :version, String
79
+ attribute :status_message, String, :tag => 'statusMessage'
80
+ attribute :status_code, String, :tag => 'statusCode'
81
+ has_one :persons, Persons
82
+ end
83
+ end
84
+
85
+ module FedEx
86
+ class Address
87
+ include HappyMapper
88
+
89
+ tag 'Address'
90
+ namespace 'v2'
91
+ element :city, String, :tag => 'City'
92
+ element :state, String, :tag => 'StateOrProvinceCode'
93
+ element :zip, String, :tag => 'PostalCode'
94
+ element :countrycode, String, :tag => 'CountryCode'
95
+ element :residential, Boolean, :tag => 'Residential'
96
+ end
97
+
98
+ class Event
99
+ include HappyMapper
100
+
101
+ tag 'Events'
102
+ namespace 'v2'
103
+ element :timestamp, String, :tag => 'Timestamp'
104
+ element :eventtype, String, :tag => 'EventType'
105
+ element :eventdescription, String, :tag => 'EventDescription'
106
+ has_one :address, Address
107
+ end
108
+
109
+ class PackageWeight
110
+ include HappyMapper
111
+
112
+ tag 'PackageWeight'
113
+ namespace 'v2'
114
+ element :units, String, :tag => 'Units'
115
+ element :value, Integer, :tag => 'Value'
116
+ end
117
+
118
+ class TrackDetails
119
+ include HappyMapper
120
+
121
+ tag 'TrackDetails'
122
+ namespace 'v2'
123
+ element :tracking_number, String, :tag => 'TrackingNumber'
124
+ element :status_code, String, :tag => 'StatusCode'
125
+ element :status_desc, String, :tag => 'StatusDescription'
126
+ element :carrier_code, String, :tag => 'CarrierCode'
127
+ element :service_info, String, :tag => 'ServiceInfo'
128
+ has_one :weight, PackageWeight, :tag => 'PackageWeight'
129
+ element :est_delivery, String, :tag => 'EstimatedDeliveryTimestamp'
130
+ has_many :events, Event
131
+ end
132
+
133
+ class Notification
134
+ include HappyMapper
135
+
136
+ tag 'Notifications'
137
+ namespace 'v2'
138
+ element :severity, String, :tag => 'Severity'
139
+ element :source, String, :tag => 'Source'
140
+ element :code, Integer, :tag => 'Code'
141
+ element :message, String, :tag => 'Message'
142
+ element :localized_message, String, :tag => 'LocalizedMessage'
143
+ end
144
+
145
+ class TransactionDetail
146
+ include HappyMapper
147
+
148
+ tag 'TransactionDetail'
149
+ namespace 'v2'
150
+ element :cust_tran_id, String, :tag => 'CustomerTransactionId'
151
+ end
152
+
153
+ class TrackReply
154
+ include HappyMapper
155
+
156
+ tag 'TrackReply'
157
+ namespace 'v2'
158
+ element :highest_severity, String, :tag => 'HighestSeverity'
159
+ element :more_data, Boolean, :tag => 'MoreData'
160
+ has_many :notifications, Notification, :tag => 'Notifications'
161
+ has_many :trackdetails, TrackDetails, :tag => 'TrackDetails'
162
+ has_one :tran_detail, TransactionDetail, :tab => 'TransactionDetail'
163
+ end
164
+ end
165
+
166
+ class Place
167
+ include HappyMapper
168
+ element :name, String
169
+ end
170
+
171
+ class Radar
172
+ include HappyMapper
173
+ has_many :places, Place
174
+ end
175
+
176
+ class Post
177
+ include HappyMapper
178
+
179
+ attribute :href, String
180
+ attribute :hash, String
181
+ attribute :description, String
182
+ attribute :tag, String
183
+ attribute :time, Time
184
+ attribute :others, Integer
185
+ attribute :extended, String
186
+ end
187
+
188
+ class Posts
189
+ include HappyMapper
190
+
191
+ attribute :user, String
192
+ attribute :tag, String
193
+ has_many :post, Post
194
+ end
195
+
196
+ class User
197
+ include HappyMapper
198
+
199
+ element :id, Integer
200
+ element :name, String
201
+ element :screen_name, String
202
+ element :location, String
203
+ element :description, String
204
+ element :profile_image_url, String
205
+ element :url, String
206
+ element :protected, Boolean
207
+ element :followers_count, Integer
208
+ end
209
+
210
+ class Status
211
+ include HappyMapper
212
+
213
+ element :id, Integer
214
+ element :text, String
215
+ element :created_at, Time
216
+ element :source, String
217
+ element :truncated, Boolean
218
+ element :in_reply_to_status_id, Integer
219
+ element :in_reply_to_user_id, Integer
220
+ element :favorited, Boolean
221
+ element :non_existent, String, :tag => 'dummy', :namespace => 'fake'
222
+ has_one :user, User
223
+ end
224
+
225
+ class CurrentWeather
226
+ include HappyMapper
227
+
228
+ tag 'ob'
229
+ namespace 'aws'
230
+ element :temperature, Integer, :tag => 'temp'
231
+ element :feels_like, Integer, :tag => 'feels-like'
232
+ element :current_condition, String, :tag => 'current-condition', :attributes => {:icon => String}
233
+ end
234
+
235
+ # for type coercion
236
+ class ProductGroup < String; end
237
+
238
+ module PITA
239
+ class Item
240
+ include HappyMapper
241
+
242
+ tag 'Item' # if you put class in module you need tag
243
+ element :asin, String, :tag => 'ASIN'
244
+ element :detail_page_url, URI, :tag => 'DetailPageURL', :parser => :parse
245
+ element :manufacturer, String, :tag => 'Manufacturer', :deep => true
246
+ element :point, String, :tag => 'point', :namespace => 'georss'
247
+ element :product_group, ProductGroup, :tag => 'ProductGroup', :deep => true, :parser => :new, :raw => true
248
+ end
249
+
250
+ class Items
251
+ include HappyMapper
252
+
253
+ tag 'Items' # if you put class in module you need tag
254
+ element :total_results, Integer, :tag => 'TotalResults'
255
+ element :total_pages, Integer, :tag => 'TotalPages'
256
+ has_many :items, Item
257
+ end
258
+ end
259
+
260
+ module GitHub
261
+ class Commit
262
+ include HappyMapper
263
+
264
+ tag "commit"
265
+ element :url, String
266
+ element :tree, String
267
+ element :message, String
268
+ element :id, String
269
+ element :'committed-date', Date
270
+ end
271
+ end
272
+
273
+ describe HappyMapper do
274
+
275
+ describe "being included into another class" do
276
+ before do
277
+ Foo.instance_variable_set("@attributes", {})
278
+ Foo.instance_variable_set("@elements", {})
279
+ end
280
+ class Foo; include HappyMapper end
281
+
282
+ it "should set attributes to an array" do
283
+ Foo.attributes.should == []
284
+ end
285
+
286
+ it "should set @elements to a hash" do
287
+ Foo.elements.should == []
288
+ end
289
+
290
+ it "should allow adding an attribute" do
291
+ lambda {
292
+ Foo.attribute :name, String
293
+ }.should change(Foo, :attributes)
294
+ end
295
+
296
+ it "should allow adding an attribute containing a dash" do
297
+ lambda {
298
+ Foo.attribute :'bar-baz', String
299
+ }.should change(Foo, :attributes)
300
+ end
301
+
302
+ it "should be able to get all attributes in array" do
303
+ Foo.attribute :name, String
304
+ Foo.attributes.size.should == 1
305
+ end
306
+
307
+ it "should allow adding an element" do
308
+ lambda {
309
+ Foo.element :name, String
310
+ }.should change(Foo, :elements)
311
+ end
312
+
313
+ it "should allow adding an element containing a dash" do
314
+ lambda {
315
+ Foo.element :'bar-baz', String
316
+ }.should change(Foo, :elements)
317
+
318
+ end
319
+
320
+ it "should be able to get all elements in array" do
321
+ Foo.element(:name, String)
322
+ Foo.elements.size.should == 1
323
+ end
324
+
325
+ it "should allow has one association" do
326
+ Foo.has_one(:user, User)
327
+ element = Foo.elements.first
328
+ element.name.should == 'user'
329
+ element.type.should == User
330
+ element.options[:single] = true
331
+ end
332
+
333
+ it "should allow has many association" do
334
+ Foo.has_many(:users, User)
335
+ element = Foo.elements.first
336
+ element.name.should == 'users'
337
+ element.type.should == User
338
+ element.options[:single] = false
339
+ end
340
+
341
+ it "should default tag name to lowercase class" do
342
+ Foo.tag_name.should == 'foo'
343
+ end
344
+
345
+ it "should default tag name of class in modules to the last constant lowercase" do
346
+ module Bar; class Baz; include HappyMapper; end; end
347
+ Bar::Baz.tag_name.should == 'baz'
348
+ end
349
+
350
+ it "should allow setting tag name" do
351
+ Foo.tag('FooBar')
352
+ Foo.tag_name.should == 'FooBar'
353
+ end
354
+
355
+ it "should allow setting a namespace" do
356
+ Foo.namespace(namespace = "foo")
357
+ Foo.namespace.should == namespace
358
+ end
359
+
360
+ # This is important for allowing serializing of elements with namespaces
361
+ it "should allow setting a full namespace (prefix and url) via a hash" do
362
+ Foo.namespace(namespace = {'example-v1' => 'http://example.com/v1'})
363
+ Foo.namespace.should == 'example-v1'
364
+ Foo.namespace_url.should == 'http://example.com/v1'
365
+ end
366
+
367
+ # Prefixes might change for a given namespace. It is safer to namespace by url.
368
+ it "should allow setting a namespace url" do
369
+ Foo.namespace_url(url = 'http://example.com/namespace')
370
+ Foo.namespace_url.should == url
371
+ end
372
+
373
+ it "should provide #parse" do
374
+ Foo.should respond_to(:parse)
375
+ end
376
+ end
377
+
378
+ describe "#attributes" do
379
+ it "should only return attributes for the current class" do
380
+ Post.attributes.size.should == 7
381
+ Status.attributes.size.should == 0
382
+ end
383
+ end
384
+
385
+ describe "#elements" do
386
+ it "should only return elements for the current class" do
387
+ Post.elements.size.should == 0
388
+ Status.elements.size.should == 10
389
+ end
390
+ end
391
+
392
+ it "should parse xml attributes into ruby objects" do
393
+ posts = Post.parse(fixture_file('posts.xml'))
394
+ posts.size.should == 20
395
+ first = posts.first
396
+ first.href.should == 'http://roxml.rubyforge.org/'
397
+ first.hash.should == '19bba2ab667be03a19f67fb67dc56917'
398
+ first.description.should == 'ROXML - Ruby Object to XML Mapping Library'
399
+ first.tag.should == 'ruby xml gems mapping'
400
+ first.time.should == Time.utc(2008, 8, 9, 5, 24, 20)
401
+ first.others.should == 56
402
+ first.extended.should == 'ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML. Using simple annotations, it enables Ruby classes to be custom-mapped to XML. ROXML takes care of the marshalling and unmarshalling of mapped attributes so that developers can focus on building first-class Ruby classes.'
403
+ end
404
+
405
+ it "should parse xml elements to ruby objcts" do
406
+ statuses = Status.parse(fixture_file('statuses.xml'))
407
+ statuses.size.should == 20
408
+ first = statuses.first
409
+ first.id.should == 882281424
410
+ first.created_at.should == Time.utc(2008, 8, 9, 5, 38, 12)
411
+ first.source.should == 'web'
412
+ first.truncated.should be_false
413
+ first.in_reply_to_status_id.should == 1234
414
+ first.in_reply_to_user_id.should == 12345
415
+ first.favorited.should be_false
416
+ first.user.id.should == 4243
417
+ first.user.name.should == 'John Nunemaker'
418
+ first.user.screen_name.should == 'jnunemaker'
419
+ first.user.location.should == 'Mishawaka, IN, US'
420
+ first.user.description.should == 'Loves his wife, ruby, notre dame football and iu basketball'
421
+ first.user.profile_image_url.should == 'http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg'
422
+ first.user.url.should == 'http://addictedtonew.com'
423
+ first.user.protected.should be_false
424
+ first.user.followers_count.should == 486
425
+ end
426
+
427
+ it "should parse xml containing the desired element as root node" do
428
+ address = Address.parse(fixture_file('address.xml'), :single => true)
429
+ address.street.should == 'Milchstrasse'
430
+ address.postcode.should == '26131'
431
+ address.housenumber.should == '23'
432
+ address.city.should == 'Oldenburg'
433
+ address.country.should == 'Germany'
434
+ end
435
+
436
+ it "should parse xml with default namespace (amazon)" do
437
+ file_contents = fixture_file('pita.xml')
438
+ items = PITA::Items.parse(file_contents, :single => true)
439
+ items.total_results.should == 22
440
+ items.total_pages.should == 3
441
+ first = items.items[0]
442
+ second = items.items[1]
443
+ first.asin.should == '0321480791'
444
+ first.point.should == '38.5351715088 -121.7948684692'
445
+ first.detail_page_url.should be_a_kind_of(URI)
446
+ first.detail_page_url.to_s.should == 'http://www.amazon.com/gp/redirect.html%3FASIN=0321480791%26tag=ws%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0321480791%253FSubscriptionId=dontbeaswoosh'
447
+ first.manufacturer.should == 'Addison-Wesley Professional'
448
+ first.product_group.should == '<ProductGroup>Book</ProductGroup>'
449
+ second.asin.should == '047022388X'
450
+ second.manufacturer.should == 'Wrox'
451
+ end
452
+
453
+ it "should parse xml that has attributes of elements" do
454
+ items = CurrentWeather.parse(fixture_file('current_weather.xml'))
455
+ first = items[0]
456
+ first.temperature.should == 51
457
+ first.feels_like.should == 51
458
+ first.current_condition.should == 'Sunny'
459
+ first.current_condition.icon.should == 'http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif'
460
+ end
461
+
462
+ it "should parse xml with nested elements" do
463
+ radars = Radar.parse(fixture_file('radar.xml'))
464
+ first = radars[0]
465
+ first.places.size.should == 1
466
+ first.places[0].name.should == 'Store'
467
+ second = radars[1]
468
+ second.places.size.should == 0
469
+ third = radars[2]
470
+ third.places.size.should == 2
471
+ third.places[0].name.should == 'Work'
472
+ third.places[1].name.should == 'Home'
473
+ end
474
+
475
+ it "should parse xml that has elements with dashes" do
476
+ commit = GitHub::Commit.parse(fixture_file('commit.xml'))
477
+ commit.message.should == "move commands.rb and helpers.rb into commands/ dir"
478
+ commit.url.should == "http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
479
+ commit.id.should == "c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
480
+ commit.committed_date.should == Date.parse("2008-03-02T16:45:41-08:00")
481
+ commit.tree.should == "28a1a1ca3e663d35ba8bf07d3f1781af71359b76"
482
+ end
483
+
484
+ it "should parse xml with no namespace" do
485
+ product = Product.parse(fixture_file('product_no_namespace.xml'), :single => true)
486
+ product.title.should == "A Title"
487
+ product.feature_bullets.bug.should == 'This is a bug'
488
+ product.feature_bullets.features.size.should == 2
489
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
490
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
491
+ end
492
+
493
+ it "should parse xml with default namespace" do
494
+ product = Product.parse(fixture_file('product_default_namespace.xml'), :single => true)
495
+ product.title.should == "A Title"
496
+ product.feature_bullets.bug.should == 'This is a bug'
497
+ product.feature_bullets.features.size.should == 2
498
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
499
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
500
+ end
501
+
502
+ it "should parse xml with single namespace" do
503
+ product = Product.parse(fixture_file('product_single_namespace.xml'), :single => true)
504
+ product.title.should == "A Title"
505
+ product.feature_bullets.bug.should == 'This is a bug'
506
+ product.feature_bullets.features.size.should == 2
507
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
508
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
509
+ end
510
+
511
+ it "should parse xml with multiple namespaces" do
512
+ track = FedEx::TrackReply.parse(fixture_file('multiple_namespaces.xml'))
513
+ track.highest_severity.should == 'SUCCESS'
514
+ track.more_data.should be_false
515
+ notification = track.notifications.first
516
+ notification.code.should == 0
517
+ notification.localized_message.should == 'Request was successfully processed.'
518
+ notification.message.should == 'Request was successfully processed.'
519
+ notification.severity.should == 'SUCCESS'
520
+ notification.source.should == 'trck'
521
+ detail = track.trackdetails.first
522
+ detail.carrier_code.should == 'FDXG'
523
+ detail.est_delivery.should == '2009-01-02T00:00:00'
524
+ detail.service_info.should == 'Ground-Package Returns Program-Domestic'
525
+ detail.status_code.should == 'OD'
526
+ detail.status_desc.should == 'On FedEx vehicle for delivery'
527
+ detail.tracking_number.should == '9611018034267800045212'
528
+ detail.weight.units.should == 'LB'
529
+ detail.weight.value.should == 2
530
+ events = detail.events
531
+ events.size.should == 10
532
+ first_event = events[0]
533
+ first_event.eventdescription.should == 'On FedEx vehicle for delivery'
534
+ first_event.eventtype.should == 'OD'
535
+ first_event.timestamp.should == '2009-01-02T06:00:00'
536
+ first_event.address.city.should == 'WICHITA'
537
+ first_event.address.countrycode.should == 'US'
538
+ first_event.address.residential.should be_false
539
+ first_event.address.state.should == 'KS'
540
+ first_event.address.zip.should == '67226'
541
+ last_event = events[-1]
542
+ last_event.eventdescription.should == 'In FedEx possession'
543
+ last_event.eventtype.should == 'IP'
544
+ last_event.timestamp.should == '2008-12-27T09:40:00'
545
+ last_event.address.city.should == 'LONGWOOD'
546
+ last_event.address.countrycode.should == 'US'
547
+ last_event.address.residential.should be_false
548
+ last_event.address.state.should == 'FL'
549
+ last_event.address.zip.should == '327506398'
550
+ track.tran_detail.cust_tran_id.should == '20090102-111321'
551
+ end
552
+
553
+ it "should parse family search xml" do
554
+ tree = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
555
+ tree.version.should == '1.0.20071213.942'
556
+ tree.status_message.should == 'OK'
557
+ tree.status_code.should == '200'
558
+ tree.persons.person.size.should == 1
559
+ tree.persons.person.first.version.should == '1199378491000'
560
+ tree.persons.person.first.modified.should == Time.utc(2008, 1, 3, 16, 41, 31) # 2008-01-03T09:41:31-07:00
561
+ tree.persons.person.first.id.should == 'KWQS-BBQ'
562
+ # namespaced elements (by url)
563
+ tree.persons.person.first.information.alternateIds.ids.should_not be_kind_of(String)
564
+ tree.persons.person.first.information.alternateIds.ids.size.should == 8
565
+ tree.persons.person.first.information.gender.should == 'Male'
566
+ tree.persons.person.first.information.living.should == false
567
+ end
568
+
569
+ it "should not set values for missing attributes" do
570
+ posts = Post.parse(fixture_file("partial_posts.xml"))
571
+ first = posts.first
572
+ first.href.should be_nil
573
+ first.hash.should be_nil
574
+ first.description.should be_nil
575
+ first.tag.should be_nil
576
+ first.time.should be_nil
577
+ first.others.should be_nil
578
+ first.extended.should be_nil
579
+ end
580
+
581
+ describe "serializing to xml" do
582
+
583
+ it "should return xml with elements representing the objects" do
584
+ address = Address.parse(fixture_file('address.xml'), :single => true)
585
+ xml = address.to_xml
586
+ xml.should be_kind_of(String)
587
+ # Would love to use libxml-ruby if xml_helper.rb were converted
588
+ doc = REXML::Document.new xml
589
+ doc.should have_nodes("/address", 1)
590
+ doc.should have_nodes("/address/street", 1)
591
+ doc.should match_xpath("/address/street","Milchstrasse")
592
+ doc.should match_xpath("/address/housenumber","23")
593
+ doc.should match_xpath("/address/postcode","26131")
594
+ doc.should match_xpath("/address/city","Oldenburg")
595
+ doc.should match_xpath("/address/country","Germany")
596
+ end
597
+
598
+ it "should return xml with non-primitive elements from ruby objects" do
599
+ posts = Posts.parse(fixture_file('posts.xml'))
600
+ xml = posts.to_xml
601
+ doc = REXML::Document.new xml
602
+ doc.should have_nodes("/posts",1)
603
+ doc.should have_nodes("/posts/post",20)
604
+ end
605
+
606
+ it "should return xml with attributes representing 'attribute' objects" do
607
+ posts = Posts.parse(fixture_file('posts.xml'))
608
+ xml = posts.to_xml
609
+ doc = REXML::Document.new xml
610
+ doc.should have_nodes("/posts/post",20)
611
+ doc.should match_xpath("/posts/post[1]/@href","http://roxml.rubyforge.org/")
612
+ doc.should match_xpath("/posts/post[1]/@hash","19bba2ab667be03a19f67fb67dc56917")
613
+ doc.should match_xpath("/posts/post[1]/@description","ROXML - Ruby Object to XML Mapping Library")
614
+ doc.should match_xpath("/posts/post[1]/@tag","ruby xml gems mapping")
615
+ doc.should match_xpath("/posts/post[1]/@others","56")
616
+ doc.should match_xpath("/posts/post[1]/@extended","ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML. Using simple annotations, it enables Ruby classes to be custom-mapped to XML. ROXML takes care of the marshalling and unmarshalling of mapped attributes so that developers can focus on building first-class Ruby classes.")
617
+ end
618
+
619
+ it "should be able to create new objects and serialize to xml" do
620
+ posts = Posts.new
621
+ posts.user = 'jimmyz'
622
+ posts.tag = 'happymapper'
623
+ posts.post << Post.new
624
+ posts.post << Post.new
625
+ xml = posts.to_xml
626
+ doc = REXML::Document.new xml
627
+ doc.should have_nodes("/posts",1)
628
+ doc.should have_nodes("/posts/post", 2)
629
+ doc.should match_xpath("/posts/@user",'jimmyz')
630
+ doc.should match_xpath("/posts/@tag",'happymapper')
631
+ end
632
+
633
+ it "should not serialize attributes or elements that are nil" do
634
+ posts = Posts.new
635
+ xml = posts.to_xml
636
+ doc = REXML::Document.new xml
637
+ doc.should have_nodes("/posts",1)
638
+ doc.should have_nodes("/posts/post",0)
639
+ doc.should have_nodes("/posts/@user",0)
640
+ end
641
+
642
+ describe "with namespace url and prefix" do
643
+ before(:all) do
644
+ ft = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
645
+ xml = ft.to_xml
646
+ @doc = REXML::Document.new xml
647
+ @namespaces = {
648
+ 'fsapi-v1' => 'http://api.familysearch.org/v1',
649
+ 'fs' => 'http://api.familysearch.org/familytree/v1'
650
+ }
651
+ end
652
+
653
+ it "should add namespaces to the root element that is being serialized" do
654
+ @doc.should match_xpath("/fs:familytree/@xmlns:fsapi-v1",'http://api.familysearch.org/v1',@namespaces)
655
+ end
656
+
657
+ it "should serialize elements with a namespace url and prefix" do
658
+ base_xpath = "/fs:familytree/fs:persons/fs:person[1]"
659
+ @doc.should have_nodes("#{base_xpath}/fsapi-v1:information",1,@namespaces)
660
+ @doc.should have_nodes("#{base_xpath}/fsapi-v1:information/fsapi-v1:alternateIds",1,@namespaces)
661
+ @doc.should have_nodes("#{base_xpath}/fsapi-v1:information/fsapi-v1:alternateIds/fsapi-v1:id",8,@namespaces)
662
+ end
663
+ end
664
+
665
+ end
666
+ end