diff --git a/lib/fake_sqs/actions/receive_message.rb b/lib/fake_sqs/actions/receive_message.rb index 6063fb7..aad8b3d 100644 --- a/lib/fake_sqs/actions/receive_message.rb +++ b/lib/fake_sqs/actions/receive_message.rb @@ -18,6 +18,12 @@ def call(queue_name, params) params.select{|k,v | k =~ /AttributeName\.\d+/}.each do |key, value| filtered_attribute_names << value end + + filtered_message_attribute_names = [] + params.select{|k,v | k =~ /MessageAttributeName\.\d+/}.each do |key, value| + filtered_message_attribute_names << value + end + messages = queue.receive_message(params.merge(queues: @queues)) @satisfied = !messages.empty? || expired?(queue, params) @responder.call :ReceiveMessage do |xml| @@ -27,6 +33,7 @@ def call(queue_name, params) xml.ReceiptHandle receipt xml.MD5OfBody message.md5 xml.Body message.body + xml.MD5OfMessageAttributes message.message_attributes_md5 message.attributes.each do |name, value| if filtered_attribute_names.include?("All") || filtered_attribute_names.include?(name) xml.Attribute do @@ -35,6 +42,19 @@ def call(queue_name, params) end end end + + 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 247b540..9410ded 100644 --- a/lib/fake_sqs/actions/send_message.rb +++ b/lib/fake_sqs/actions/send_message.rb @@ -13,6 +13,7 @@ def call(queue_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 0c68ad7..ea034bd 100644 --- a/lib/fake_sqs/message.rb +++ b/lib/fake_sqs/message.rb @@ -5,13 +5,15 @@ module FakeSQS class Message attr_reader :body, :id, :md5, :delay_seconds, :approximate_receive_count, - :sender_id, :approximate_first_receive_timestamp, :sent_timestamp + :sender_id, :approximate_first_receive_timestamp, :sent_timestamp, :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) + @sender_id = options.fetch("SenderId") { SecureRandom.uuid.delete('-').upcase[0...21] } @approximate_receive_count = 0 @sent_timestamp = Time.now.to_i * 1000 @@ -56,6 +58,48 @@ def attributes def receipt Digest::SHA1.hexdigest self.id 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 0fd5a1b..70c1272 100644 --- a/spec/acceptance/message_actions_spec.rb +++ b/spec/acceptance/message_actions_spec.rb @@ -25,6 +25,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" diff --git a/spec/unit/message_spec.rb b/spec/unit/message_spec.rb index 52a8ba7..138a8d9 100644 --- a/spec/unit/message_spec.rb +++ b/spec/unit/message_spec.rb @@ -20,6 +20,51 @@ 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 @@ -73,4 +118,16 @@ 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