diff --git a/lib/fog/aws.rb b/lib/fog/aws.rb new file mode 100644 index 000000000..e9cca5b73 --- /dev/null +++ b/lib/fog/aws.rb @@ -0,0 +1,2 @@ +require File.dirname(__FILE__) + '/aws/simpledb' +require File.dirname(__FILE__) + '/aws/s3' diff --git a/lib/fog/aws/s3.rb b/lib/fog/aws/s3.rb new file mode 100644 index 000000000..13e915d79 --- /dev/null +++ b/lib/fog/aws/s3.rb @@ -0,0 +1,81 @@ +require 'rubygems' +require 'base64' +require 'cgi' +require 'curb' +require 'hmac-sha1' + +require File.dirname(__FILE__) + '/s3/parsers' + +module Fog + module AWS + class S3 + + # Initialize connection to S3 + # + # ==== Notes + # options parameter must include values for :aws_access_key_id and + # :aws_secret_access_key in order to create a connection + # + # ==== Examples + # sdb = S3.new( + # :aws_access_key_id => your_aws_access_key_id, + # :aws_secret_access_key => your_aws_secret_access_key + # ) + # + # ==== Parameters + # options<~Hash>:: config arguments for connection. Defaults to {}. + # + # ==== Returns + # S3 object with connection to aws. + def initialize(options={}) + @aws_access_key_id = options[:aws_access_key_id] + @aws_secret_access_key = options[:aws_secret_access_key] + @hmac = HMAC::SHA1.new(@aws_secret_access_key) + @host = options[:host] || 's3.amazonaws.com' + @port = options[:port] || 443 + @scheme = options[:scheme] || 'https' + @connection = Curl::Easy.new("#{@scheme}://#{@host}:#{@port}") + end + + def get_service + request(:get, "#{@scheme}://#{@host}:#{@port}", Fog::Parsers::AWS::S3::GetServiceParser.new) + end + + def put_bucket(name) + request(:put, "#{@scheme}://#{name}.#{@host}:#{@port}", Fog::Parsers::AWS::S3::BasicParser.new) + end + + private + + def request(method, url, parser, data=nil) + @connection.headers['Date'] = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S +0000") + params = [ + method.to_s.upcase, + content_md5 = '', + content_type = '', + @connection.headers['Date'], + canonicalized_amz_headers = nil, + canonicalized_resource = '/' + ] + string_to_sign = params.delete_if {|value| value.nil?}.join("\n") + hmac = @hmac.update(string_to_sign) + signature = Base64.encode64(hmac.digest).strip + + @connection.url = url + @connection.headers['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}" + case method + when :get + p @connection.url + @connection.http_get + when :put + @connection.http_put(data) + end + p @connection.headers + p @connection.body_str + Nokogiri::XML::SAX::Parser.new(parser).parse(@connection.body_str) + parser.result + end + + end + end +end \ No newline at end of file diff --git a/lib/fog/aws/s3/parsers.rb b/lib/fog/aws/s3/parsers.rb new file mode 100644 index 000000000..0142b5d4a --- /dev/null +++ b/lib/fog/aws/s3/parsers.rb @@ -0,0 +1,59 @@ +require File.dirname(__FILE__) + '/../../parser' + +module Fog + module Parsers + module AWS + module S3 + + class BasicParser < Fog::Parsers::Base + + attr_reader :result + + def initialize + reset + end + + def reset + @result = {} + end + + def characters(string) + @value << string.strip + end + + def start_element(name, attrs = []) + @value = '' + end + + end + + class GetServiceParser < Fog::Parsers::AWS::S3::BasicParser + + def reset + @bucket = {} + @result = { :owner => {}, :buckets => [] } + end + + def end_element(name) + case name + when 'Bucket' + @result[:buckets] << @bucket + @bucket = {} + when 'CreationDate' + @bucket[:creation_date] = @value + when 'DisplayName' + @result[:owner][:display_name] = @value + when 'ID' + @result[:owner][:id] = @value + when 'Name' + @bucket[:name] = @value + end + end + + end + + + end + end + end +end diff --git a/lib/fog/aws/simpledb.rb b/lib/fog/aws/simpledb.rb new file mode 100644 index 000000000..bd834ad4d --- /dev/null +++ b/lib/fog/aws/simpledb.rb @@ -0,0 +1,306 @@ +require 'rubygems' +require 'base64' +require 'cgi' +require 'curb' +require 'hmac-sha2' + +require File.dirname(__FILE__) + '/simpledb/parsers' + +module Fog + module AWS + class SimpleDB + + # Initialize connection to SimpleDB + # + # ==== Notes + # options parameter must include values for :aws_access_key_id and + # :aws_secret_access_key in order to create a connection + # + # ==== Examples + # sdb = SimpleDB.new( + # :aws_access_key_id => your_aws_access_key_id, + # :aws_secret_access_key => your_aws_secret_access_key + # ) + # + # ==== Parameters + # options<~Hash>:: config arguments for connection. Defaults to {}. + # + # ==== Returns + # SimpleDB object with connection to aws. + def initialize(options={}) + @aws_access_key_id = options[:aws_access_key_id] + @aws_secret_access_key = options[:aws_secret_access_key] + @hmac = HMAC::SHA256.new(@aws_secret_access_key) + @host = options[:host] || 'sdb.amazonaws.com' + @namespace = options[:namespace] || 'http://sdb.amazonaws.com/doc/2007-11-07/' + @nil_string = options[:nil_string]|| 'nil' + @port = options[:port] || 443 + @scheme = options[:scheme] || 'https' + @connection = Curl::Easy.new("#{@scheme}://#{@host}:#{@port}") + end + + # Create a SimpleDB domain + # + # ==== Parameters + # domain_name<~String>:: Name of domain. Must be between 3 and 255 of the + # following characters: a-z, A-Z, 0-9, '_', '-' and '.'. + # + # ==== Returns + # Hash:: The :request_id and :box_usage values for the request. + def create_domain(domain_name) + request({ + 'Action' => 'CreateDomain', + 'DomainName' => domain_name + }, Fog::Parsers::AWS::SimpleDB::BasicParser.new(@nil_string)) + end + + # Delete a SimpleDB domain + # + # ==== Parameters + # domain_name<~String>:: Name of domain. Must be between 3 and 255 of the + # following characters: a-z, A-Z, 0-9, '_', '-' and '.'. + # + # ==== Returns + # Hash:: The :request_id and :box_usage values for the request. + def delete_domain(domain_name) + request({ + 'Action' => 'DeleteDomain', + 'DomainName' => domain_name + }, Fog::Parsers::AWS::SimpleDB::BasicParser.new(@nil_string)) + end + + # List SimpleDB domains + # + # ==== Parameters + # max_number_of_domains<~Integer>:: Maximum number of domains to return + # between 1 and 100, defaults to 100. + # next_token<~Integer>:: Offset token to start list, defaults to nil. + # + # ==== Returns + # Hash:: + # :request_id and :box_usage + # :domains array of domain names. + # :next_token offset to start with if there are are more domains to list + def list_domains(max_number_of_domains = nil, next_token = nil) + request({ + 'Action' => 'ListDomains', + 'MaxNumberOfDomains' => max_number_of_domains, + 'NextToken' => next_token + }, Fog::Parsers::AWS::SimpleDB::ListDomainsParser.new(@nil_string)) + end + + # List metadata for SimpleDB domain + # + # ==== Parameters + # domain_name<~String>:: Name of domain. Must be between 3 and 255 of the + # following characters: a-z, A-Z, 0-9, '_', '-' and '.'. + # + # ==== Returns + # Hash:: + # :timestamp last update time for metadata. + # :item_count number of items in domain + # :attribute_value_count number of all name/value pairs in domain + # :attribute_name_count number of unique attribute names in domain + # :item_name_size_bytes total size of item names in domain, in bytes + # :attribute_values_size_bytes total size of attributes, in bytes + # :attribute_names_size_bytes total size of unique attribute names, in bytes + def domain_metadata(domain_name) + request({ + 'Action' => 'DomainMetadata', + 'DomainName' => domain_name + }, Fog::Parsers::AWS::SimpleDB::DomainMetadataParser.new(@nil_string)) + end + + # Put items attributes into a SimpleDB domain + # + # ==== Parameters + # domain_name<~String>:: Name of domain. Must be between 3 and 255 of the + # following characters: a-z, A-Z, 0-9, '_', '-' and '.'. + # items<~Hash>:: Keys are the items names and may use any UTF-8 + # characters valid in xml. Control characters and sequences not allowed + # in xml are not valid. Can be up to 1024 bytes long. Values are the + # attributes to add to the given item and may use any UTF-8 characters + # valid in xml. Control characters and sequences not allowed in xml are + # not valid. Each name and value can be up to 1024 bytes long. + # + # ==== Returns + # Hash:: + # :request_id and :box_usage + def batch_put_attributes(domain_name, items, replace_attributes = Hash.new([])) + request({ + 'Action' => 'BatchPutAttributes', + 'DomainName' => domain_name + }.merge!(encode_batch_attributes(items, replace_attributes)), Fog::Parsers::AWS::SimpleDB::BasicParser.new(@nil_string)) + end + + # Put item attributes into a SimpleDB domain + # + # ==== Parameters + # domain_name<~String>:: Name of domain. Must be between 3 and 255 of the + # following characters: a-z, A-Z, 0-9, '_', '-' and '.'. + # item_name<~String>:: Name of the item. May use any UTF-8 characters valid + # in xml. Control characters and sequences not allowed in xml are not + # valid. Can be up to 1024 bytes long. + # attributes<~Hash>:: Name/value pairs to add to the item. Attribute names + # and values may use any UTF-8 characters valid in xml. Control characters + # and sequences not allowed in xml are not valid. Each name and value can + # be up to 1024 bytes long. + # + # ==== Returns + # Hash:: + # :request_id and :box_usage + def put_attributes(domain_name, item_name, attributes, replace_attributes = []) + batch_put_attributes(domain_name, { item_name => attributes }, { item_name => replace_attributes }) + end + + # List metadata for SimpleDB domain + # + # ==== Parameters + # domain_name<~String>:: Name of domain. Must be between 3 and 255 of the + # following characters: a-z, A-Z, 0-9, '_', '-' and '.'. + # item_name<~String>:: Name of the item. May use any UTF-8 characters valid + # in xml. Control characters and sequences not allowed in xml are not + # valid. Can be up to 1024 bytes long. + # attributes<~Hash>:: Name/value pairs to remove from the item. Defaults to + # nil, which will delete the entire item. Attribute names and values may + # use any UTF-8 characters valid in xml. Control characters and sequences + # not allowed in xml are not valid. Each name and value can be up to 1024 + # bytes long. + # + # ==== Returns + # Hash:: :request_id and :box_usage for request + def delete_attributes(domain_name, item_name, attributes = nil) + request({ + 'Action' => 'DeleteAttributes', + 'DomainName' => domain_name, + 'ItemName' => item_name + }.merge!(encode_attributes(attributes)), Fog::Parsers::AWS::SimpleDB::BasicParser.new(@nil_string)) + end + + # List metadata for SimpleDB domain + # + # ==== Parameters + # domain_name<~String>:: Name of domain. Must be between 3 and 255 of the + # following characters: a-z, A-Z, 0-9, '_', '-' and '.'. + # item_name<~String>:: Name of the item. May use any UTF-8 characters valid + # in xml. Control characters and sequences not allowed in xml are not + # valid. Can be up to 1024 bytes long. + # attributes<~Hash>:: Name/value pairs to return from the item. Defaults to + # nil, which will return all attributes. Attribute names and values may use + # any UTF-8 characters valid in xml. Control characters and sequences not + # allowed in xml are not valid. Each name and value can be up to 1024 + # bytes long. + # + # ==== Returns + # Hash:: + # :request_id and :box_usage for request + # :attributes list of attribute name/values for the item + def get_attributes(domain_name, item_name, attributes = nil) + request({ + 'Action' => 'GetAttributes', + 'DomainName' => domain_name, + 'ItemName' => item_name, + }.merge!(encode_attribute_names(attributes)), Fog::Parsers::AWS::SimpleDB::GetAttributesParser.new(@nil_string)) + end + + # Select item data from SimpleDB + # + # ==== Parameters + # select_expression<~String>:: Expression to query domain with. + # next_token<~Integer>:: Offset token to start list, defaults to nil. + # + # ==== Returns + # Hash:: + # :request_id and :box_usage for request + # :items list of attribute name/values for the items formatted as + # { 'item_name' => { 'attribute_name' => ['attribute_value'] }} + # :next_token offset to start with if there are are more domains to list + def select(select_expression, next_token = nil) + request({ + 'Action' => 'Select', + 'NextToken' => next_token, + 'SelectExpression' => select_expression + }, Fog::Parsers::AWS::SimpleDB::SelectParser.new(@nil_string)) + end + + private + + def encode_batch_attributes(items, replace_attributes = Hash.new([])) + encoded_attributes = {} + item_index = 0 + items.keys.each do |item_key| + encoded_attributes["Item.#{item_index}.ItemName"] = item_key.to_s + items[item_key].keys.each do |attribute_key| + attribute_index = 0 + Array(items[item_key][attribute_key]).each do |value| + encoded_attributes["Item.#{item_index}.Attribute.#{attribute_index}.Name"] = attribute_key.to_s + encoded_attributes["Item.#{item_index}.Attribute.#{attribute_index}.Replace"] = 'true' if replace_attributes[item_key].include?(attribute_key) + encoded_attributes["Item.#{item_index}.Attribute.#{attribute_index}.Value"] = sdb_encode(value) + attribute_index += 1 + end + item_index += 1 + end + end if items + encoded_attributes + end + + def encode_attributes(attributes, replace_attributes = []) + encoded_attributes = {} + i = 0 + attributes.keys.each do |key| + Array(attributes[key]).each do |value| + encoded_attributes["Attribute.#{i}.Name"] = key.to_s + encoded_attributes["Attribute.#{i}.Replace"] = 'true' if replace_attributes.include?(key) + encoded_attributes["Attribute.#{i}.Value"] = sdb_encode(value) + i += 1 + end + end if attributes + encoded_attributes + end + + def encode_attribute_names(attributes) + encoded_attribute_names = {} + attributes.each_with_index do |attribute, i| + encoded_attribute_names["AttributeName.#{i}"] = attribute.to_s + end if attributes + encoded_attribute_names + end + + def sdb_encode(value) + value.nil? ? @nil_string : value.to_s + end + + def request(params, parser) + params.delete_if {|key,value| value.nil? } + params.merge!({ + 'AWSAccessKeyId' => @aws_access_key_id, + 'SignatureMethod' => 'HmacSHA256', + 'SignatureVersion' => '2', + 'Timestamp' => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"), + 'Version' => '2007-11-07' + }) + + query = '' + params.keys.sort.each do |key| + query << "#{key}=#{CGI.escape(params[key]).gsub(/\+/, '%20')}&" + end + + method = query.length > 2000 ? 'POST' : 'GET' + string_to_sign = "#{method}\n#{@host}\n/\n" << query.chop + hmac = @hmac.update(string_to_sign) + query << "Signature=#{CGI.escape(Base64.encode64(hmac.digest).strip).gsub(/\+/, '%20')}" + + if method == 'GET' + @connection.url = "#{@scheme}://#{@host}:#{@port}/?#{query}" + @connection.http_get + else + @connection.url = "#{@scheme}://#{@host}:#{@port}" + @connection.http_post(query) + end + Nokogiri::XML::SAX::Parser.new(parser).parse(@connection.body_str) + parser.result + end + + end + end +end diff --git a/lib/fog/aws/simpledb/parsers.rb b/lib/fog/aws/simpledb/parsers.rb new file mode 100644 index 000000000..772cca8d5 --- /dev/null +++ b/lib/fog/aws/simpledb/parsers.rb @@ -0,0 +1,107 @@ +require File.dirname(__FILE__) + '/../../parser' + +module Fog + module Parsers + module AWS + module SimpleDB + class BasicParser < Fog::Parsers::Base + + def initialize(nil_string) + @nil_string = nil_string + reset + end + + def end_element(name) + case(name) + when 'BoxUsage' then result[:box_usage] = @value.to_f + when 'RequestId' then result[:request_id] = @value + end + end + + def sdb_decode(value) + value.eql?(@nil_string) ? nil : value + end + + end + + class ListDomainsParser < Fog::Parsers::AWS::SimpleDB::BasicParser + + def reset + @result = { :domains => [] } + end + + def end_element(name) + case(name) + when 'BoxUsage' then result[:box_usage] = @value.to_f + when 'DomainName' then result[:domains] << @value + when 'NextToken' then result[:next_token] = @value + when 'RequestId' then result[:request_id] = @value + end + end + + end + + class DomainMetadataParser < Fog::Parsers::AWS::SimpleDB::BasicParser + + def reset + @result = {} + end + + def end_element(name) + case name + when 'AttributeNameCount' then result[:attribute_name_count] = @value.to_i + when 'AttributeNamesSizeBytes' then result[:attribute_names_size_bytes] = @value.to_i + when 'AttributeValueCount' then result[:attribute_value_count] = @value.to_i + when 'AttributeValuesSizeBytes' then result[:attribute_values_size_bytes] = @value.to_i + when 'BoxUsage' then result[:box_usage] = @value.to_f + when 'ItemCount' then result[:item_count] = @value.to_i + when 'ItemNamesSizeBytes' then result[:item_names_size_bytes] = @value.to_i + when 'RequestId' then result[:request_id] = @value + when 'Timestamp' then result[:timestamp] = @value + end + end + + end + + class GetAttributesParser < Fog::Parsers::AWS::SimpleDB::BasicParser + + def reset + @attribute = nil + @result = { :attributes => {} } + end + + def end_element(name) + case name + when 'BoxUsage' then result[:box_usage] = @value.to_f + when 'Name' then @attribute = @value + when 'RequestId' then result[:request_id] = @value + when 'Value' then (result[:attributes][@attribute] ||= []) << sdb_decode(@value) + end + end + + end + + class SelectParser < Fog::Parsers::AWS::SimpleDB::BasicParser + + def reset + @item_name = @attribute_name = nil + @result = { :items => {} } + end + + def end_element(name) + case name + when 'BoxUsage' then result[:box_usage] = @value.to_f + when 'Item' then @item_name = @attribute_name = nil + when 'Name' then @item_name.nil? ? @item_name = @value : @attribute_name = @value + when 'NextToken' then result[:next_token] = @value + when 'RequestId' then result[:request_id] = @value + when 'Value' then ((result[:items][@item_name] ||= {})[@attribute_name] ||= []) << sdb_decode(@value) + end + end + + end + + end + end + end +end diff --git a/lib/fog/parser.rb b/lib/fog/parser.rb new file mode 100644 index 000000000..30c1c8ec5 --- /dev/null +++ b/lib/fog/parser.rb @@ -0,0 +1,28 @@ +require 'rubygems' +require 'nokogiri' + +module Fog + module Parsers + class Base < Nokogiri::XML::SAX::Document + + attr_reader :result + + def initialize + reset + end + + def reset + @result = {} + end + + def characters(string) + @value << string.strip + end + + def start_element(name, attrs = []) + @value = '' + end + + end + end +end diff --git a/spec/aws/s3/get_service_spec.rb b/spec/aws/s3/get_service_spec.rb new file mode 100644 index 000000000..fb1676cbb --- /dev/null +++ b/spec/aws/s3/get_service_spec.rb @@ -0,0 +1,9 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'S3.get_service' do + + it 'should return stuff' do + p s3.get_service + end + +end \ No newline at end of file diff --git a/spec/aws/simpledb/batch_put_attributes_spec.rb b/spec/aws/simpledb/batch_put_attributes_spec.rb new file mode 100644 index 000000000..da966a437 --- /dev/null +++ b/spec/aws/simpledb/batch_put_attributes_spec.rb @@ -0,0 +1,35 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.batch_put_attributes' do + + before(:all) do + sdb.create_domain('batch_put_attributes') + end + + after(:all) do + sdb.delete_domain('batch_put_attributes') + end + + it 'should have no attributes for y before batch_put_attributes' do + lambda { sdb.get_attributes('batch_put_attributes', 'a') }.should eventually { |expected| expected[:attributes].should be_empty } + end + + it 'should have no attributes for x before batch_put_attributes' do + lambda { sdb.get_attributes('batch_put_attributes', 'x') }.should eventually { |expected| expected[:attributes].should be_empty } + end + + it 'should return proper attributes from batch_put_attributes' do + actual = sdb.batch_put_attributes('batch_put_attributes', { 'a' => { 'b' => 'c' }, 'x' => { 'y' => 'z' } }) + actual[:request_id].should be_a(String) + actual[:box_usage].should be_a(Float) + end + + it 'should have correct attributes for a after batch_put_attributes' do + lambda { sdb.get_attributes('batch_put_attributes', 'a') }.should eventually { |expected| expected[:attributes].should == { 'b' => ['c'] } } + end + + it 'should have correct attributes for x after batch_put_attributes' do + lambda { sdb.get_attributes('batch_put_attributes', 'x') }.should eventually { |expected| expected[:attributes].should == { 'y' => ['z'] } } + end + +end \ No newline at end of file diff --git a/spec/aws/simpledb/create_domain_spec.rb b/spec/aws/simpledb/create_domain_spec.rb new file mode 100644 index 000000000..9ee3bc6dd --- /dev/null +++ b/spec/aws/simpledb/create_domain_spec.rb @@ -0,0 +1,23 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.create_domain' do + + after(:all) do + sdb.delete_domain('create_domain') + end + + it 'should not include test domain in list_domains before create_domain' do + lambda { sdb.list_domains }.should_not eventually { |expected| expected[:domains].should_not include('create_domain') } + end + + it 'should return proper attributes from create_domain' do + actual = sdb.create_domain('create_domain') + actual[:request_id].should be_a(String) + actual[:box_usage].should be_a(Float) + end + + it 'should include test in list_domains after create_domain' do + lambda { sdb.list_domains }.should eventually { |expected| expected[:domains].should include('create_domain') } + end + +end diff --git a/spec/aws/simpledb/delete_attributes.rb b/spec/aws/simpledb/delete_attributes.rb new file mode 100644 index 000000000..b10ca8680 --- /dev/null +++ b/spec/aws/simpledb/delete_attributes.rb @@ -0,0 +1,28 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.delete_attributes' do + + before(:all) do + sdb.create_domain('delete_attributes') + sdb.put_attributes('delete_attributes', 'foo', { :bar => :baz }) + end + + after(:all) do + sdb.delete_domain('delete_attributes') + end + + it 'should have attributes for foo before delete_attributes' do + lambda { sdb.get_attributes('delete_attributes', 'foo') }.should eventually { |expected| expected[:attributes].should == { 'bar' => ['baz'] } } + end + + it 'should return proper attributes from delete_attributes' do + actual = sdb.delete_attributes('delete_attributes', 'foo') + actual[:request_id].should be_a(String) + actual[:box_usage].should be_a(Float) + end + + it 'should have no attributes for foo after delete_attributes' do + lambda { sdb.get_attributes('delete_attributes', 'foo') }.should eventually { |expected| expected[:attributes].should be_empty } + end + +end diff --git a/spec/aws/simpledb/delete_domain_spec.rb b/spec/aws/simpledb/delete_domain_spec.rb new file mode 100644 index 000000000..699b17e8d --- /dev/null +++ b/spec/aws/simpledb/delete_domain_spec.rb @@ -0,0 +1,23 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.delete_domain' do + + before(:all) do + sdb.create_domain('delete_domain') + end + + it 'should include delete_domain in list_domains before delete_domain' do + lambda { sdb.list_domains }.should eventually { |expected| expected[:domains].should include('delete_domain') } + end + + it 'should return proper attributes' do + actual = sdb.delete_domain('delete_domain') + actual[:request_id].should be_a(String) + actual[:box_usage].should be_a(Float) + end + + it 'should not include delete_domain in list_domains after delete_domain' do + lambda { sdb.list_domains }.should_not eventually { |expected| expected[:domains].should_not include('delete_domain') } + end + +end \ No newline at end of file diff --git a/spec/aws/simpledb/domain_metadata_spec.rb b/spec/aws/simpledb/domain_metadata_spec.rb new file mode 100644 index 000000000..fb4fdc4f7 --- /dev/null +++ b/spec/aws/simpledb/domain_metadata_spec.rb @@ -0,0 +1,40 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.domain_metadata' do + + before(:all) do + sdb.create_domain('domain_metadata') + end + + after(:all) do + sdb.delete_domain('domain_metadata') + end + + it 'should return proper attributes when there are no items' do + results = sdb.domain_metadata('domain_metadata') + results[:attribute_name_count].should == 0 + results[:attribute_names_size_bytes].should == 0 + results[:attribute_value_count].should == 0 + results[:attribute_values_size_bytes].should == 0 + results[:box_usage].should be_a(Float) + results[:item_count].should == 0 + results[:item_names_size_bytes].should == 0 + results[:request_id].should be_a(String) + results[:timestamp].should be_a(String) + end + + it 'should return proper attributes with items' do + sdb.put_attributes('domain_metadata', 'foo', { :bar => :baz }) + results = sdb.domain_metadata('domain_metadata') + results[:attribute_name_count].should == 1 + results[:attribute_names_size_bytes].should == 3 + results[:attribute_value_count].should == 1 + results[:attribute_values_size_bytes].should == 3 + results[:box_usage].should be_a(Float) + results[:item_count].should == 1 + results[:item_names_size_bytes].should == 3 + results[:request_id].should be_a(String) + results[:timestamp].should be_a(String) + end + +end diff --git a/spec/aws/simpledb/get_attributes_spec.rb b/spec/aws/simpledb/get_attributes_spec.rb new file mode 100644 index 000000000..18ddc95a6 --- /dev/null +++ b/spec/aws/simpledb/get_attributes_spec.rb @@ -0,0 +1,22 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.get_attributes' do + + before(:all) do + sdb.create_domain('get_attributes') + end + + after(:all) do + sdb.delete_domain('get_attributes') + end + + it 'should have no attributes for foo before put_attributes' do + lambda { sdb.get_attributes('get_attributes', 'foo') }.should eventually { |expected| expected[:attributes].should be_empty } + end + + it 'should have attributes for foo after put_attributes' do + sdb.put_attributes('get_attributes', 'foo', { :bar => :baz }) + lambda { sdb.get_attributes('get_attributes', 'foo') }.should eventually { |expected| expected[:attributes].should == { 'bar' => ['baz'] } } + end + +end diff --git a/spec/aws/simpledb/list_domains_spec.rb b/spec/aws/simpledb/list_domains_spec.rb new file mode 100644 index 000000000..9726230a5 --- /dev/null +++ b/spec/aws/simpledb/list_domains_spec.rb @@ -0,0 +1,24 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.list_domains' do + + before(:all) do + sdb.create_domain('list_domains') + end + + after(:all) do + sdb.delete_domain('list_domains') + end + + it 'should return proper attributes' do + results = sdb.list_domains + results[:box_usage].should be_a(Float) + results[:domains].should be_an(Array) + results[:request_id].should be_a(String) + end + + it 'should include list_domains in list_domains' do + lambda { sdb.list_domains }.should eventually { |expected| expected[:domains].should include('list_domains') } + end + +end diff --git a/spec/aws/simpledb/put_attributes_spec.rb b/spec/aws/simpledb/put_attributes_spec.rb new file mode 100644 index 000000000..6a902c86a --- /dev/null +++ b/spec/aws/simpledb/put_attributes_spec.rb @@ -0,0 +1,27 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe 'SimpleDB.put_attributes' do + + before(:all) do + sdb.create_domain('put_attributes') + end + + after(:all) do + sdb.delete_domain('put_attributes') + end + + it 'should have no attributes for foo before put_attributes' do + lambda { sdb.get_attributes('put_attributes', 'foo') }.should eventually { |expected| expected[:attributes].should be_empty } + end + + it 'should return proper attributes from put_attributes' do + actual = sdb.put_attributes('put_attributes', 'foo', { 'bar' => 'baz' }) + actual[:request_id].should be_a(String) + actual[:box_usage].should be_a(Float) + end + + it 'should have attributes for foo after put_attributes' do + lambda { sdb.get_attributes('put_attributes', 'foo') }.should eventually { |expected| expected[:attributes].should == { 'bar' => ['baz'] } } + end + +end diff --git a/spec/eventually_spec.rb b/spec/eventually_spec.rb new file mode 100644 index 000000000..3fc5536ae --- /dev/null +++ b/spec/eventually_spec.rb @@ -0,0 +1,39 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe "should eventually { block }" do + + it "should pass if block returns true immediately" do + lambda { true }.should eventually { |expected| expected.should == true } + end + + it "should pass if block returns true after a delay" do + eventually = Eventually.new(true, 1) + lambda { true }.should eventually { |expected| expected.should == eventually.test } + end + + it "should fail if block returns false despite delay" do + lambda { + lambda { true }.should eventually { |expected| expected.should == false } + }.should raise_error(Spec::Expectations::ExpectationNotMetError) + end + +end + +describe "should_not eventually { block }" do + + it "should pass if block returns false immediately" do + lambda { true }.should_not eventually { |expected| expected.should_not == false } + end + + it "should pass if block returns false after a delay" do + eventually = Eventually.new(false, 1) + lambda { true }.should_not eventually { |expected| expected.should_not == eventually.test } + end + + it "should fail if block returns true despite delay" do + lambda { + lambda { true }.should_not eventually { |expected| expected.should_not == true } + }.should raise_error(Spec::Expectations::ExpectationNotMetError) + end + +end diff --git a/spec/fog_spec.rb b/spec/fog_spec.rb deleted file mode 100644 index ac80d6eba..000000000 --- a/spec/fog_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'spec_helper' - -describe "Fog" do - it "fails" do - fail "hey buddy, you should probably rename this file and start specing for real" - end -end diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 000000000..53607ea52 --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1 @@ +--colour diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5a5730c62..f4ab9bdd8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,3 +7,76 @@ require 'fog' Spec::Runner.configure do |config| end + +require 'fog/aws' + +def sdb + @sdb ||= begin + data = File.open(File.expand_path('~/.s3conf/s3config.yml')).read + config = YAML.load(data) + Fog::AWS::SimpleDB.new( + :aws_access_key_id => config['aws_access_key_id'], + :aws_secret_access_key => config['aws_secret_access_key'] + ) + end +end +def s3 + @s3 ||= begin + data = File.open(File.expand_path('~/.s3conf/s3config.yml')).read + config = YAML.load(data) + Fog::AWS::S3.new( + :aws_access_key_id => config['aws_access_key_id'], + :aws_secret_access_key => config['aws_secret_access_key'] + ) + end +end + +module Spec + module Matchers + class Eventually #:nodoc: + def initialize(&block) + @block = block + end + + def matches?(given_proc) + match = nil + [0,2,4,8,16].each do |delay| + begin + sleep(delay) + match = @block[given_proc.call] + break + rescue Spec::Expectations::ExpectationNotMetError => error + raise error if delay == 16 + end + end + match + end + end + + # :call-seq + # should eventually() { |expected| ... } + # Matches if block matches within 30 seconds + # + # == Examples + # + # lambda { do_something_eventually_returning_true }.should eventually {|expected| expected.should be_true } + # + # lambda { do_something_eventually_returning_false }.should eventually {|expected| expected.should_not be_true } + def eventually(&block) + Matchers::Eventually.new(&block) + end + end +end + + +class Eventually + def initialize(result, delay) + @result = result + @delay = delay + end + + def test + @start ||= Time.now + (Time.now - @start <= @delay) ? !@result : @result + end +end