diff --git a/.rubocop.yml b/.rubocop.yml
index 763aab8..725d370 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -49,6 +49,11 @@ Naming/PredicateName:
- 'has_many'
- 'has_xml_content'
+# regression specs do not describe classes
+RSpec/DescribeClass:
+ Exclude:
+ - 'spec/regressions/*.rb'
+
# Disabled because RSpec/SpecFilePathFormat is automatically enabled as a new cop
RSpec/FilePath:
Enabled: false
diff --git a/spec/features/inline_element_attributes_spec.rb b/spec/features/inline_element_attributes_spec.rb
new file mode 100644
index 0000000..142bc8d
--- /dev/null
+++ b/spec/features/inline_element_attributes_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "specifying element attributes inline" do
+ let(:currentweather_klass) do
+ Class.new do
+ include HappyMapper
+
+ tag "ob"
+ namespace "aws"
+ element :temperature, Integer, tag: "temp"
+ element :feels_like, Integer, tag: "feels-like"
+ element :current_condition, String, tag: "current-condition",
+ attributes: { icon: String }
+ end
+ end
+
+ let(:atomfeed_klass) do
+ Class.new do
+ include HappyMapper
+ tag "feed"
+
+ attribute :xmlns, String, single: true
+ element :id, String, single: true
+ element :title, String, single: true
+ element :updated, DateTime, single: true
+ element :link, String, single: false, attributes: {
+ rel: String,
+ type: String,
+ href: String
+ }
+ end
+ end
+
+ it "adds the values of the attributes to the element" do
+ items = currentweather_klass.parse(fixture_file("current_weather.xml"))
+ first = items[0]
+
+ aggregate_failures do
+ expect(first.temperature).to eq(51)
+ expect(first.feels_like).to eq(51)
+ expect(first.current_condition).to eq("Sunny")
+ expect(first.current_condition.icon).to eq("http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif")
+ end
+ end
+
+ it "parses xml when the element with embedded attributes is not present in the xml" do
+ expect do
+ currentweather_klass.parse(fixture_file("current_weather_missing_elements.xml"))
+ end.not_to raise_error
+ end
+
+ it "parses xml with attributes of elements that aren't :single => true" do
+ feed = atomfeed_klass.parse(fixture_file("atom.xml"))
+
+ aggregate_failures do
+ expect(feed.link.first.href).to eq("http://www.example.com")
+ expect(feed.link.last.href).to eq("http://www.example.com/tv_shows.atom")
+ end
+ end
+end
diff --git a/spec/features/optional_attributes_spec.rb b/spec/features/optional_attributes_spec.rb
new file mode 100644
index 0000000..b246832
--- /dev/null
+++ b/spec/features/optional_attributes_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.describe "Parsing optional attributes" do
+ before do
+ klass = Class.new do
+ include HappyMapper
+ tag "address"
+
+ attribute :street, String
+ end
+ stub_const "OptionalAttribute", klass
+ end
+
+ let(:parsed_result) { OptionalAttribute.parse(fixture_file("optional_attributes.xml")) }
+
+ it "parses an empty String as empty" do
+ expect(parsed_result[0].street).to eq("")
+ end
+
+ it "parses a String with value" do
+ expect(parsed_result[1].street).to eq("Milchstrasse")
+ end
+
+ it "parses an element with no value for the attribute" do
+ expect(parsed_result[2].street).to be_nil
+ end
+end
diff --git a/spec/happymapper_spec.rb b/spec/happymapper_spec.rb
index e0909ba..36bc384 100644
--- a/spec/happymapper_spec.rb
+++ b/spec/happymapper_spec.rb
@@ -1,91 +1,6 @@
# frozen_string_literal: true
require "spec_helper"
-require "uri"
-
-module Analytics
- class Property
- include HappyMapper
-
- tag "property"
- namespace "dxp"
- attribute :name, String
- attribute :value, String
- end
-
- class Goal
- include HappyMapper
-
- # Google Analytics does a dirtry trick where a user with no goals
- # returns a profile without any goals data or the declared namespace
- # which means Nokogiri does not pick up the namespace automatically.
- # To fix this, we manually register the namespace to avoid bad XPath
- # expression. Dirty, but works.
-
- register_namespace "ga", "http://schemas.google.com/ga/2009"
- namespace "ga"
-
- tag "goal"
- attribute :active, Boolean
- attribute :name, String
- attribute :number, Integer
- attribute :value, Float
-
- def clean_name
- name.gsub("ga:", "")
- end
- end
-
- class Profile
- include HappyMapper
-
- tag "entry"
- element :title, String
- element :tableId, String, namespace: "dxp"
-
- has_many :properties, Property
- has_many :goals, Goal
- end
-
- class Entry
- include HappyMapper
-
- tag "entry"
- element :id, String
- element :updated, DateTime
- element :title, String
- element :table_id, String, namespace: "dxp", tag: "tableId"
- has_many :properties, Property
- end
-
- class Feed
- include HappyMapper
-
- tag "feed"
- element :id, String
- element :updated, DateTime
- element :title, String
- has_many :entries, Entry
- end
-end
-
-module Atom
- class Feed
- include HappyMapper
- tag "feed"
-
- attribute :xmlns, String, single: true
- element :id, String, single: true
- element :title, String, single: true
- element :updated, DateTime, single: true
- element :link, String, single: false, attributes: {
- rel: String,
- type: String,
- href: String
- }
- # has_many :entries, Entry # nothing interesting in the entries
- end
-end
class Country
include HappyMapper
@@ -137,126 +52,6 @@ class Rate
include HappyMapper
end
-module FamilySearch
- class AlternateIds
- include HappyMapper
-
- tag "alternateIds"
- has_many :ids, String, tag: "id"
- end
-
- class Information
- include HappyMapper
-
- has_one :alternateIds, AlternateIds
- end
-
- class Person
- include HappyMapper
-
- attribute :version, String
- attribute :modified, Time
- attribute :id, String
- has_one :information, Information
- end
-
- class Persons
- include HappyMapper
- has_many :person, Person
- end
-
- class FamilyTree
- include HappyMapper
-
- tag "familytree"
- attribute :version, String
- attribute :status_message, String, tag: "statusMessage"
- attribute :status_code, String, tag: "statusCode"
- has_one :persons, Persons
- end
-end
-
-module FedEx
- class Address
- include HappyMapper
-
- tag "Address"
- namespace "v2"
- element :city, String, tag: "City"
- element :state, String, tag: "StateOrProvinceCode"
- element :zip, String, tag: "PostalCode"
- element :countrycode, String, tag: "CountryCode"
- element :residential, Boolean, tag: "Residential"
- end
-
- class Event
- include HappyMapper
-
- tag "Events"
- namespace "v2"
- element :timestamp, String, tag: "Timestamp"
- element :eventtype, String, tag: "EventType"
- element :eventdescription, String, tag: "EventDescription"
- has_one :address, Address
- end
-
- class PackageWeight
- include HappyMapper
-
- tag "PackageWeight"
- namespace "v2"
- element :units, String, tag: "Units"
- element :value, Integer, tag: "Value"
- end
-
- class TrackDetails
- include HappyMapper
-
- tag "TrackDetails"
- namespace "v2"
- element :tracking_number, String, tag: "TrackingNumber"
- element :status_code, String, tag: "StatusCode"
- element :status_desc, String, tag: "StatusDescription"
- element :carrier_code, String, tag: "CarrierCode"
- element :service_info, String, tag: "ServiceInfo"
- has_one :weight, PackageWeight, tag: "PackageWeight"
- element :est_delivery, String, tag: "EstimatedDeliveryTimestamp"
- has_many :events, Event
- end
-
- class Notification
- include HappyMapper
-
- tag "Notifications"
- namespace "v2"
- element :severity, String, tag: "Severity"
- element :source, String, tag: "Source"
- element :code, Integer, tag: "Code"
- element :message, String, tag: "Message"
- element :localized_message, String, tag: "LocalizedMessage"
- end
-
- class TransactionDetail
- include HappyMapper
-
- tag "TransactionDetail"
- namespace "v2"
- element :cust_tran_id, String, tag: "CustomerTransactionId"
- end
-
- class TrackReply
- include HappyMapper
-
- tag "TrackReply"
- namespace "v2"
- element :highest_severity, String, tag: "HighestSeverity"
- element :more_data, Boolean, tag: "MoreData"
- has_many :notifications, Notification, tag: "Notifications"
- has_many :trackdetails, TrackDetails, tag: "TrackDetails"
- has_one :tran_detail, TransactionDetail, tab: "TransactionDetail"
- end
-end
-
class Place
include HappyMapper
element :name, String
@@ -267,98 +62,6 @@ class Radar
has_many :places, Place, tag: :place
end
-class Post
- include HappyMapper
-
- attribute :href, String
- attribute :hash, String
- attribute :description, String
- attribute :tag, String
- attribute :time, Time
- attribute :others, Integer
- attribute :extended, String
-end
-
-class User
- include HappyMapper
-
- element :id, Integer
- element :name, String
- element :screen_name, String
- element :location, String
- element :description, String
- element :profile_image_url, String
- element :url, String
- element :protected, Boolean
- element :followers_count, Integer
-end
-
-class Status
- include HappyMapper
-
- register_namespace "fake", "faka:namespace"
-
- element :id, Integer
- element :text, String
- element :created_at, Time
- element :source, String
- element :truncated, Boolean
- element :in_reply_to_status_id, Integer
- element :in_reply_to_user_id, Integer
- element :favorited, Boolean
- element :non_existent, String, tag: "dummy", namespace: "fake"
- has_one :user, User
-end
-
-class CurrentWeather
- include HappyMapper
-
- tag "ob"
- namespace "aws"
- element :temperature, Integer, tag: "temp"
- element :feels_like, Integer, tag: "feels-like"
- element :current_condition, String, tag: "current-condition", attributes: { icon: String }
-end
-
-# for type coercion
-class ProductGroup < String; end
-
-module PITA
- class Item
- include HappyMapper
-
- tag "Item" # if you put class in module you need tag
- element :asin, String, tag: "ASIN"
- element :detail_page_url, URI, tag: "DetailPageURL", parser: :parse
- element :manufacturer, String, tag: "Manufacturer", deep: true
- element :point, String, tag: "point", namespace: "georss"
- element :product_group, ProductGroup, tag: "ProductGroup", deep: true,
- parser: :new, raw: true
- end
-
- class Items
- include HappyMapper
-
- tag "Items" # if you put class in module you need tag
- element :total_results, Integer, tag: "TotalResults"
- element :total_pages, Integer, tag: "TotalPages"
- has_many :items, Item
- end
-end
-
-module GitHub
- class Commit
- include HappyMapper
-
- tag "commit"
- element :url, String
- element :tree, String
- element :message, String
- element :id, String
- element :"committed-date", Date
- end
-end
-
module QuarterTest
class Quarter
include HappyMapper
@@ -398,14 +101,6 @@ class Artist
element :name, String
end
-class Location
- include HappyMapper
-
- tag "point"
- namespace "geo"
- element :latitude, String, tag: "lat"
-end
-
# Testing the XmlContent type
module Dictionary
class Variant
@@ -527,13 +222,6 @@ class Video
element :publish_options, PublishOptions, tag: "publishOptions", namespace: "video"
end
-class OptionalAttribute
- include HappyMapper
- tag "address"
-
- attribute :street, String
-end
-
class DefaultNamespaceCombi
include HappyMapper
@@ -563,9 +251,10 @@ class Thing
describe HappyMapper do
describe "being included into another class" do
let(:klass) do
- Class.new do
- include HappyMapper
- end
+ Class.new { include HappyMapper }
+ end
+ let(:nested_klass) do
+ Class.new { include HappyMapper }
end
it "sets attributes to an array" do
@@ -611,23 +300,23 @@ class Thing
end
it "allows has one association" do
- klass.has_one(:user, User)
+ klass.has_one(:user, nested_klass)
element = klass.elements.first
aggregate_failures do
expect(element.name).to eq("user")
- expect(element.type).to eq(User)
+ expect(element.type).to eq(nested_klass)
expect(element.options[:single]).to be(true)
end
end
it "allows has many association" do
- klass.has_many(:users, User)
+ klass.has_many(:users, nested_klass)
element = klass.elements.first
aggregate_failures do
expect(element.name).to eq("users")
- expect(element.type).to eq(User)
+ expect(element.type).to eq(nested_klass)
expect(element.options[:single]).to be(false)
end
end
@@ -666,19 +355,54 @@ class Thing
end
describe "#attributes" do
+ let(:foo_klass) do
+ Class.new do
+ include HappyMapper
+
+ attribute :foo, String
+ attribute :bar, String
+ end
+ end
+ let(:bar_klass) do
+ Class.new do
+ include HappyMapper
+
+ attribute :baz1, String
+ attribute :baz2, String
+ attribute :baz3, String
+ attribute :baz4, String
+ end
+ end
+
it "returns only attributes for the current class" do
aggregate_failures do
- expect(Post.attributes.size).to eq(7)
- expect(Status.attributes.size).to eq(0)
+ expect(foo_klass.attributes.size).to eq 2
+ expect(bar_klass.attributes.size).to eq 4
end
end
end
describe "#elements" do
+ let(:foo_klass) do
+ Class.new do
+ include HappyMapper
+
+ element :foo, String
+ end
+ end
+ let(:bar_klass) do
+ Class.new do
+ include HappyMapper
+
+ element :baz1, String
+ element :baz2, String
+ end
+ end
+
it "returns only elements for the current class" do
aggregate_failures do
- expect(Post.elements.size).to eq(0)
- expect(Status.elements.size).to eq(10)
+ expect(foo_klass.elements.size).to eq 1
+ expect(bar_klass.elements.size).to eq 2
end
end
end
@@ -708,54 +432,6 @@ class Thing
end
end
- it "parses xml attributes into ruby objects" do
- posts = Post.parse(fixture_file("posts.xml"))
-
- aggregate_failures do
- expect(posts.size).to eq(20)
- first = posts.first
- expect(first.href).to eq("http://roxml.rubyforge.org/")
- expect(first.hash).to eq("19bba2ab667be03a19f67fb67dc56917")
- expect(first.description).to eq("ROXML - Ruby Object to XML Mapping Library")
- expect(first.tag).to eq("ruby xml gems mapping")
- expect(first.time).to eq(Time.utc(2008, 8, 9, 5, 24, 20))
- expect(first.others).to eq(56)
- expect(first.extended)
- .to eq("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.")
- end
- end
-
- it "parses xml elements to ruby objcts" do
- statuses = Status.parse(fixture_file("statuses.xml"))
-
- aggregate_failures do
- expect(statuses.size).to eq(20)
- first = statuses.first
- expect(first.id).to eq(882_281_424)
- expect(first.created_at).to eq(Time.utc(2008, 8, 9, 5, 38, 12))
- expect(first.source).to eq("web")
- expect(first.truncated).to be_falsey
- expect(first.in_reply_to_status_id).to eq(1234)
- expect(first.in_reply_to_user_id).to eq(12_345)
- expect(first.favorited).to be_falsey
- expect(first.user.id).to eq(4243)
- expect(first.user.name).to eq("John Nunemaker")
- expect(first.user.screen_name).to eq("jnunemaker")
- expect(first.user.location).to eq("Mishawaka, IN, US")
- expect(first.user.description)
- .to eq "Loves his wife, ruby, notre dame football and iu basketball"
- expect(first.user.profile_image_url)
- .to eq("http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg")
- expect(first.user.url).to eq("http://addictedtonew.com")
- expect(first.user.protected).to be_falsey
- expect(first.user.followers_count).to eq(486)
- end
- end
-
it "parses xml containing the desired element as root node" do
address = Address.parse(fixture_file("address.xml"), single: true)
@@ -783,57 +459,6 @@ class Thing
expect(address.class).to eq(Address)
end
- it "parses xml with default namespace (amazon)" do
- file_contents = fixture_file("pita.xml")
- items = PITA::Items.parse(file_contents, single: true)
-
- aggregate_failures do
- expect(items.total_results).to eq(22)
- expect(items.total_pages).to eq(3)
-
- first = items.items[0]
-
- expect(first.asin).to eq("0321480791")
- expect(first.point).to eq("38.5351715088 -121.7948684692")
- expect(first.detail_page_url).to be_a(URI)
- expect(first.detail_page_url.to_s).to eq("http://www.amazon.com/gp/redirect.html%3FASIN=0321480791%26tag=ws%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0321480791%253FSubscriptionId=dontbeaswoosh")
- expect(first.manufacturer).to eq("Addison-Wesley Professional")
- expect(first.product_group).to eq("Book")
-
- second = items.items[1]
-
- expect(second.asin).to eq("047022388X")
- expect(second.manufacturer).to eq("Wrox")
- end
- end
-
- it "parses xml that has attributes of elements" do
- items = CurrentWeather.parse(fixture_file("current_weather.xml"))
- first = items[0]
-
- aggregate_failures do
- expect(first.temperature).to eq(51)
- expect(first.feels_like).to eq(51)
- expect(first.current_condition).to eq("Sunny")
- expect(first.current_condition.icon).to eq("http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif")
- end
- end
-
- it "parses xml with attributes of elements that aren't :single => true" do
- feed = Atom::Feed.parse(fixture_file("atom.xml"))
-
- aggregate_failures do
- expect(feed.link.first.href).to eq("http://www.example.com")
- expect(feed.link.last.href).to eq("http://www.example.com/tv_shows.atom")
- end
- end
-
- it "parses xml with optional elements with embedded attributes" do
- expect do
- CurrentWeather.parse(fixture_file("current_weather_missing_elements.xml"))
- end.not_to raise_error
- end
-
it "returns nil rather than empty array for absent values when :single => true" do
address = Address.parse('', single: true)
expect(address).to be_nil
@@ -871,18 +496,6 @@ class Thing
end
end
- it "parses xml that has elements with dashes" do
- commit = GitHub::Commit.parse(fixture_file("commit.xml"))
-
- aggregate_failures do
- expect(commit.message).to eq("move commands.rb and helpers.rb into commands/ dir")
- expect(commit.url).to eq("http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b")
- expect(commit.id).to eq("c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b")
- expect(commit.committed_date).to eq(Date.parse("2008-03-02T16:45:41-08:00"))
- expect(commit.tree).to eq("28a1a1ca3e663d35ba8bf07d3f1781af71359b76")
- end
- end
-
it "parses xml with no namespace" do
product = Product.parse(fixture_file("product_no_namespace.xml"), single: true)
@@ -919,103 +532,12 @@ class Thing
end
end
- it "parses xml with multiple namespaces" do
- track = FedEx::TrackReply.parse(fixture_file("multiple_namespaces.xml"))
-
- aggregate_failures do
- expect(track.highest_severity).to eq("SUCCESS")
- expect(track.more_data).to be_falsey
- notification = track.notifications.first
- expect(notification.code).to eq(0)
- expect(notification.localized_message).to eq("Request was successfully processed.")
- expect(notification.message).to eq("Request was successfully processed.")
- expect(notification.severity).to eq("SUCCESS")
- expect(notification.source).to eq("trck")
- detail = track.trackdetails.first
- expect(detail.carrier_code).to eq("FDXG")
- expect(detail.est_delivery).to eq("2009-01-02T00:00:00")
- expect(detail.service_info).to eq("Ground-Package Returns Program-Domestic")
- expect(detail.status_code).to eq("OD")
- expect(detail.status_desc).to eq("On FedEx vehicle for delivery")
- expect(detail.tracking_number).to eq("9611018034267800045212")
- expect(detail.weight.units).to eq("LB")
- expect(detail.weight.value).to eq(2)
- events = detail.events
- expect(events.size).to eq(10)
- first_event = events[0]
- expect(first_event.eventdescription).to eq("On FedEx vehicle for delivery")
- expect(first_event.eventtype).to eq("OD")
- expect(first_event.timestamp).to eq("2009-01-02T06:00:00")
- expect(first_event.address.city).to eq("WICHITA")
- expect(first_event.address.countrycode).to eq("US")
- expect(first_event.address.residential).to be_falsey
- expect(first_event.address.state).to eq("KS")
- expect(first_event.address.zip).to eq("67226")
- last_event = events[-1]
- expect(last_event.eventdescription).to eq("In FedEx possession")
- expect(last_event.eventtype).to eq("IP")
- expect(last_event.timestamp).to eq("2008-12-27T09:40:00")
- expect(last_event.address.city).to eq("LONGWOOD")
- expect(last_event.address.countrycode).to eq("US")
- expect(last_event.address.residential).to be_falsey
- expect(last_event.address.state).to eq("FL")
- expect(last_event.address.zip).to eq("327506398")
- expect(track.tran_detail.cust_tran_id).to eq("20090102-111321")
- end
- end
-
- it "is able to parse google analytics api xml" do
- data = Analytics::Feed.parse(fixture_file("analytics.xml"))
-
- aggregate_failures do
- expect(data.id).to eq("http://www.google.com/analytics/feeds/accounts/nunemaker@gmail.com")
- expect(data.entries.size).to eq(4)
-
- entry = data.entries[0]
- expect(entry.title).to eq("addictedtonew.com")
- expect(entry.properties.size).to eq(4)
-
- property = entry.properties[0]
- expect(property.name).to eq("ga:accountId")
- expect(property.value).to eq("85301")
- end
- end
-
- it "is able to parse google analytics profile xml with manually declared namespace" do
- data = Analytics::Profile.parse(fixture_file("analytics_profile.xml"))
-
- aggregate_failures do
- expect(data.entries.size).to eq(6)
- entry = data.entries[0]
- expect(entry.title).to eq("www.homedepot.com")
- expect(entry.properties.size).to eq(6)
- expect(entry.goals.size).to eq(0)
- end
- end
-
it "allows speficying child element class with a string" do
bar = StringFoo::Bar.parse ""
expect(bar.things).to contain_exactly(StringFoo::Thing)
end
- it "parses family search xml" do
- tree = FamilySearch::FamilyTree.parse(fixture_file("family_tree.xml"))
-
- aggregate_failures do
- expect(tree.version).to eq("1.0.20071213.942")
- expect(tree.status_message).to eq("OK")
- expect(tree.status_code).to eq("200")
- expect(tree.persons.person.size).to eq(1)
- expect(tree.persons.person.first.version).to eq("1199378491000")
- expect(tree.persons.person.first.modified)
- .to eq(Time.utc(2008, 1, 3, 16, 41, 31)) # 2008-01-03T09:41:31-07:00
- expect(tree.persons.person.first.id).to eq("KWQS-BBQ")
- expect(tree.persons.person.first.information.alternateIds.ids).not_to be_a(String)
- expect(tree.persons.person.first.information.alternateIds.ids.size).to eq(8)
- end
- end
-
it "parses multiple images" do
artist = Artist.parse(fixture_file("multiple_primitives.xml"))
@@ -1025,27 +547,6 @@ class Thing
end
end
- it "parses lastfm namespaces" do
- l = Location.parse(fixture_file("lastfm.xml"))
- expect(l.first.latitude).to eq("51.53469")
- end
-
- describe "Parse optional attributes" do
- let(:parsed_result) { OptionalAttribute.parse(fixture_file("optional_attributes.xml")) }
-
- it "parses an empty String as empty" do
- expect(parsed_result[0].street).to eq("")
- end
-
- it "parses a String with value" do
- expect(parsed_result[1].street).to eq("Milchstrasse")
- end
-
- it "parses an element with no value for the attribute" do
- expect(parsed_result[2].street).to be_nil
- end
- end
-
describe "Default namespace combi" do
let(:file_contents) { fixture_file("default_namespace_combi.xml") }
let(:book) { DefaultNamespaceCombi.parse(file_contents, single: true) }
@@ -1128,9 +629,16 @@ class Thing
end
describe "with limit option" do
+ let(:post_klass) do
+ Class.new do
+ include HappyMapper
+ tag "post"
+ end
+ end
+
it "returns results with limited size: 6" do
sizes = []
- Post.parse(fixture_file("posts.xml"), in_groups_of: 6) do |a|
+ post_klass.parse(fixture_file("posts.xml"), in_groups_of: 6) do |a|
sizes << a.size
end
expect(sizes).to eq([6, 6, 6, 2])
@@ -1138,7 +646,7 @@ class Thing
it "returns results with limited size: 10" do
sizes = []
- Post.parse(fixture_file("posts.xml"), in_groups_of: 10) do |a|
+ post_klass.parse(fixture_file("posts.xml"), in_groups_of: 10) do |a|
sizes << a.size
end
expect(sizes).to eq([10, 10])
diff --git a/spec/regressions/amazon_search_response_spec.rb b/spec/regressions/amazon_search_response_spec.rb
new file mode 100644
index 0000000..764851b
--- /dev/null
+++ b/spec/regressions/amazon_search_response_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+require "uri"
+
+RSpec.describe "parsing an amazon search result" do
+ before do
+ # for type coercion
+ productgroup_klass = Class.new(String)
+ stub_const "ProductGroup", productgroup_klass
+
+ item_klass = Class.new do
+ include HappyMapper
+
+ tag "Item"
+ element :asin, String, tag: "ASIN"
+ element :detail_page_url, URI, tag: "DetailPageURL", parser: :parse
+ element :manufacturer, String, tag: "Manufacturer", deep: true
+ element :point, String, tag: "point", namespace: "georss"
+ element :product_group, ProductGroup, tag: "ProductGroup", deep: true,
+ parser: :new, raw: true
+ end
+ stub_const "Item", item_klass
+
+ items_klass = Class.new do
+ include HappyMapper
+
+ tag "Items"
+ element :total_results, Integer, tag: "TotalResults"
+ element :total_pages, Integer, tag: "TotalPages"
+ has_many :items, Item
+ end
+ stub_const "Items", items_klass
+ end
+
+ it "parses xml with default and other namespace and various custom parsing" do
+ file_contents = fixture_file("pita.xml")
+ items = Items.parse(file_contents, single: true)
+
+ aggregate_failures do
+ expect(items.total_results).to eq(22)
+ expect(items.total_pages).to eq(3)
+
+ first = items.items[0]
+
+ expect(first.asin).to eq("0321480791")
+ expect(first.point).to eq("38.5351715088 -121.7948684692")
+ expect(first.detail_page_url).to be_a(URI)
+ expect(first.detail_page_url.to_s).to eq("http://www.amazon.com/gp/redirect.html%3FASIN=0321480791%26tag=ws%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0321480791%253FSubscriptionId=dontbeaswoosh")
+ expect(first.manufacturer).to eq("Addison-Wesley Professional")
+ expect(first.product_group).to eq("Book")
+
+ second = items.items[1]
+
+ expect(second.asin).to eq("047022388X")
+ expect(second.manufacturer).to eq("Wrox")
+ end
+ end
+end
diff --git a/spec/regressions/familysearch_spec.rb b/spec/regressions/familysearch_spec.rb
new file mode 100644
index 0000000..9dae8be
--- /dev/null
+++ b/spec/regressions/familysearch_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "parsing a familysearch family tree" do
+ before do
+ alternateids_klass = Class.new do
+ include HappyMapper
+
+ tag "alternateIds"
+ has_many :ids, String, tag: "id"
+ end
+ stub_const "AlternateIds", alternateids_klass
+
+ information_klass = Class.new do
+ include HappyMapper
+
+ has_one :alternateIds, AlternateIds
+ end
+ stub_const "Information", information_klass
+
+ person_klass = Class.new do
+ include HappyMapper
+
+ attribute :version, String
+ attribute :modified, Time
+ attribute :id, String
+ has_one :information, Information
+ end
+ stub_const "Person", person_klass
+
+ persons_klass = Class.new do
+ include HappyMapper
+ has_many :person, Person
+ end
+ stub_const "Persons", persons_klass
+
+ familytree_klass = Class.new do
+ include HappyMapper
+
+ tag "familytree"
+ attribute :version, String
+ attribute :status_message, String, tag: "statusMessage"
+ attribute :status_code, String, tag: "statusCode"
+ has_one :persons, Persons
+ end
+ stub_const "FamilyTree", familytree_klass
+ end
+
+ it "parses family search xml correctly" do
+ tree = FamilyTree.parse(fixture_file("family_tree.xml"))
+
+ aggregate_failures do
+ expect(tree.version).to eq("1.0.20071213.942")
+ expect(tree.status_message).to eq("OK")
+ expect(tree.status_code).to eq("200")
+ expect(tree.persons.person.size).to eq(1)
+ expect(tree.persons.person.first.version).to eq("1199378491000")
+ expect(tree.persons.person.first.modified)
+ .to eq(Time.utc(2008, 1, 3, 16, 41, 31)) # 2008-01-03T09:41:31-07:00
+ expect(tree.persons.person.first.id).to eq("KWQS-BBQ")
+ expect(tree.persons.person.first.information.alternateIds.ids).not_to be_a(String)
+ expect(tree.persons.person.first.information.alternateIds.ids.size).to eq(8)
+ end
+ end
+end
diff --git a/spec/regressions/fedex_spec.rb b/spec/regressions/fedex_spec.rb
new file mode 100644
index 0000000..19d5474
--- /dev/null
+++ b/spec/regressions/fedex_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "parsing FedEx xml" do
+ before do
+ address_klass = Class.new do
+ include HappyMapper
+
+ tag "Address"
+ namespace "v2"
+ element :city, String, tag: "City"
+ element :state, String, tag: "StateOrProvinceCode"
+ element :zip, String, tag: "PostalCode"
+ element :countrycode, String, tag: "CountryCode"
+ element :residential, HappyMapper::Boolean, tag: "Residential"
+ end
+ stub_const "Address", address_klass
+
+ event_klass = Class.new do
+ include HappyMapper
+
+ tag "Events"
+ namespace "v2"
+ element :timestamp, String, tag: "Timestamp"
+ element :eventtype, String, tag: "EventType"
+ element :eventdescription, String, tag: "EventDescription"
+ has_one :address, Address
+ end
+ stub_const "Event", event_klass
+
+ packageweight_klass = Class.new do
+ include HappyMapper
+
+ tag "PackageWeight"
+ namespace "v2"
+ element :units, String, tag: "Units"
+ element :value, Integer, tag: "Value"
+ end
+ stub_const "PackageWeight", packageweight_klass
+
+ trackdetails_klass = Class.new do
+ include HappyMapper
+
+ tag "TrackDetails"
+ namespace "v2"
+ element :tracking_number, String, tag: "TrackingNumber"
+ element :status_code, String, tag: "StatusCode"
+ element :status_desc, String, tag: "StatusDescription"
+ element :carrier_code, String, tag: "CarrierCode"
+ element :service_info, String, tag: "ServiceInfo"
+ has_one :weight, PackageWeight, tag: "PackageWeight"
+ element :est_delivery, String, tag: "EstimatedDeliveryTimestamp"
+ has_many :events, Event
+ end
+ stub_const "TrackDetails", trackdetails_klass
+
+ notification_klass = Class.new do
+ include HappyMapper
+
+ tag "Notifications"
+ namespace "v2"
+ element :severity, String, tag: "Severity"
+ element :source, String, tag: "Source"
+ element :code, Integer, tag: "Code"
+ element :message, String, tag: "Message"
+ element :localized_message, String, tag: "LocalizedMessage"
+ end
+ stub_const "Notification", notification_klass
+
+ transactiondetail_klass = Class.new do
+ include HappyMapper
+
+ tag "TransactionDetail"
+ namespace "v2"
+ element :cust_tran_id, String, tag: "CustomerTransactionId"
+ end
+ stub_const "TransactionDetail", transactiondetail_klass
+
+ trackreply_klass = Class.new do
+ include HappyMapper
+
+ tag "TrackReply"
+ namespace "v2"
+ element :highest_severity, String, tag: "HighestSeverity"
+ element :more_data, HappyMapper::Boolean, tag: "MoreData"
+ has_many :notifications, Notification, tag: "Notifications"
+ has_many :trackdetails, TrackDetails, tag: "TrackDetails"
+ has_one :tran_detail, TransactionDetail, tab: "TransactionDetail"
+ end
+ stub_const "TrackReply", trackreply_klass
+ end
+
+ it "parses xml with multiple namespaces" do
+ track = TrackReply.parse(fixture_file("multiple_namespaces.xml"))
+
+ aggregate_failures do
+ expect(track.highest_severity).to eq("SUCCESS")
+ expect(track.more_data).to be_falsey
+ notification = track.notifications.first
+ expect(notification.code).to eq(0)
+ expect(notification.localized_message).to eq("Request was successfully processed.")
+ expect(notification.message).to eq("Request was successfully processed.")
+ expect(notification.severity).to eq("SUCCESS")
+ expect(notification.source).to eq("trck")
+ detail = track.trackdetails.first
+ expect(detail.carrier_code).to eq("FDXG")
+ expect(detail.est_delivery).to eq("2009-01-02T00:00:00")
+ expect(detail.service_info).to eq("Ground-Package Returns Program-Domestic")
+ expect(detail.status_code).to eq("OD")
+ expect(detail.status_desc).to eq("On FedEx vehicle for delivery")
+ expect(detail.tracking_number).to eq("9611018034267800045212")
+ expect(detail.weight.units).to eq("LB")
+ expect(detail.weight.value).to eq(2)
+ events = detail.events
+ expect(events.size).to eq(10)
+ first_event = events[0]
+ expect(first_event.eventdescription).to eq("On FedEx vehicle for delivery")
+ expect(first_event.eventtype).to eq("OD")
+ expect(first_event.timestamp).to eq("2009-01-02T06:00:00")
+ expect(first_event.address.city).to eq("WICHITA")
+ expect(first_event.address.countrycode).to eq("US")
+ expect(first_event.address.residential).to be_falsey
+ expect(first_event.address.state).to eq("KS")
+ expect(first_event.address.zip).to eq("67226")
+ last_event = events[-1]
+ expect(last_event.eventdescription).to eq("In FedEx possession")
+ expect(last_event.eventtype).to eq("IP")
+ expect(last_event.timestamp).to eq("2008-12-27T09:40:00")
+ expect(last_event.address.city).to eq("LONGWOOD")
+ expect(last_event.address.countrycode).to eq("US")
+ expect(last_event.address.residential).to be_falsey
+ expect(last_event.address.state).to eq("FL")
+ expect(last_event.address.zip).to eq("327506398")
+ expect(track.tran_detail.cust_tran_id).to eq("20090102-111321")
+ end
+ end
+end
diff --git a/spec/regressions/github_spec.rb b/spec/regressions/github_spec.rb
new file mode 100644
index 0000000..f49dc81
--- /dev/null
+++ b/spec/regressions/github_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "parsing github commit xml" do
+ before do
+ commit_klass = Class.new do
+ include HappyMapper
+
+ tag "commit"
+ element :url, String
+ element :tree, String
+ element :message, String
+ element :id, String
+ element :"committed-date", Date
+ end
+ stub_const "Commit", commit_klass
+ end
+
+ it "parses xml that has elements with dashes" do
+ commit = Commit.parse(fixture_file("commit.xml"))
+
+ aggregate_failures do
+ expect(commit.message).to eq("move commands.rb and helpers.rb into commands/ dir")
+ expect(commit.url).to eq("http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b")
+ expect(commit.id).to eq("c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b")
+ expect(commit.committed_date).to eq(Date.parse("2008-03-02T16:45:41-08:00"))
+ expect(commit.tree).to eq("28a1a1ca3e663d35ba8bf07d3f1781af71359b76")
+ end
+ end
+end
diff --git a/spec/regressions/google_analytics_spec.rb b/spec/regressions/google_analytics_spec.rb
new file mode 100644
index 0000000..c382663
--- /dev/null
+++ b/spec/regressions/google_analytics_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "parsing Google Analytics XML" do
+ before do
+ property_klass = Class.new do
+ include HappyMapper
+
+ tag "property"
+ namespace "dxp"
+ attribute :name, String
+ attribute :value, String
+ end
+ stub_const "Property", property_klass
+
+ goal_klass = Class.new do
+ include HappyMapper
+
+ # Google Analytics does a dirtry trick where a user with no goals
+ # returns a profile without any goals data or the declared namespace
+ # which means Nokogiri does not pick up the namespace automatically.
+ # To fix this, we manually register the namespace to avoid bad XPath
+ # expression. Dirty, but works.
+
+ register_namespace "ga", "http://schemas.google.com/ga/2009"
+ namespace "ga"
+
+ tag "goal"
+ attribute :active, HappyMapper::Boolean
+ attribute :name, String
+ attribute :number, Integer
+ attribute :value, Float
+ end
+ stub_const "Goal", goal_klass
+
+ profile_klass = Class.new do
+ include HappyMapper
+
+ tag "entry"
+ element :title, String
+ element :tableId, String, namespace: "dxp"
+
+ has_many :properties, Property
+ has_many :goals, Goal
+ end
+ stub_const "Profile", profile_klass
+
+ entry_klass = Class.new do
+ include HappyMapper
+
+ tag "entry"
+ element :id, String
+ element :updated, DateTime
+ element :title, String
+ element :table_id, String, namespace: "dxp", tag: "tableId"
+ has_many :properties, Property
+ end
+ stub_const "Entry", entry_klass
+
+ feed_klass = Class.new do
+ include HappyMapper
+
+ tag "feed"
+ element :id, String
+ element :updated, DateTime
+ element :title, String
+ has_many :entries, Entry
+ end
+ stub_const "Feed", feed_klass
+ end
+
+ it "is able to parse google analytics api xml" do
+ data = Feed.parse(fixture_file("analytics.xml"))
+
+ aggregate_failures do
+ expect(data.id).to eq("http://www.google.com/analytics/feeds/accounts/nunemaker@gmail.com")
+ expect(data.entries.size).to eq(4)
+
+ entry = data.entries[0]
+ expect(entry.title).to eq("addictedtonew.com")
+ expect(entry.properties.size).to eq(4)
+
+ property = entry.properties[0]
+ expect(property.name).to eq("ga:accountId")
+ expect(property.value).to eq("85301")
+ end
+ end
+
+ it "is able to parse google analytics profile xml with manually declared namespace" do
+ data = Profile.parse(fixture_file("analytics_profile.xml"))
+
+ aggregate_failures do
+ expect(data.entries.size).to eq(6)
+ entry = data.entries[0]
+ expect(entry.title).to eq("www.homedepot.com")
+ expect(entry.properties.size).to eq(6)
+ expect(entry.goals.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/regressions/lastfm_spec.rb b/spec/regressions/lastfm_spec.rb
new file mode 100644
index 0000000..8275e3b
--- /dev/null
+++ b/spec/regressions/lastfm_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+RSpec.describe "parsing lastfm xml" do
+ before do
+ klass = Class.new do
+ include HappyMapper
+
+ tag "point"
+ namespace "geo"
+ element :latitude, String, tag: "lat"
+ end
+ stub_const "Location", klass
+ end
+
+ it "maps namespaces correctly" do
+ l = Location.parse(fixture_file("lastfm.xml"))
+ expect(l.first.latitude).to eq("51.53469")
+ end
+end
diff --git a/spec/regressions/twitter_statuses_spec.rb b/spec/regressions/twitter_statuses_spec.rb
new file mode 100644
index 0000000..580c138
--- /dev/null
+++ b/spec/regressions/twitter_statuses_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "parsing twitter statuses" do
+ before do
+ user_klass = Class.new do
+ include HappyMapper
+
+ element :id, Integer
+ element :name, String
+ element :screen_name, String
+ element :location, String
+ element :description, String
+ element :profile_image_url, String
+ element :url, String
+ element :protected, HappyMapper::Boolean
+ element :followers_count, Integer
+ end
+ stub_const "User", user_klass
+
+ status_klass = Class.new do
+ include HappyMapper
+
+ register_namespace "fake", "faka:namespace"
+
+ element :id, Integer
+ element :text, String
+ element :created_at, Time
+ element :source, String
+ element :truncated, HappyMapper::Boolean
+ element :in_reply_to_status_id, Integer
+ element :in_reply_to_user_id, Integer
+ element :favorited, HappyMapper::Boolean
+ element :non_existent, String, tag: "dummy", namespace: "fake"
+ has_one :user, User
+ end
+ stub_const "Status", status_klass
+ end
+
+ it "parses xml elements to ruby objects" do
+ statuses = Status.parse(fixture_file("statuses.xml"))
+
+ aggregate_failures do
+ expect(statuses.size).to eq(20)
+ first = statuses.first
+ expect(first.id).to eq(882_281_424)
+ expect(first.created_at).to eq(Time.utc(2008, 8, 9, 5, 38, 12))
+ expect(first.source).to eq("web")
+ expect(first.truncated).to be_falsey
+ expect(first.in_reply_to_status_id).to eq(1234)
+ expect(first.in_reply_to_user_id).to eq(12_345)
+ expect(first.favorited).to be_falsey
+ expect(first.user.id).to eq(4243)
+ expect(first.user.name).to eq("John Nunemaker")
+ expect(first.user.screen_name).to eq("jnunemaker")
+ expect(first.user.location).to eq("Mishawaka, IN, US")
+ expect(first.user.description)
+ .to eq "Loves his wife, ruby, notre dame football and iu basketball"
+ expect(first.user.profile_image_url)
+ .to eq("http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg")
+ expect(first.user.url).to eq("http://addictedtonew.com")
+ expect(first.user.protected).to be_falsey
+ expect(first.user.followers_count).to eq(486)
+ end
+ end
+end
diff --git a/spec/features/ignay_spec.rb b/spec/regressions/vod_catalog_spec.rb
similarity index 52%
rename from spec/features/ignay_spec.rb
rename to spec/regressions/vod_catalog_spec.rb
index cf390b6..2a33a8e 100644
--- a/spec/features/ignay_spec.rb
+++ b/spec/regressions/vod_catalog_spec.rb
@@ -2,43 +2,49 @@
require "spec_helper"
-class CatalogTree
- include HappyMapper
+RSpec.describe "parsing a VOD catalog" do
+ before do
+ catalog_tree = Class.new do
+ include HappyMapper
- tag "CatalogTree"
- register_namespace "xmlns", "urn:eventis:prodis:onlineapi:1.0"
- register_namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance"
- register_namespace "xsd", "http://www.w3.org/2001/XMLSchema"
+ tag "CatalogTree"
+ register_namespace "xmlns", "urn:eventis:prodis:onlineapi:1.0"
+ register_namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance"
+ register_namespace "xsd", "http://www.w3.org/2001/XMLSchema"
- attribute :code, String
+ attribute :code, String
- has_many :nodes, "CatalogNode", tag: "Node", xpath: "."
-end
+ has_many :nodes, "CatalogNode", tag: "Node", xpath: "."
+ end
+ stub_const "CatalogTree", catalog_tree
-class CatalogNode
- include HappyMapper
+ catalog_node = Class.new do
+ include HappyMapper
- tag "Node"
+ tag "Node"
- attribute :back_office_id, String, tag: "vodBackOfficeId"
+ attribute :back_office_id, String, tag: "vodBackOfficeId"
- has_one :name, String, tag: "Name"
- # other important fields
+ has_one :name, String, tag: "Name"
+ # other important fields
- has_many :translations, "CatalogNode::Translations", tag: "Translation", xpath: "child::*"
+ has_many :translations, "CatalogNode::Translations", tag: "Translation",
+ xpath: "child::*"
- class Translations
- include HappyMapper
- tag "Translation"
+ has_many :nodes, self, tag: "Node", xpath: "child::*"
+ end
+ stub_const "CatalogNode", catalog_node
- attribute :language, String, tag: "Language"
- has_one :name, String, tag: "Name"
- end
+ translations = Class.new do
+ include HappyMapper
+ tag "Translation"
- has_many :nodes, CatalogNode, tag: "Node", xpath: "child::*"
-end
+ attribute :language, String, tag: "Language"
+ has_one :name, String, tag: "Name"
+ end
+ stub_const "CatalogNode::Translations", translations
+ end
-RSpec.describe "parsing a VOD catalog" do
let(:catalog_tree) { CatalogTree.parse(fixture_file("inagy.xml"), single: true) }
it "is not nil" do
diff --git a/spec/regressions/yahoo_posts_spec.rb b/spec/regressions/yahoo_posts_spec.rb
new file mode 100644
index 0000000..af93367
--- /dev/null
+++ b/spec/regressions/yahoo_posts_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "parsing a yahoo posts xml" do
+ before do
+ post_klass = Class.new do
+ include HappyMapper
+
+ attribute :href, String
+ attribute :hash, String
+ attribute :description, String
+ attribute :tag, String
+ attribute :time, Time
+ attribute :others, Integer
+ attribute :extended, String
+ end
+ stub_const "Post", post_klass
+ end
+
+ it "parses xml attributes into ruby objects" do
+ posts = Post.parse(fixture_file("posts.xml"))
+
+ aggregate_failures do
+ expect(posts.size).to eq(20)
+ first = posts.first
+ expect(first.href).to eq("http://roxml.rubyforge.org/")
+ expect(first.hash).to eq("19bba2ab667be03a19f67fb67dc56917")
+ expect(first.description).to eq("ROXML - Ruby Object to XML Mapping Library")
+ expect(first.tag).to eq("ruby xml gems mapping")
+ expect(first.time).to eq(Time.utc(2008, 8, 9, 5, 24, 20))
+ expect(first.others).to eq(56)
+ expect(first.extended)
+ .to eq("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.")
+ end
+ end
+end