move parse code to Entity
This commit is contained in:
parent
2e3bf2f132
commit
2cdaea0d70
5 changed files with 297 additions and 258 deletions
|
|
@ -99,7 +99,23 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Construct a new instance of the given Entity and populate the properties
|
||||||
|
# with the attributes found in the XML.
|
||||||
|
# Works recursively on nested Entities and Arrays thereof.
|
||||||
|
#
|
||||||
|
# @param [Nokogiri::XML::Element] root_node xml nodes
|
||||||
|
# @return [Entity] instance
|
||||||
|
def self.from_xml(root_node)
|
||||||
|
raise ArgumentError, "only Nokogiri::XML::Element allowed" unless root_node.instance_of?(Nokogiri::XML::Element)
|
||||||
|
raise InvalidRootNode, "'#{root_node.name}' can't be parsed by #{name}" unless root_node.name == entity_name
|
||||||
|
|
||||||
|
populate_entity(root_node)
|
||||||
|
end
|
||||||
|
|
||||||
# Makes an underscored, lowercase form of the class name
|
# Makes an underscored, lowercase form of the class name
|
||||||
|
#
|
||||||
|
# @see .entity_class
|
||||||
|
#
|
||||||
# @return [String] entity name
|
# @return [String] entity name
|
||||||
def self.entity_name
|
def self.entity_name
|
||||||
name.rpartition("::").last.tap do |word|
|
name.rpartition("::").last.tap do |word|
|
||||||
|
|
@ -108,6 +124,23 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Transform the given String from the lowercase underscored version to a
|
||||||
|
# camelized variant and returns the Class constant.
|
||||||
|
#
|
||||||
|
# @see .entity_name
|
||||||
|
#
|
||||||
|
# @param [String] class_name "snake_case" class name
|
||||||
|
# @return [Class] entity class
|
||||||
|
def self.entity_class(class_name)
|
||||||
|
raise InvalidEntityName, "'#{class_name}' is invalid" unless class_name =~ /^[a-z]*(_[a-z]*)*$/
|
||||||
|
class_name.sub!(/^[a-z]/, &:upcase)
|
||||||
|
class_name.gsub!(/_([a-z])/) { Regexp.last_match[1].upcase }
|
||||||
|
|
||||||
|
raise UnknownEntity, "'#{class_name}' not found" unless Entities.const_defined?(class_name)
|
||||||
|
|
||||||
|
Entities.const_get(class_name)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def setable?(name, val)
|
def setable?(name, val)
|
||||||
|
|
@ -173,8 +206,99 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param [Nokogiri::XML::Element] root_node xml nodes
|
||||||
|
# @return [Entity] instance
|
||||||
|
def self.populate_entity(root_node)
|
||||||
|
# Use all known properties to build the Entity (entity_data). All additional xml elements
|
||||||
|
# are respected and attached to a hash as string (additional_xml_elements). It is intended
|
||||||
|
# to build a hash invariable of an Entity definition, in order to support receiving objects
|
||||||
|
# from the future versions of Diaspora, where new elements may have been added.
|
||||||
|
entity_data = {}
|
||||||
|
xml_order = []
|
||||||
|
additional_xml_elements = {}
|
||||||
|
|
||||||
|
root_node.element_children.each do |child|
|
||||||
|
xml_name = child.name
|
||||||
|
property = find_property_for_xml_name(xml_name)
|
||||||
|
|
||||||
|
if property
|
||||||
|
entity_data[property] = parse_element_from_node(class_props[property], xml_name, root_node)
|
||||||
|
xml_order << property
|
||||||
|
else
|
||||||
|
additional_xml_elements[xml_name] = child.text
|
||||||
|
xml_order << xml_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
new(entity_data, xml_order, additional_xml_elements).tap do |entity|
|
||||||
|
entity.verify_signatures if entity.respond_to? :verify_signatures
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :populate_entity
|
||||||
|
|
||||||
|
# @param [Class] type target type to parse
|
||||||
|
# @param [String] xml_name xml tag to parse
|
||||||
|
# @param [Nokogiri::XML::Element] node XML node to parse
|
||||||
|
# @return [Object] parsed data
|
||||||
|
def self.parse_element_from_node(type, xml_name, node)
|
||||||
|
if type == String
|
||||||
|
parse_string_from_node(xml_name, node)
|
||||||
|
elsif type.instance_of?(Array)
|
||||||
|
parse_array_from_node(type.first, node)
|
||||||
|
elsif type.ancestors.include?(Entity)
|
||||||
|
parse_entity_from_node(type, node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :parse_element_from_node
|
||||||
|
|
||||||
|
# create simple entry in data hash
|
||||||
|
#
|
||||||
|
# @param [String] name xml tag to parse
|
||||||
|
# @param [Nokogiri::XML::Element] root_node XML root_node to parse
|
||||||
|
# @return [String] data
|
||||||
|
def self.parse_string_from_node(name, root_node)
|
||||||
|
node = root_node.xpath(name.to_s)
|
||||||
|
node.first.text if node.any?
|
||||||
|
end
|
||||||
|
private_class_method :parse_string_from_node
|
||||||
|
|
||||||
|
# create an entry in the data hash for the nested entity
|
||||||
|
#
|
||||||
|
# @param [Class] type target type to parse
|
||||||
|
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
||||||
|
# @return [Entity] parsed child entity
|
||||||
|
def self.parse_entity_from_node(type, root_node)
|
||||||
|
node = root_node.xpath(type.entity_name)
|
||||||
|
type.from_xml(node.first) if node.any?
|
||||||
|
end
|
||||||
|
private_class_method :parse_entity_from_node
|
||||||
|
|
||||||
|
# collect all nested children of that type and create an array in the data hash
|
||||||
|
#
|
||||||
|
# @param [Class] type target type to parse
|
||||||
|
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
||||||
|
# @return [Array<Entity>] array with parsed child entities
|
||||||
|
def self.parse_array_from_node(type, root_node)
|
||||||
|
node = root_node.xpath(type.entity_name)
|
||||||
|
node.map {|child| type.from_xml(child) }
|
||||||
|
end
|
||||||
|
private_class_method :parse_array_from_node
|
||||||
|
|
||||||
# Raised, if entity is not valid
|
# Raised, if entity is not valid
|
||||||
class ValidationError < RuntimeError
|
class ValidationError < RuntimeError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Raised, if the root node doesn't match the class name
|
||||||
|
class InvalidRootNode < RuntimeError
|
||||||
|
end
|
||||||
|
|
||||||
|
# Raised, if the entity name in the XML is invalid
|
||||||
|
class InvalidEntityName < RuntimeError
|
||||||
|
end
|
||||||
|
|
||||||
|
# Raised, if the entity contained within the XML cannot be mapped to a
|
||||||
|
# defined {Entity} subclass.
|
||||||
|
class UnknownEntity < RuntimeError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,5 @@ module DiasporaFederation
|
||||||
# Raised, if the parsed Magic Envelope specifies an unhandled encoding.
|
# Raised, if the parsed Magic Envelope specifies an unhandled encoding.
|
||||||
class InvalidEncoding < RuntimeError
|
class InvalidEncoding < RuntimeError
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised, if the entity name in the XML is invalid
|
|
||||||
class InvalidEntityName < RuntimeError
|
|
||||||
end
|
|
||||||
|
|
||||||
# Raised, if the entity contained within the XML cannot be mapped to a
|
|
||||||
# defined {Entity} subclass.
|
|
||||||
class UnknownEntity < RuntimeError
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ module DiasporaFederation
|
||||||
# </XML>
|
# </XML>
|
||||||
#
|
#
|
||||||
# (The +post+ element is there for historic reasons...)
|
# (The +post+ element is there for historic reasons...)
|
||||||
|
# @deprecated
|
||||||
module XmlPayload
|
module XmlPayload
|
||||||
# Encapsulates an Entity inside the wrapping xml structure
|
# Encapsulates an Entity inside the wrapping xml structure
|
||||||
# and returns the XML Object.
|
# and returns the XML Object.
|
||||||
|
|
@ -18,7 +19,6 @@ module DiasporaFederation
|
||||||
# @param [Entity] entity subject
|
# @param [Entity] entity subject
|
||||||
# @return [Nokogiri::XML::Element] XML root node
|
# @return [Nokogiri::XML::Element] XML root node
|
||||||
# @raise [ArgumentError] if the argument is not an Entity subclass
|
# @raise [ArgumentError] if the argument is not an Entity subclass
|
||||||
# @deprecated
|
|
||||||
def self.pack(entity)
|
def self.pack(entity)
|
||||||
raise ArgumentError, "only instances of DiasporaFederation::Entity allowed" unless entity.is_a?(Entity)
|
raise ArgumentError, "only instances of DiasporaFederation::Entity allowed" unless entity.is_a?(Entity)
|
||||||
|
|
||||||
|
|
@ -46,117 +46,16 @@ module DiasporaFederation
|
||||||
raise ArgumentError, "only Nokogiri::XML::Element allowed" unless xml.instance_of?(Nokogiri::XML::Element)
|
raise ArgumentError, "only Nokogiri::XML::Element allowed" unless xml.instance_of?(Nokogiri::XML::Element)
|
||||||
|
|
||||||
data = xml_wrapped?(xml) ? xml.at_xpath("post/*[1]") : xml
|
data = xml_wrapped?(xml) ? xml.at_xpath("post/*[1]") : xml
|
||||||
klass_name = entity_class_name(data.name)
|
|
||||||
raise Salmon::UnknownEntity, "'#{klass_name}' not found" unless Entities.const_defined?(klass_name)
|
|
||||||
|
|
||||||
klass = Entities.const_get(klass_name)
|
Entity.entity_class(data.name).from_xml(data)
|
||||||
populate_entity(klass, data)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [Nokogiri::XML::Element] element
|
# @param [Nokogiri::XML::Element] element
|
||||||
# @deprecated
|
|
||||||
def self.xml_wrapped?(element)
|
def self.xml_wrapped?(element)
|
||||||
(element.name == "XML" && !element.at_xpath("post").nil? &&
|
(element.name == "XML" && !element.at_xpath("post").nil? &&
|
||||||
!element.at_xpath("post").children.empty?)
|
!element.at_xpath("post").children.empty?)
|
||||||
end
|
end
|
||||||
private_class_method :xml_wrapped?
|
private_class_method :xml_wrapped?
|
||||||
|
|
||||||
# Transform the given String from the lowercase underscored version to a
|
|
||||||
# camelized variant, used later for getting the Class constant.
|
|
||||||
#
|
|
||||||
# @param [String] term "snake_case" class name
|
|
||||||
# @return [String] "CamelCase" class name
|
|
||||||
def self.entity_class_name(term)
|
|
||||||
term.to_s.tap do |string|
|
|
||||||
raise Salmon::InvalidEntityName, "'#{string}' is invalid" unless string =~ /^[a-z]*(_[a-z]*)*$/
|
|
||||||
string.sub!(/^[a-z]/, &:upcase)
|
|
||||||
string.gsub!(/_([a-z])/) { Regexp.last_match[1].upcase }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :entity_class_name
|
|
||||||
|
|
||||||
# Construct a new instance of the given Entity and populate the properties
|
|
||||||
# with the attributes found in the XML.
|
|
||||||
# Works recursively on nested Entities and Arrays thereof.
|
|
||||||
#
|
|
||||||
# @param [Class] klass entity class
|
|
||||||
# @param [Nokogiri::XML::Element] root_node xml nodes
|
|
||||||
# @return [Entity] instance
|
|
||||||
def self.populate_entity(klass, root_node)
|
|
||||||
# Use all known properties to build the Entity (entity_data). All additional xml elements
|
|
||||||
# are respected and attached to a hash as string (additional_xml_elements). It is intended
|
|
||||||
# to build a hash invariable of an Entity definition, in order to support receiving objects
|
|
||||||
# from the future versions of Diaspora, where new elements may have been added.
|
|
||||||
entity_data = {}
|
|
||||||
xml_order = []
|
|
||||||
additional_xml_elements = {}
|
|
||||||
|
|
||||||
root_node.element_children.each do |child|
|
|
||||||
xml_name = child.name
|
|
||||||
property = klass.find_property_for_xml_name(xml_name)
|
|
||||||
|
|
||||||
if property
|
|
||||||
entity_data[property] = parse_element_from_node(klass.class_props[property], xml_name, root_node)
|
|
||||||
xml_order << property
|
|
||||||
else
|
|
||||||
additional_xml_elements[xml_name] = child.text
|
|
||||||
xml_order << xml_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
klass.new(entity_data, xml_order, additional_xml_elements).tap do |entity|
|
|
||||||
entity.verify_signatures if entity.respond_to? :verify_signatures
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :populate_entity
|
|
||||||
|
|
||||||
# @param [Class] type target type to parse
|
|
||||||
# @param [String] xml_name xml tag to parse
|
|
||||||
# @param [Nokogiri::XML::Element] node XML node to parse
|
|
||||||
# @return [Object] parsed data
|
|
||||||
def self.parse_element_from_node(type, xml_name, node)
|
|
||||||
if type == String
|
|
||||||
parse_string_from_node(xml_name, node)
|
|
||||||
elsif type.instance_of?(Array)
|
|
||||||
parse_array_from_node(type, node)
|
|
||||||
elsif type.ancestors.include?(Entity)
|
|
||||||
parse_entity_from_node(type, node)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :parse_element_from_node
|
|
||||||
|
|
||||||
# create simple entry in data hash
|
|
||||||
#
|
|
||||||
# @param [String] name xml tag to parse
|
|
||||||
# @param [Nokogiri::XML::Element] root_node XML root_node to parse
|
|
||||||
# @return [String] data
|
|
||||||
def self.parse_string_from_node(name, root_node)
|
|
||||||
node = root_node.xpath(name.to_s)
|
|
||||||
node.first.text if node.any?
|
|
||||||
end
|
|
||||||
private_class_method :parse_string_from_node
|
|
||||||
|
|
||||||
# create an entry in the data hash for the nested entity
|
|
||||||
#
|
|
||||||
# @param [Class] type target type to parse
|
|
||||||
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
|
||||||
# @return [Entity] parsed child entity
|
|
||||||
def self.parse_entity_from_node(type, root_node)
|
|
||||||
node = root_node.xpath(type.entity_name)
|
|
||||||
populate_entity(type, node.first) if node.any?
|
|
||||||
end
|
|
||||||
private_class_method :parse_entity_from_node
|
|
||||||
|
|
||||||
# collect all nested children of that type and create an array in the data hash
|
|
||||||
#
|
|
||||||
# @param [Array<Class>] type target type to parse
|
|
||||||
# @param [Nokogiri::XML::Element] root_node XML node to parse
|
|
||||||
# @return [Array<Entity>] array with parsed child entities
|
|
||||||
def self.parse_array_from_node(type, root_node)
|
|
||||||
node = root_node.xpath(type.first.entity_name)
|
|
||||||
node.map {|child| populate_entity(type.first, child) }
|
|
||||||
end
|
|
||||||
private_class_method :parse_array_from_node
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,142 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".from_xml" do
|
||||||
|
let(:entity) { Entities::TestEntity.new(test: "asdf") }
|
||||||
|
let(:entity_xml) { entity.to_xml }
|
||||||
|
|
||||||
|
context "sanity" do
|
||||||
|
it "expects an Nokogiri::XML::Element as param" do
|
||||||
|
expect {
|
||||||
|
Entities::TestEntity.from_xml(entity_xml)
|
||||||
|
}.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises and error when the param is not an Nokogiri::XML::Element" do
|
||||||
|
["asdf", 1234, true, :test, entity].each do |val|
|
||||||
|
expect {
|
||||||
|
Entity.from_xml(val)
|
||||||
|
}.to raise_error ArgumentError, "only Nokogiri::XML::Element allowed"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error when the entity class doesn't match the root node" do
|
||||||
|
xml = <<-XML
|
||||||
|
<unknown_entity>
|
||||||
|
<test>asdf</test>
|
||||||
|
</unknown_entity>
|
||||||
|
XML
|
||||||
|
|
||||||
|
expect {
|
||||||
|
Entity.from_xml(Nokogiri::XML::Document.parse(xml).root)
|
||||||
|
}.to raise_error Entity::InvalidRootNode, "'unknown_entity' can't be parsed by DiasporaFederation::Entity"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "returned object" do
|
||||||
|
subject { Entities::TestEntity.from_xml(entity_xml) }
|
||||||
|
|
||||||
|
it "#to_h should match entity.to_h" do
|
||||||
|
expect(subject.to_h).to eq(entity.to_h)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an entity instance of the original class" do
|
||||||
|
expect(subject).to be_an_instance_of Entities::TestEntity
|
||||||
|
expect(subject.test).to eq("asdf")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "parsing" do
|
||||||
|
it "uses xml_name for parsing" do
|
||||||
|
xml = <<-XML.strip
|
||||||
|
<test_entity_with_xml_name>
|
||||||
|
<test>asdf</test>
|
||||||
|
<asdf>qwer</asdf>
|
||||||
|
</test_entity_with_xml_name>
|
||||||
|
XML
|
||||||
|
|
||||||
|
entity = Entities::TestEntityWithXmlName.from_xml(Nokogiri::XML::Document.parse(xml).root)
|
||||||
|
|
||||||
|
expect(entity).to be_an_instance_of Entities::TestEntityWithXmlName
|
||||||
|
expect(entity.test).to eq("asdf")
|
||||||
|
expect(entity.qwer).to eq("qwer")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows name for parsing even when property has a xml_name" do
|
||||||
|
xml = <<-XML.strip
|
||||||
|
<test_entity_with_xml_name>
|
||||||
|
<test>asdf</test>
|
||||||
|
<qwer>qwer</qwer>
|
||||||
|
</test_entity_with_xml_name>
|
||||||
|
XML
|
||||||
|
|
||||||
|
entity = Entities::TestEntityWithXmlName.from_xml(Nokogiri::XML::Document.parse(xml).root)
|
||||||
|
|
||||||
|
expect(entity).to be_an_instance_of Entities::TestEntityWithXmlName
|
||||||
|
expect(entity.test).to eq("asdf")
|
||||||
|
expect(entity.qwer).to eq("qwer")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't drop unknown properties" do
|
||||||
|
xml = <<-XML
|
||||||
|
<test_entity>
|
||||||
|
<a_prop_from_newer_diaspora_version>some value</a_prop_from_newer_diaspora_version>
|
||||||
|
<test>asdf</test>
|
||||||
|
<some_random_property>another value</some_random_property>
|
||||||
|
</test_entity>
|
||||||
|
XML
|
||||||
|
|
||||||
|
entity = Entities::TestEntity.from_xml(Nokogiri::XML::Document.parse(xml).root)
|
||||||
|
|
||||||
|
expect(entity).to be_an_instance_of Entities::TestEntity
|
||||||
|
expect(entity.test).to eq("asdf")
|
||||||
|
expect(entity.additional_xml_elements).to eq(
|
||||||
|
"a_prop_from_newer_diaspora_version" => "some value",
|
||||||
|
"some_random_property" => "another value"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates Entity with nil 'additional_xml_elements' if the xml has only known properties" do
|
||||||
|
entity = Entities::TestEntity.from_xml(entity_xml)
|
||||||
|
|
||||||
|
expect(entity).to be_an_instance_of Entities::TestEntity
|
||||||
|
expect(entity.test).to eq("asdf")
|
||||||
|
expect(entity.additional_xml_elements).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "relayable signature verification feature support" do
|
||||||
|
it "calls signatures verification on relayable unpack" do
|
||||||
|
entity = FactoryGirl.build(:comment_entity, author: alice.diaspora_id)
|
||||||
|
entity_xml = entity.to_xml
|
||||||
|
entity_xml.at_xpath("author_signature").content = nil
|
||||||
|
|
||||||
|
expect {
|
||||||
|
Entities::Comment.from_xml(entity_xml)
|
||||||
|
}.to raise_error DiasporaFederation::Entities::Relayable::SignatureVerificationFailed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "nested entities" do
|
||||||
|
let(:child_entity1) { Entities::TestEntity.new(test: "bla") }
|
||||||
|
let(:child_entity2) { Entities::OtherEntity.new(asdf: "blabla") }
|
||||||
|
let(:nested_entity) {
|
||||||
|
Entities::TestNestedEntity.new(asdf: "QWERT",
|
||||||
|
test: child_entity1,
|
||||||
|
multi: [child_entity2, child_entity2])
|
||||||
|
}
|
||||||
|
let(:nested_payload) { nested_entity.to_xml }
|
||||||
|
|
||||||
|
it "parses the xml with all the nested data" do
|
||||||
|
entity = Entities::TestNestedEntity.from_xml(nested_payload)
|
||||||
|
expect(entity.test.to_h).to eq(child_entity1.to_h)
|
||||||
|
expect(entity.multi).to have(2).items
|
||||||
|
expect(entity.multi.first.to_h).to eq(child_entity2.to_h)
|
||||||
|
expect(entity.asdf).to eq("QWERT")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe ".entity_name" do
|
describe ".entity_name" do
|
||||||
it "strips the module and returns the name underscored" do
|
it "strips the module and returns the name underscored" do
|
||||||
expect(Entities::TestDefaultEntity.entity_name).to eq("test_default_entity")
|
expect(Entities::TestDefaultEntity.entity_name).to eq("test_default_entity")
|
||||||
|
|
@ -105,6 +241,40 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".entity_class" do
|
||||||
|
it "should parse a single word" do
|
||||||
|
expect(Entity.entity_class("entity")).to eq(Entities::Entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should parse with underscore" do
|
||||||
|
expect(Entity.entity_class("test_entity")).to eq(Entities::TestEntity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error when the entity name contains special characters" do
|
||||||
|
expect {
|
||||||
|
Entity.entity_class("te.st-enti/ty")
|
||||||
|
}.to raise_error Entity::InvalidEntityName, "'te.st-enti/ty' is invalid"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error when the entity name contains upper case letters" do
|
||||||
|
expect {
|
||||||
|
Entity.entity_class("TestEntity")
|
||||||
|
}.to raise_error Entity::InvalidEntityName, "'TestEntity' is invalid"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error when the entity name contains numbers" do
|
||||||
|
expect {
|
||||||
|
Entity.entity_class("te5t_ent1ty_w1th_number5")
|
||||||
|
}.to raise_error Entity::InvalidEntityName, "'te5t_ent1ty_w1th_number5' is invalid"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error when the entity is unknown" do
|
||||||
|
expect {
|
||||||
|
Entity.entity_class("unknown_entity")
|
||||||
|
}.to raise_error Entity::UnknownEntity, "'UnknownEntity' not found"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "nested entities" do
|
context "nested entities" do
|
||||||
let(:nested_data) {
|
let(:nested_data) {
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -66,25 +66,10 @@ XML
|
||||||
}.to raise_error ArgumentError, "only Nokogiri::XML::Element allowed"
|
}.to raise_error ArgumentError, "only Nokogiri::XML::Element allowed"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises an error when the entity is unknown" do
|
|
||||||
xml = <<-XML
|
|
||||||
<XML>
|
|
||||||
<post>
|
|
||||||
<unknown_entity>
|
|
||||||
<test>asdf</test>
|
|
||||||
</test_entity>
|
|
||||||
</post>
|
|
||||||
</XML>
|
|
||||||
XML
|
|
||||||
expect {
|
|
||||||
Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root)
|
|
||||||
}.to raise_error Salmon::UnknownEntity, "'UnknownEntity' not found"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "returned object" do
|
context "returned object" do
|
||||||
subject { Salmon::XmlPayload.unpack(payload) }
|
subject { Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(entity_xml).root) }
|
||||||
|
|
||||||
it "#to_h should match entity.to_h" do
|
it "#to_h should match entity.to_h" do
|
||||||
expect(subject.to_h).to eq(entity.to_h)
|
expect(subject.to_h).to eq(entity.to_h)
|
||||||
|
|
@ -94,149 +79,19 @@ XML
|
||||||
expect(subject).to be_an_instance_of Entities::TestEntity
|
expect(subject).to be_an_instance_of Entities::TestEntity
|
||||||
expect(subject.test).to eq("asdf")
|
expect(subject.test).to eq("asdf")
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context "unwrapped xml" do
|
|
||||||
it "allows unwrapped entities" do
|
it "allows unwrapped entities" do
|
||||||
xml = <<-XML
|
xml = <<-XML
|
||||||
<test_entity>
|
<test_entity>
|
||||||
<test>asdf</test>
|
<test>asdf</test>
|
||||||
</test_entity>
|
</test_entity>
|
||||||
XML
|
|
||||||
|
|
||||||
entity = Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root)
|
|
||||||
|
|
||||||
expect(entity).to be_an_instance_of Entities::TestEntity
|
|
||||||
expect(entity.test).to eq("asdf")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "parsing" do
|
|
||||||
it "uses xml_name for parsing" do
|
|
||||||
xml = <<-XML.strip
|
|
||||||
<XML>
|
|
||||||
<post>
|
|
||||||
<test_entity_with_xml_name>
|
|
||||||
<test>asdf</test>
|
|
||||||
<asdf>qwer</asdf>
|
|
||||||
</test_entity>
|
|
||||||
</post>
|
|
||||||
</XML>
|
|
||||||
XML
|
|
||||||
entity = Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root)
|
|
||||||
|
|
||||||
expect(entity).to be_an_instance_of Entities::TestEntityWithXmlName
|
|
||||||
expect(entity.test).to eq("asdf")
|
|
||||||
expect(entity.qwer).to eq("qwer")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "allows name for parsing even when property has a xml_name" do
|
|
||||||
xml = <<-XML.strip
|
|
||||||
<XML>
|
|
||||||
<post>
|
|
||||||
<test_entity_with_xml_name>
|
|
||||||
<test>asdf</test>
|
|
||||||
<qwer>qwer</qwer>
|
|
||||||
</test_entity>
|
|
||||||
</post>
|
|
||||||
</XML>
|
|
||||||
XML
|
|
||||||
entity = Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root)
|
|
||||||
|
|
||||||
expect(entity).to be_an_instance_of Entities::TestEntityWithXmlName
|
|
||||||
expect(entity.test).to eq("asdf")
|
|
||||||
expect(entity.qwer).to eq("qwer")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't drop unknown properties" do
|
|
||||||
xml = <<-XML
|
|
||||||
<XML>
|
|
||||||
<post>
|
|
||||||
<test_entity>
|
|
||||||
<a_prop_from_newer_diaspora_version>some value</a_prop_from_newer_diaspora_version>
|
|
||||||
<test>asdf</test>
|
|
||||||
<some_random_property>another value</some_random_property>
|
|
||||||
</test_entity>
|
|
||||||
</post>
|
|
||||||
</XML>
|
|
||||||
XML
|
XML
|
||||||
|
|
||||||
entity = Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root)
|
entity = Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root)
|
||||||
|
|
||||||
expect(entity).to be_an_instance_of Entities::TestEntity
|
expect(entity).to be_an_instance_of Entities::TestEntity
|
||||||
expect(entity.test).to eq("asdf")
|
expect(entity.test).to eq("asdf")
|
||||||
expect(entity.additional_xml_elements).to eq(
|
|
||||||
"a_prop_from_newer_diaspora_version" => "some value",
|
|
||||||
"some_random_property" => "another value"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates Entity with nil 'additional_xml_elements' if the xml has only known properties" do
|
|
||||||
entity = Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(entity_xml).root)
|
|
||||||
|
|
||||||
expect(entity).to be_an_instance_of Entities::TestEntity
|
|
||||||
expect(entity.test).to eq("asdf")
|
|
||||||
expect(entity.additional_xml_elements).to be_empty
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "relayable signature verification feature support" do
|
|
||||||
it "calls signatures verification on relayable unpack" do
|
|
||||||
entity = FactoryGirl.build(:comment_entity, author: alice.diaspora_id)
|
|
||||||
payload = Salmon::XmlPayload.pack(entity)
|
|
||||||
payload.at_xpath("post/*[1]/author_signature").content = nil
|
|
||||||
|
|
||||||
expect {
|
|
||||||
Salmon::XmlPayload.unpack(payload)
|
|
||||||
}.to raise_error DiasporaFederation::Entities::Relayable::SignatureVerificationFailed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "nested entities" do
|
|
||||||
let(:child_entity1) { Entities::TestEntity.new(test: "bla") }
|
|
||||||
let(:child_entity2) { Entities::OtherEntity.new(asdf: "blabla") }
|
|
||||||
let(:nested_entity) {
|
|
||||||
Entities::TestNestedEntity.new(asdf: "QWERT",
|
|
||||||
test: child_entity1,
|
|
||||||
multi: [child_entity2, child_entity2])
|
|
||||||
}
|
|
||||||
let(:nested_payload) { Salmon::XmlPayload.pack(nested_entity) }
|
|
||||||
|
|
||||||
it "parses the xml with all the nested data" do
|
|
||||||
entity = Salmon::XmlPayload.unpack(nested_payload)
|
|
||||||
expect(entity.test.to_h).to eq(child_entity1.to_h)
|
|
||||||
expect(entity.multi).to have(2).items
|
|
||||||
expect(entity.multi.first.to_h).to eq(child_entity2.to_h)
|
|
||||||
expect(entity.asdf).to eq("QWERT")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe ".entity_class_name" do
|
|
||||||
it "should parse a single word" do
|
|
||||||
expect(Salmon::XmlPayload.send(:entity_class_name, "entity")).to eq("Entity")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should parse with underscore" do
|
|
||||||
expect(Salmon::XmlPayload.send(:entity_class_name, "test_entity")).to eq("TestEntity")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an error when the entity name contains special characters" do
|
|
||||||
expect {
|
|
||||||
Salmon::XmlPayload.send(:entity_class_name, "te.st-enti/ty")
|
|
||||||
}.to raise_error Salmon::InvalidEntityName, "'te.st-enti/ty' is invalid"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an error when the entity name contains upper case letters" do
|
|
||||||
expect {
|
|
||||||
Salmon::XmlPayload.send(:entity_class_name, "TestEntity")
|
|
||||||
}.to raise_error Salmon::InvalidEntityName, "'TestEntity' is invalid"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an error when the entity name contains numbers" do
|
|
||||||
expect {
|
|
||||||
Salmon::XmlPayload.send(:entity_class_name, "te5t_ent1ty_w1th_number5")
|
|
||||||
}.to raise_error Salmon::InvalidEntityName, "'te5t_ent1ty_w1th_number5' is invalid"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue