diff --git a/fake_sqs.gemspec b/fake_sqs.gemspec index d6188d9..e32a8a6 100644 --- a/fake_sqs.gemspec +++ b/fake_sqs.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |gem| gem.require_paths = ["lib"] gem.license = "MIT" - gem.add_dependency "sinatra" + gem.add_dependency "sinatra", "~> 1" gem.add_dependency "builder" gem.add_development_dependency "rspec" @@ -27,6 +27,6 @@ Gem::Specification.new do |gem| gem.add_development_dependency "faraday" gem.add_development_dependency "thin" gem.add_development_dependency "verbose_hash_fetch" - gem.add_development_dependency "activesupport" + gem.add_development_dependency "activesupport", "< 5" end diff --git a/lib/fake_sqs/actions/receive_message.rb b/lib/fake_sqs/actions/receive_message.rb index 55d3e55..b479e3c 100644 --- a/lib/fake_sqs/actions/receive_message.rb +++ b/lib/fake_sqs/actions/receive_message.rb @@ -11,13 +11,30 @@ def initialize(options = {}) def call(name, params) queue = @queues.get(name) messages = queue.receive_message(params) + filtered_message_attribute_names = [] + params.select{|k,v | k =~ /MessageAttributeName\.\d+/}.each do |key, value| + filtered_message_attribute_names << value + end @responder.call :ReceiveMessage do |xml| messages.each do |receipt, message| xml.Message do xml.MessageId message.id xml.ReceiptHandle receipt xml.MD5OfBody message.md5 + xml.MD5OfMessageAttributes message.message_attributes_md5 xml.Body message.body + message.message_attributes.each do |attribute| + if filtered_message_attribute_names.include?("All") || filtered_message_attribute_names.include?(attribute) + xml.MessageAttribute do + xml.Name attribute["Name"] + xml.Value do + xml.StringValue attribute["Value.StringValue"] if attribute["Value.StringValue"] + xml.BinaryValue attribute["Value.BinaryValue"] if attribute["Value.BinaryValue"] + xml.DataType attribute["Value.DataType"] + end + end + end + end end end end diff --git a/lib/fake_sqs/actions/send_message.rb b/lib/fake_sqs/actions/send_message.rb index 79adcd1..26440b8 100644 --- a/lib/fake_sqs/actions/send_message.rb +++ b/lib/fake_sqs/actions/send_message.rb @@ -13,6 +13,7 @@ def call(name, params) message = queue.send_message(params) @responder.call :SendMessage do |xml| xml.MD5OfMessageBody message.md5 + xml.MD5OfMessageAttributes message.message_attributes_md5 xml.MessageId message.id end end diff --git a/lib/fake_sqs/message.rb b/lib/fake_sqs/message.rb index 225147c..7e4c0df 100644 --- a/lib/fake_sqs/message.rb +++ b/lib/fake_sqs/message.rb @@ -3,13 +3,14 @@ module FakeSQS class Message - attr_reader :body, :id, :md5 + attr_reader :body, :id, :md5, :message_attributes attr_accessor :visibility_timeout def initialize(options = {}) @body = options.fetch("MessageBody") @id = options.fetch("Id") { SecureRandom.uuid } @md5 = options.fetch("MD5") { Digest::MD5.hexdigest(@body) } + @message_attributes = extract_attributes(options) end def expire! @@ -32,5 +33,48 @@ def attributes } end + def message_attributes_md5 + sorted_attributes = @message_attributes.sort { |a,b| a["Name"] <=> b["Name"] } + + buffer = sorted_attributes.each_with_object([]) do |attribute, buffer| + add_string_to_buffer(attribute["Name"], buffer) + add_string_to_buffer(attribute["Value.DataType"], buffer) + + if (attribute["Value.StringValue"]) + buffer << 1 + add_string_to_buffer(attribute["Value.StringValue"], buffer) + elsif (attribute["Value.BinaryValue"]) + buffer << 2 + add_binary_to_buffer(attribute["Value.BinaryValue"], buffer) + end + end + Digest::MD5.hexdigest(buffer.pack("C*")) + end + + def add_string_to_buffer(string, buffer) + string_bytes = string.force_encoding('UTF-8').bytes.to_a + buffer.concat [string_bytes.size].pack("N").bytes.to_a + buffer.concat string_bytes + end + + def add_binary_to_buffer(binary, buffer) + binary_bytes = binary.unpack("m*")[0].bytes.to_a + buffer.concat [binary_bytes.size].pack("N").bytes.to_a + buffer.concat binary_bytes + end + + private + def extract_attributes(options) + attributes = [] + options.each {|key, value| + if /MessageAttribute\.(?\d+)\.(?.*)/ =~ key + index = attr_index.to_i - 1 + attributes[index] = Hash.new unless attributes[index] + attributes[index][attr_name] = value + end + } + attributes + end + end end diff --git a/spec/acceptance/message_actions_spec.rb b/spec/acceptance/message_actions_spec.rb index 8008023..9f2f11b 100644 --- a/spec/acceptance/message_actions_spec.rb +++ b/spec/acceptance/message_actions_spec.rb @@ -26,6 +26,24 @@ expect(result.message_id.size).to eq 36 end + specify "SendMessage with message_attributes" do + sent_message = sqs.send_message(queue_url: queue_url, message_body: "this is my message", + message_attributes: {"foo_class"=> {string_value: "FooWorker", data_type: "String"}}) + + received_message = sqs.receive_message(queue_url: queue_url, message_attribute_names: ["All"]).messages.first + + expect(sent_message.md5_of_message_attributes).to eq "e3a8318d4639138c2c6fdd04e1f69c8b" + expect(sent_message.md5_of_message_attributes).to eq received_message.md5_of_message_attributes + + expect(received_message.message_attributes.keys).to eq ["foo_class"] + message_attributes = received_message.message_attributes["foo_class"] + expect(message_attributes.string_value).to eq "FooWorker" + expect(message_attributes.binary_value).to be_nil + expect(message_attributes.string_list_values).to eq [] + expect(message_attributes.binary_list_values).to eq [] + expect(message_attributes.data_type).to eq "String" + end + specify "ReceiveMessage" do body = "test 123" @@ -193,7 +211,7 @@ ) expect(nothing.messages.size).to eq 0 - sleep(5) + sleep(7) same_message = sqs.receive_message( queue_url: queue_url, diff --git a/spec/unit/message_spec.rb b/spec/unit/message_spec.rb index c6e1901..500c768 100644 --- a/spec/unit/message_spec.rb +++ b/spec/unit/message_spec.rb @@ -20,6 +20,55 @@ end + describe "#message_attributes" do + + it "has message attributes" do + body = {"MessageBody" => "abc"} + attributes = create_attributes [ + {name: "one", string_value: "A String Value", data_type:"String"}, + {name: "two", string_value: "35", data_type:"Number"}, + {name: "three", binary_value: "c29tZSBiaW5hcnkgZGF0YQ==", data_type:"Binary"} + ] + message = create_message(body.merge attributes) + + expect(message.message_attributes.size).to eq 3 + expect(message.message_attributes_md5).to eq "6d31a67b8fa3c1a74d030c5de73fd7e2" + end + + it "calculates string attribute md5" do + body = {"MessageBody" => "abc"} + attributes = create_attributes [ + {name: "one", string_value: "A String Value", data_type:"String"} + ] + message = create_message(body.merge attributes) + + expect(message.message_attributes_md5).to eq "88bb810f131daa54b83485598cc35693" + end + + it "calculates number attribute md5" do + body = {"MessageBody" => "abc"} + attributes = create_attributes [ + {name: "two", string_value: "35", data_type:"Number"} + ] + message = create_message(body.merge attributes) + + expect(message.message_attributes_md5).to eq "7eb7af82e3ed82aef934e78b9ed11f12" + end + + it "calculates binary attribute md5" do + body = {"MessageBody" => "abc"} + attributes = create_attributes [ + {name: "three", binary_value: "c29tZSBiaW5hcnkgZGF0YQ==", data_type: "Binary"} + ] + message = create_message(body.merge attributes) + + expect(message.message_attributes_md5).to eq "c0f297612d491707df87d6444ecb4817" + end + + + end + + describe "#id" do it "is generated" do @@ -64,4 +113,17 @@ def create_message(options = {}) FakeSQS::Message.new({"MessageBody" => "test"}.merge(options)) end + def create_attributes(attributes = []) + result = {} + + attributes.each_with_index do |attribute, index| + result["MessageAttribute.#{index+1}.Name"] = attribute[:name] if attribute[:name] + result["MessageAttribute.#{index+1}.Value.StringValue"] = attribute[:string_value] if attribute[:string_value] + result["MessageAttribute.#{index+1}.Value.BinaryValue"] = attribute[:binary_value] if attribute[:binary_value] + result["MessageAttribute.#{index+1}.Value.DataType"] = attribute[:data_type] if attribute[:data_type] + end + + result + end + end