diff --git a/README.md b/README.md index c3958d6a8..8607bf9bb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ -Run "rake" to run all tests. +# Azure Ruby SDK -Run "gem build azure.gemspec" to build gem. \ No newline at end of file +## Generating documentation + +Run the following command: + + rake doc + +This will generate the API documentation in the `./doc` directory. + +## Running tests + +In order to run the tests, run `rake`. + +This will run all the unit tests, and then attempt to run the integration tests, +which need a real azure server running. + +In order for the integration tests to run, you need the following ENV variables: + +* `AZURE_ACCOUNT_NAME`: The name of the storage account you're using. + - If testing against the emulator, this must be `devstoreaccount1` +* `AZURE_ACCESS_KEY`: The Base64-encoded Access Key for your storage account. + - If testing against the emulator, this must be + `Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==` +* `AZURE_BLOB_HOST`: Pointing to the server running the Azure platform. + - If testing against the real thing: `http://.blob.core.windows.net` + - If testing against the emulator: `http://localhost:10000/` +* `AZURE_QUEUE_HOST`: Pointing to the server running the Azure platform. + - If testing against the real thing: `http://.queue.core.windows.net` + - If testing against the emulator: `http://localhost:10001/` +* `AZURE_TABLE_HOST`: Pointing to the server running the Azure platform. + - If testing against the real thing: `http://.table.core.windows.net` + - If testing against the emulator: `http://localhost:10002/` +* `AZURE_ACS_NAMESPACE`: a ServiceBus management namespace. +* `AZURE_SB_ACCESS_KEY`: The Base64-encoded Access Key for your ServiceBus + namespace. +* `AZURE_SB_ISSUER`: The name of the issuer for the ServiceBus. This should be `owner` diff --git a/Rakefile b/Rakefile index 1639455cf..6d4e9ce88 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,10 @@ require "rake/testtask" require "rubygems/package_task" +task :doc do + system "yard --plugin yard-tomdoc -o doc/ -" +end + gem_spec = eval(File.read("./azure.gemspec")) Gem::PackageTask.new(gem_spec) do |pkg| pkg.need_zip = false @@ -8,6 +12,21 @@ Gem::PackageTask.new(gem_spec) do |pkg| end namespace :test do + task :require_environment do + unset_environment = [ + ENV.fetch("AZURE_ACCOUNT_NAME", nil), + ENV.fetch("AZURE_ACCESS_KEY", nil), + ENV.fetch("AZURE_TABLE_HOST", nil), + ENV.fetch("AZURE_BLOB_HOST", nil), + ENV.fetch("AZURE_QUEUE_HOST", nil), + ENV.fetch("AZURE_ACS_NAMESPACE", nil), + ENV.fetch("AZURE_SB_ACCESS_KEY", nil), + ENV.fetch("AZURE_SB_ISSUER", nil) + ].include?(nil) + + abort "[ABORTING] Configure your environment to run the integration tests" if unset_environment + end + Rake::TestTask.new :unit do |t| t.pattern = "test/unit/**/*_test.rb" t.verbose = true @@ -20,6 +39,8 @@ namespace :test do t.libs = ["lib", "test"] end + task :integration => :require_environment + namespace :integration do def component_task(component) Rake::TestTask.new component do |t| @@ -27,32 +48,17 @@ namespace :test do t.verbose = true t.libs = ["lib", "test"] end + + task component => "test:require_environment" end component_task :tables component_task :blobs component_task :queues component_task :service_bus - - task :conditionally do - name = ENV.fetch("AZURE_ACCOUNT_NAME", nil) - key = ENV.fetch("AZURE_ACCESS_KEY", nil) - t_host = ENV.fetch("AZURE_TABLE_HOST", nil) - b_host = ENV.fetch("AZURE_BLOB_HOST", nil) - q_host = ENV.fetch("AZURE_QUEUE_HOST", nil) - acs_namespace = ENV.fetch("AZURE_ACS_NAMESPACE", nil) - sb_access_key = ENV.fetch("AZURE_SB_ACCESS_KEY", nil) - sb_issuer = ENV.fetch("AZURE_SB_ISSUER", nil) - - if name && key && t_host && b_host && q_host && acs_namespace && sb_access_key && sb_issuer - Rake::Task["test:integration"].invoke - else - warn "[WARNING] Configure your environment to run the integration tests" - end - end end - Rake::TestTask.new :cleanup do |t| + task :cleanup => :require_environment do $:.unshift "lib" require 'azure' @@ -76,6 +82,6 @@ namespace :test do end end -task :test => ["test:unit", "test:integration:conditionally"] +task :test => ["test:unit", "test:integration"] task default: :test diff --git a/azure.gemspec b/azure.gemspec index a342a0937..fc0efa476 100644 --- a/azure.gemspec +++ b/azure.gemspec @@ -2,22 +2,20 @@ require "date" Gem::Specification.new do |s| s.name = "azure" - s.version = "0.1.1" - s.date = Date.today.iso8601 + s.version = "0.1.0" - s.authors = ["AppFog","Microsoft"] + s.authors = ["Microsoft"] s.email = "azure@microsoft.com" s.description = "Services and ruby SDKs to access the Windows Azure platform." s.summary = "Implementation of several Windows Azure SDKs in ruby." s.homepage = "http://azure.com" - s.files = `git ls-files | grep -v "^examples/"`.split("\n") + s.files = `git ls-files`.split("\n") - s.add_runtime_dependency("uuid", "~> 2.0") - s.add_runtime_dependency("ratom", "~> 0.6") s.add_runtime_dependency("nokogiri", "~> 1.5") s.add_runtime_dependency("mime-types", "~> 1.0") s.add_development_dependency("rake") s.add_development_dependency("minitest", "~> 3.0") - s.add_development_dependency("em-minitest-spec") + s.add_development_dependency("yard") + s.add_development_dependency("yard-tomdoc") end diff --git a/lib/azure/atom.rb b/lib/azure/atom.rb index 3e7c6571a..855f7e15c 100644 --- a/lib/azure/atom.rb +++ b/lib/azure/atom.rb @@ -1,170 +1,173 @@ -require "atom" +require "time" +require "delegate" +require "nokogiri" require "azure/tables/types" module Azure - # Collection of XML::Node extensions for generating Atom feeds. + # Public: The Atom module includes functionality to generate and parse Atom + # feeds and entries. module Atom - Entry = ::Atom::Entry - Feed = ::Atom::Feed - - # Generates a Data Property, making sure that it's in the correct namespace - # (dataservices). - class Property < XML::Node - - # Public: Set up the property. + # Convenience module so abstract the logic of generating XML. The objects + # that include this module must implement #as_xml, such that it returns a + # Nokogiri::XML::Node. + module Serializable + # Public: Convert this object into XML. # - # name - The property name, without the namespace qualification. - # value - The property's value. - def initialize(name, value) - super("d:#{name}") - self << value - end - - # Public: Set the property's value, and sets the content type based on the - # value's type. - # - # value - The value of the property. - # - # Returns self. - def <<(value) - self["m:type"] = Azure::Tables::Types.type_of(value) - super(value) - end - - def to_xml(*) - self + # Returns a String. + def to_xml + as_xml.to_xml end end - # Represent a list of properties in the proper namespace - # (dataservices/metadata). - class PropertyList < XML::Node - include ::Atom::Xml::Parseable + # Public: An Atom Entry corresponds to a representation of a single object. + # + # In order to parse an entry's XML into a more manageable object, call + # Entry.parse. + # + # In order to generate an XML string from an entry, call #to_xml (included + # from Serializable). + class Entry + include Serializable - add_extension_namespace "d", "http://schemas.microsoft.com/ado/2007/08/dataservices" - - # Public: Initialize the property list. + # Public: Parses a string of XML, returning a new Entry. # - # Yields the PropertyList. - def initialize(o=nil) - super("m:properties") + # xml - A String of XML data. + # + # Returns an Atom::Entry. + def self.parse(xml) + doc = Nokogiri::XML(xml) + new do |entry| + entry.id = (doc % "id").text + entry.updated = Time.parse((doc % "updated").text) + entry.content = (doc % "content").inner_html + yield entry, doc if block_given? + end + end - if o && o.is_a?(LibXML::XML::Reader) - o.node.children.each do |node| - self << node.copy(true) unless node.blank? + # Public: Initializes the Entry. + # + # Yields the new Entry. + def initialize + yield self if block_given? + end + + # Public: Get/Set the Entry's id. + attr_accessor :id + + # Public: Get/Set the Entry's content. + attr_accessor :content + + # Public: Get the Entry's updated-at Time (defaults to now). + def updated + @updated || Time.now + end + + # Public: Set the Entry's updated-at Time. + attr_writer :updated + + # Convert this object into an XML node that can be serialized. + # + # xml - A Nokogiri::XML::Builder to use as the parent node (optional). + # + # Returns a Nokogiri::XML::Node. + def as_xml(xml=Nokogiri::XML::Builder.new) + as_xml = ->(obj, parent) do + if obj.respond_to?(:as_xml) + obj.as_xml(parent) + else + obj.to_s end end - yield self if block_given? - end + xml.entry("xmlns" => "http://www.w3.org/2005/Atom", + "xmlns:m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata", + "xmlns:d" => "http://schemas.microsoft.com/ado/2007/08/dataservices") do |xml| + xml.id(self.id) + xml.updated(self.updated.xmlschema) + xml.title + xml.author do |xml| + xml.name + end - # Public: Add several properties at the same time. - # - # properties - A Hash of property name => property value pairs. - # - # Returns the passed properties. - def merge(properties) - properties.each do |name, value| - self[name] = value + if content.respond_to?(:as_xml) + xml.content("type" => "application/xml") do |xml| + content.as_xml(xml) + end + else + xml.content(content, "type" => "application/xml") + end end - end - # Public: Add a property to this list. This will internally store a - # Atom::Nodes::Property object. - # - # property - The name of the property to be included. - # value - The value of the property. - # - # Returns nothing. - def []=(property, value) - self << Property.new(property, value) - end - - def to_xml(*) - self + xml end end - # Represent an entry's tag, ensuring it has the correct content - # type and that it conforms to the Atom::Content interface so it can be used - # seamlessly with Atom::Entry objects. - class Content < XML::Node - include ::Atom::Xml::Parseable + # Public: An Atom Feed is a list of Entries. + # + # In order to parse a feed's XML into a more manageable object, call + # Feed.parse. + # + # In order to generate an XML string from a feed, call #to_xml (included + # from Serializable). + class Feed + include Serializable - add_extension_namespace "m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" + # Get the Array of entries in this feed. + attr :entries - element "m:properties", class: Azure::Atom::PropertyList - - # Public: Initialize the content node. + # Public: Parses a string of XML, returning a new Feed. # - # Yields self. - def initialize(o=nil) - super("content") - self["type"] = "application/xml" - - if o && o.is_a?(LibXML::XML::Reader) - o.read_inner_xml - o.read - parse o + # xml - A String of XML data. + # + # Returns an Atom::Feed. + def self.parse(xml, entry_parser=Entry) + doc = Nokogiri::XML(xml) + new do |feed| + feed.id = (doc % "id").text + feed.updated = Time.parse((doc % "updated").text) + (doc / "entry").each do |entry| + feed.entries << entry_parser.parse(entry.to_xml) + end end + end + # Public: Initialize the Feed. + # + # Yields the Feed. + def initialize + @entries = [] yield self if block_given? end - # Public: Cast this object into something Atom::Entry can understand as - # content. By returning itself (as a Node) the Entry won't try to escape - # this as CDATA. + # Public: Get/Set the Feed's id. + attr_accessor :id + + # Public: Get the Feed's updated-at Time (defaults to now). + def updated + @updated || Time.now + end + + # Public: Set the Feed's updated-at Time. + attr_writer :updated + + # Convert this object into an XML node that can be serialized. # - # Returns self. - def to_xml(*) - self + # xml - A Nokogiri::XML::Builder to use as the parent node (optional). + # + # Returns a Nokogiri::XML::Node. + def as_xml(xml=Nokogiri::XML::Builder.new) + xml.entry("xmlns" => "http://www.w3.org/2005/Atom", + "xmlns:m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata", + "xmlns:d" => "http://schemas.microsoft.com/ado/2007/08/dataservices") do |xml| + xml.id(self.id) + xml.updated(self.updated.xmlschema) + xml.title + entries.each do |entry| + entry.as_xml(xml) + end + end + xml end end end end - -# FIXME: The rAtom gem doesn't play well when you extend your classes, raising -# weird errors, so we're monkeypatching their classes. This *sucks*. -module Atom - # Public: An Atom feed, that understands the Microsoft ADO namespaces for Data - # services. - class Feed - add_extension_namespace "d", "http://schemas.microsoft.com/ado/2007/08/dataservices" - add_extension_namespace "m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" - end - - # Public: An Atom entry, that understands the Microsoft ADO namespaces for Data - # services. - class Entry - add_extension_namespace "d", "http://schemas.microsoft.com/ado/2007/08/dataservices" - add_extension_namespace "m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" - - element "content", class: Azure::Atom::Content - - # Public: Generate a list of data properties to include in this Entry's - # content. - # FIXME: This method isn't very confident as getter, use content.m_properties instead. - # - # Yields a Nodes::PropertyList. - # - # Example: - # - # entry.properties do |props| - # props["one"] = 1 - # props["two"] = 2 - # end - # - # Returns the XML node for entry's . - def properties(&block) - if !@content - @content = Azure::Atom::Content.new - @m_properties = Azure::Atom::PropertyList.new - @content << @m_properties - end - - yield @m_properties if block_given? - - @m_properties - end - end -end diff --git a/lib/azure/queues.rb b/lib/azure/queues.rb index e766e77b7..774af5b28 100644 --- a/lib/azure/queues.rb +++ b/lib/azure/queues.rb @@ -181,6 +181,8 @@ module Azure def self.get_messages(queue, options = {}, service = Azure::Queues::Services::GetMessages.new) response = service.call(queue.name, options) + options[:visibility_timeout] = options.fetch(:visibility_timeout, 1) + # FIXME: need error handling document = Nokogiri::XML(response.body) (document / "//QueueMessagesList/QueueMessage").map do |node| diff --git a/lib/azure/tables.rb b/lib/azure/tables.rb index d0c203674..6eefed2c8 100644 --- a/lib/azure/tables.rb +++ b/lib/azure/tables.rb @@ -3,7 +3,7 @@ require "azure/tables/table" require "azure/tables/entity" require "azure/tables/entities_collection" require "azure/tables/tables_collection" -require "azure/atom" +require "azure/tables/atom" module Azure module Tables @@ -15,7 +15,7 @@ module Azure # Returns an Array of Table elements. def self.all(query = {}, service=Azure::Tables::Services::QueryTables.new) response = service.call(query) - feed = Atom::Feed.load_feed(response.body) + feed = Atom::Feed.parse(response.body) collection = Azure::Tables::TablesCollection.from_entries(feed.entries, query) collection.continuation_token( @@ -36,7 +36,7 @@ module Azure response = service.call(name) if response.success? - Table.from_entry(Atom::Entry.load_entry(response.body)) + Table.from_entry(Atom::Entry.parse(response.body)) else Table.from_error(response.error) end @@ -54,7 +54,7 @@ module Azure response = service.call(name) if response.success? - Table.from_entry(Atom::Entry.load_entry(response.body)) + Table.from_entry(Atom::Entry.parse(response.body)) else Table.from_error(response.error) end @@ -85,7 +85,7 @@ module Azure if response.success? entity.reset( - Entity.from_entry(Azure::Atom::Entry.load_entry(response.body)) + Entity.from_entry(Atom::Entry.parse(response.body)) ) entity.etag = response.headers["etag"] else @@ -181,7 +181,7 @@ module Azure response = service.call(table.name, query) if response.success? - Entity.from_entry(Azure::Atom::Entry.load_entry(response.body)) + Entity.from_entry(Atom::Entry.parse(response.body)) else nil end @@ -199,7 +199,7 @@ module Azure response = service.call(table.name, query) if response.success? - feed = Atom::Feed.load_feed(response.body) + feed = Atom::Feed.parse(response.body) collection = Azure::Tables::EntitiesCollection.from_entries(table, feed.entries, query) collection.continuation_token( response.headers["x-ms-continuation-nextpartitionkey"], diff --git a/lib/azure/tables/atom.rb b/lib/azure/tables/atom.rb new file mode 100644 index 000000000..42a528f58 --- /dev/null +++ b/lib/azure/tables/atom.rb @@ -0,0 +1,197 @@ +require "azure/atom" + +module Azure + module Tables + # Specific group of extensions to simplify working with entries as used in + # the Table Service. See Azure::Atom for more information. + module Atom + # A table's or entity's entry has a list of properties that represent the + # object in question. + class Entry < Azure::Atom::Entry + def self.parse(xml) # :nodoc: + super(xml) do |entry, doc| + doc.remove_namespaces! + props = doc % "properties" + entry.properties.merge(PropertyList.parse(props.to_xml)) + end + end + + def initialize(*) # :nodoc: + @properties = PropertyList.new + super + end + + # Public: Get the list of properties from this Entry. + # + # Yields the list of properties. + # + # Returns the list of properties. + def properties + yield @properties if block_given? + @properties + end + + # Public: Get the content of this entry. Defaults to its properties. + def content + @content ||= properties + end + end + + class Feed < Azure::Atom::Feed # :nodoc: + def self.parse(xml, entry_parser=Tables::Atom::Entry) + super(xml, entry_parser) + end + end + + # A PropertyList is a hash-like object that groups the meta-properties + # than an Entry can have. + # + # It represents an tag. + class PropertyList + include Azure::Atom::Serializable + include Enumerable + + # Public: Parses a string of XML, returning a new PropertyList. + # + # xml - A String of XML data. + # + # Returns a Tables::Atom::PropertyList. + def self.parse(xml) + doc = Nokogiri::XML(xml) + doc.remove_namespaces! + new do |list| + doc.root.children.reject(&:text?).each do |property| + p = Property.parse(property.to_xml) + list[property.name] = p + end + end + end + + # Public: Initialize the PropertyList. + # + # Yields the new PropertyList. + def initialize + @properties = {} + yield self if block_given? + end + + # Public: Iterate over every property. + # + # Yields each name/value pair. + # + # Returns the list of properties. + def each + @properties.each do |name, property| + yield name, property.value + end + self + end + + # Public: Access the value of a property, cast into the corresponding + # type. See Azure::Table::Types. + # + # name - The name of the property. + # + # Returns the property's value. + def [](name) + @properties[name].value + end + + # Public: Set the value of a property. + # + # name - The name of the property. + # value - The object with this property's value. + # + # Returns the property's value. + def []=(name, value) + value = Property.new(name, value) unless value.respond_to?(:value) + @properties[name] = value + end + + # Public: Merge a hash of name/value pairs into this list of properties. + # + # props - A Hash. + # + # Returns the passed Hash. + def merge(props) + props.each do |name, value| + self[name] = value + end + end + + # Public: Get the size of the PropertyList. + # + # Returns a Fixnum. + def size + @properties.size + end + + # Convert this object into an XML node that can be serialized. + # + # xml - A Nokogiri::XML::Builder to use as the parent node (optional). + # + # Returns a Nokogiri::XML::Node. + def as_xml(xml=Nokogiri::XML::Builder.new) + xml.send("m:properties") do |xml| + @properties.values.each do |property| + property.as_xml(xml) + end + end + + xml + end + end + + # A Property represents a single field of information. It has a name, a + # value, and a type (inferred from the value using Azure::Tables::Types). + class Property + include Azure::Atom::Serializable + + # Public: Parses a string of XML, returning a new PropertyList. + # + # xml - A String of XML data. + # + # Returns a Tables::Atom::PropertyList. + def self.parse(xml) + doc = Nokogiri::XML(xml) + doc.remove_namespaces! + prop = doc.root + value = Azure::Tables::Types.cast(prop.text, prop["type"]) + new(prop.name, value) + end + + # Get the property's name. + attr :name + + # Get the property's value. + attr :value + + # Public: Initialize the Property. + # + # name - The property's name. + # value - The property's value. + def initialize(name, value) + @name = name + @value = value + end + + # Public: Get the type of this property. See Azure::Tables::Types. + # + # Returns a String. + def type + Azure::Tables::Types.type_of(value) + end + + # Convert this object into an XML node that can be serialized. + # + # xml - A Nokogiri::XML::Builder to use as the parent node (optional). + # + # Returns a Nokogiri::XML::Node. + def as_xml(xml=Nokogiri::XML::Builder.new) + xml.send("d:#{name}", value, "m:type" => type) + xml + end + end + end + end +end diff --git a/lib/azure/tables/entity.rb b/lib/azure/tables/entity.rb index 82efe7fcd..6a1dabb2a 100644 --- a/lib/azure/tables/entity.rb +++ b/lib/azure/tables/entity.rb @@ -1,4 +1,3 @@ -require "azure/tables/types" require "azure/tables" require "azure/error" @@ -13,7 +12,7 @@ module Azure # Public: Returns an Entity from an Atom::Entry object. # - # entry - Atom::Entry object representing the Entity. + # entry - Tables::Atom::Entry object representing the Entity. # # Returns Azure::Entity def self.from_entry(entry) @@ -21,13 +20,8 @@ module Azure entity.url = URI(entry.id) - entry.content.m_properties.each do |property_node| - value = Types.cast( - property_node.content, - property_node.attributes["type"] - ) - - entity[property_node.name] = value + entry.properties.each do |name, value| + entity[name] = value end entity diff --git a/lib/azure/tables/service.rb b/lib/azure/tables/service.rb index da5ede701..db6e5357a 100644 --- a/lib/azure/tables/service.rb +++ b/lib/azure/tables/service.rb @@ -3,7 +3,7 @@ require "azure/core/utils/queryable" require "azure/tables/auth/shared_key" require "azure/tables/auth/shared_key_lite" require "azure/tables/uri" -require "azure/atom" +require "azure/tables/atom" module Azure module Tables @@ -59,7 +59,7 @@ module Azure # # Returns a Response. def call(table_name) - body = Atom::Entry.new do |entry| + body = Tables::Atom::Entry.new do |entry| entry.properties["TableName"] = table_name end @@ -89,7 +89,7 @@ module Azure # # Returns a Response. def call(table_name, attributes) - body = Atom::Entry.new do |entry| + body = Tables::Atom::Entry.new do |entry| entry.updated = Time.now.utc entry.properties.merge(attributes) end @@ -116,7 +116,7 @@ module Azure attributes.fetch("RowKey") ) - body = Atom::Entry.new do |entry| + body = Tables::Atom::Entry.new do |entry| entry.id = uri entry.updated = Time.now.utc entry.properties.merge(attributes) @@ -139,7 +139,7 @@ module Azure # # Returns a Response. def call(uri, attributes, etag) - body = Atom::Entry.new do |entry| + body = Tables::Atom::Entry.new do |entry| entry.id = uri entry.updated = Time.now.utc entry.properties.merge(attributes) diff --git a/lib/azure/tables/table.rb b/lib/azure/tables/table.rb index 4a42ae775..79182837e 100644 --- a/lib/azure/tables/table.rb +++ b/lib/azure/tables/table.rb @@ -25,7 +25,7 @@ module Azure # # Returns a Table. def self.from_entry(entry) - name = entry.content.m_properties.children.first.content + name = entry.properties["TableName"] new(name, ::URI.parse(entry.id)) end diff --git a/lib/azure/tables/types.rb b/lib/azure/tables/types.rb index 1f2ba0785..2a791cbc8 100644 --- a/lib/azure/tables/types.rb +++ b/lib/azure/tables/types.rb @@ -44,6 +44,8 @@ module Azure Integer(serialized) when "Edm.Boolean" /true/i === serialized + when "Edm.Guid" + GUID.new(serialized.to_s) else serialized.to_s end diff --git a/test/support/name_generator.rb b/test/support/name_generator.rb new file mode 100644 index 000000000..88811621d --- /dev/null +++ b/test/support/name_generator.rb @@ -0,0 +1,44 @@ +class NameGenerator + def initialize(&cleanup_proc) + @cleanup_proc = cleanup_proc + @names = [] + end + + def name + alpha = ("a".."z").to_a + name = 10.times.map { alpha[Random.rand(alpha.size)]}.join + @names << name + name + end + + def clean + @names.reject! do |name| + @cleanup_proc.call(name) + end + end +end + +TableNameHelper = NameGenerator.new do |name| + table = Azure::Tables::Table.new(name) + Azure::Tables.delete(table) +end + +ContainerNameHelper = NameGenerator.new do |name| + container = Azure::Blobs::Container.new(name) + container.delete +end + +QueueNameHelper = NameGenerator.new do |name| + queue = Azure::Queues::Queue.new(name) + queue.delete +end + +ServiceBusQueueNameHelper = NameGenerator.new do |name| + queue = Azure::ServiceBus::Queues::Queue.new(name) + queue.delete +end + +ServiceBusTopicNameHelper = NameGenerator.new do |name| + topic = Azure::ServiceBus::Topics::Topic.new(name) + topic.delete +end diff --git a/test/unit/atom_test.rb b/test/unit/atom_test.rb index 4b8da2d61..a058cd2c2 100644 --- a/test/unit/atom_test.rb +++ b/test/unit/atom_test.rb @@ -1,58 +1,112 @@ require "test_helper" require "azure/atom" -describe "Generating Atom entries with property lists" do - it "lists the properties in the node" do - entry = Atom::Entry.new do |entry| - entry.properties do |props| - props["Prop1Name"] = "Prop1Value" - props["Prop2Name"] = "Prop2Value" - end - end - - entry.properties.first.name.must_equal "d:Prop1Name" - entry.properties.first.content.must_equal "Prop1Value" - - entry.properties.last.name.must_equal "d:Prop2Name" - entry.properties.last.content.must_equal "Prop2Value" +describe "Parsing Atom" do + let :entry_xml do + <<-XML + + + http://myaccount.table.core.windows.net/Tables('mytable') + + 2009-01-04T17:18:54.7062347Z + + + + + + + + mytable + + + + XML end - it "can bulk-update a property list" do - entry = Atom::Entry.new do |entry| - entry.properties.merge(a: 1, b: 2, c: 3) - end - - doc = XML::Parser.string(entry.to_xml).parse - doc.find("//d:a[text() = '1']").wont_be_empty - doc.find("//d:b[text() = '2']").wont_be_empty - doc.find("//d:c[text() = '3']").wont_be_empty + let :feed_xml do + <<-XML + + + http://myaccount.table.core.windows.net/Tables + Tables + 2009-01-04T17:18:54.7062347Z + + + http://myaccount.table.core.windows.net/Tables('mytable') + + 2009-01-04T17:18:54.7062347Z + + + + + + + + mytable + + + + + XML end - it "can set properties in several ways" do - entry = Atom::Entry.new do |entry| - entry.properties["a"] = 1 - entry.properties.merge(b: 2, c: 3) - entry.properties do |props| - props["d"] = 4 - end - end - - doc = XML::Parser.string(entry.to_xml).parse - doc.find("//d:a[text() = '1']").wont_be_empty - doc.find("//d:b[text() = '2']").wont_be_empty - doc.find("//d:c[text() = '3']").wont_be_empty - doc.find("//d:d[text() = '4']").wont_be_empty + let :feeds_entry_xml do + (Nokogiri::XML(feed_xml) % "entry").to_xml end - it "generates properties with the given data type" do - node = Azure::Atom::Property.new("name", "value") - node["m:type"].must_equal "Edm.String" + it "parses an entry correctly" do + entry = Azure::Atom::Entry.parse(entry_xml) + entry.id.must_equal "http://myaccount.table.core.windows.net/Tables('mytable')" + entry.updated.must_equal Time.parse("2009-01-04T17:18:54.7062347Z") + entry.content.must_match /d:TableName/ end - it "generates properties with the given data name" do - node = Azure::Atom::Property.new("firstName", "value") - node.name.must_equal "d:firstName" - node = Azure::Atom::Property.new(:firstName, "value") - node.name.must_equal "d:firstName" + it "parses a feed correctly" do + feed = Azure::Atom::Feed.parse(feed_xml) + feed.id.must_equal "http://myaccount.table.core.windows.net/Tables" + feed.updated.must_equal Time.parse("2009-01-04T17:18:54.7062347Z") + feed.entries.size.must_equal 1 + + entry = feed.entries.first + entry.id.must_equal "http://myaccount.table.core.windows.net/Tables('mytable')" + end + + it "chooses the entry parser when parsing feeds" do + entry = double() + parser = MiniTest::Mock.new + parser.expect(:parse, entry, [feeds_entry_xml]) + + feed = Azure::Atom::Feed.parse(feed_xml, parser) + + parser.verify + end +end + +describe "Generating Atom" do + class ComplexContent + def as_xml(xml) + xml.foo("bar") + xml + end + end + + it "includes the content of the entry when it's a string" do + entry = Azure::Atom::Entry.new do |entry| + entry.content = "FooBar" + end + + doc = Nokogiri::XML(entry.to_xml) + content = doc % "content" + content.text.must_equal "FooBar" + end + + it "includes the content of the entry when it's an XML structure" do + entry = Azure::Atom::Entry.new do |entry| + entry.content = ComplexContent.new + end + + doc = Nokogiri::XML(entry.to_xml) + foo_in_content = doc % "content > foo" + foo_in_content.text.must_equal "bar" end end diff --git a/test/unit/tables/atom_test.rb b/test/unit/tables/atom_test.rb new file mode 100644 index 000000000..dbddd12bb --- /dev/null +++ b/test/unit/tables/atom_test.rb @@ -0,0 +1,74 @@ +require "test_helper" +require "azure/tables/atom" + +describe "Generating Atom entries with property lists" do + it "can set the properties one by one" do + entry = Azure::Tables::Atom::Entry.new do |entry| + entry.properties["name1"] = "value" + entry.properties["name2"] = "value" + end + entry.properties.size.must_equal 2 + end + + it "can set the properties by merging a hash" do + entry = Azure::Tables::Atom::Entry.new do |entry| + entry.properties.merge("name1" => "value", "name2" => "value") + end + entry.properties.size.must_equal 2 + end + + it "overrides a previous property with the same name" do + entry = Azure::Tables::Atom::Entry.new do |entry| + entry.properties["name"] = "value1" + entry.properties["name"] = "value2" + end + + entry.properties.size.must_equal 1 + entry.properties["name"].must_equal "value2" + end + + it "can pass the properties as a block" do + entry = Azure::Tables::Atom::Entry.new do |entry| + entry.properties do |props| + props["Prop1Name"] = "Prop1Value" + props["Prop2Name"] = "Prop2Value" + end + end + entry.properties.size.must_equal 2 + end + + it "can be serialized to xml" do + entry = Azure::Tables::Atom::Entry.new do |entry| + entry.properties do |props| + props["name"] = "value" + end + end + + doc = Nokogiri::XML(entry.to_xml) + prop = doc.xpath("//d:name", doc.collect_namespaces).first + prop.text.must_equal "value" + end +end + +describe "Parsing property lists" do + let :property_list do + <<-XML + + bar + baz + 20 + + XML + end + + it "can parse a simple property list" do + list = Azure::Tables::Atom::PropertyList.parse(property_list) + list["Foo"].must_equal "bar" + list["Bar"].must_equal "baz" + end + + it "will cast properties to the corresponding class" do + list = Azure::Tables::Atom::PropertyList.parse(property_list) + list["Number"].must_equal 20 + end +end diff --git a/test/unit/tables/entities_collection_test.rb b/test/unit/tables/entities_collection_test.rb index 887bd2e6b..c64780c22 100644 --- a/test/unit/tables/entities_collection_test.rb +++ b/test/unit/tables/entities_collection_test.rb @@ -11,7 +11,7 @@ describe Azure::Tables::EntitiesCollection do before do @table = Azure::Tables::Table.new("table_name") - entries = Azure::Atom::Feed.load_feed(Fixtures[:query_entities_response]).entries + entries = Azure::Tables::Atom::Feed.parse(Fixtures[:query_entities_response]).entries @collection = Azure::Tables::EntitiesCollection.from_entries(@table, entries, {}, service) end diff --git a/test/unit/tables/entity_test.rb b/test/unit/tables/entity_test.rb index b526c5f93..fa003d5d1 100644 --- a/test/unit/tables/entity_test.rb +++ b/test/unit/tables/entity_test.rb @@ -3,7 +3,7 @@ require 'azure/tables/entity' describe Azure::Tables::Entity do before do - @entry = Azure::Atom::Entry.load_entry(Fixtures[:insert_entity_response_entry]) + @entry = Azure::Tables::Atom::Entry.parse(Fixtures[:insert_entity_response_entry]) end it "should be able to instantiate an entity from an xml entry" do diff --git a/test/unit/tables/table_test.rb b/test/unit/tables/table_test.rb index d705270a2..080cc3833 100644 --- a/test/unit/tables/table_test.rb +++ b/test/unit/tables/table_test.rb @@ -3,7 +3,7 @@ require "azure/tables/table" describe Azure::Tables::Table do def entry - Atom::Entry.load_entry(Fixtures[:create_table_response_entry]) + Azure::Tables::Atom::Entry.parse(Fixtures[:create_table_response_entry]) end it "can be instantiated from an entry" do