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