Current section
Files
Jump to
Current section
Files
lib/consumer/batch_test.exs
defmodule HareMq.Consumer.BatchTest do
use HareMq.TestCase
alias HareMq.Connection
alias HareMq.Test.RabbitMQManagement, as: Mgmt
defmodule BatchTestPublisher do
use HareMq.Publisher,
exchange: "batch_test_exchange",
routing_key: "batch_test_queue"
end
defmodule BatchTestConsumer do
use HareMq.Consumer,
queue_name: "batch_test_queue",
routing_key: "batch_test_queue",
exchange: "batch_test_exchange",
batch_size: 3
def consume(messages, :batch) do
case :global.whereis_name(:batch_test_pid) do
pid when is_pid(pid) -> send(pid, {:batched, messages})
_ -> :ok
end
:ok
end
end
defmodule BatchTimeoutConsumer do
use HareMq.Consumer,
queue_name: "batch_timeout_queue",
routing_key: "batch_timeout_queue",
exchange: "batch_timeout_exchange",
batch_size: 5,
batch_timeout_ms: 1000
def consume(messages, :batch) do
case :global.whereis_name(:batch_timeout_test_pid) do
pid when is_pid(pid) -> send(pid, {:batched, messages})
_ -> :ok
end
:ok
end
end
defmodule BatchTimeoutPublisher do
use HareMq.Publisher,
exchange: "batch_timeout_exchange",
routing_key: "batch_timeout_queue"
end
defmodule StreamBatchConsumer do
use HareMq.Consumer,
queue_name: "stream_batch_queue",
routing_key: "stream_batch_queue",
exchange: "stream_batch_exchange",
stream: true,
batch_size: 3
def consume(messages, :batch) do
case :global.whereis_name(:stream_batch_test_pid) do
pid when is_pid(pid) -> send(pid, {:batched, messages})
_ -> :ok
end
:ok
end
end
defmodule StreamBatchPublisher do
use HareMq.Publisher,
exchange: "stream_batch_exchange",
routing_key: "stream_batch_queue"
end
defmodule BatchRetryConsumer do
use HareMq.Consumer,
queue_name: "batch_retry_queue",
routing_key: "batch_retry_queue",
exchange: "batch_retry_exchange",
batch_size: 2,
retry_limit: 1,
delay_in_ms: 50_000
def consume(messages, :batch) do
case :global.whereis_name(:batch_retry_test_pid) do
pid when is_pid(pid) -> send(pid, {:batched, messages})
_ -> :ok
end
# First attempt fails, triggering republish
:error
end
end
defmodule BatchRetryPublisher do
use HareMq.Publisher,
exchange: "batch_retry_exchange",
routing_key: "batch_retry_queue"
end
setup do
Application.put_env(:hare_mq, :amqp, url: Mgmt.amqp_url())
Application.put_env(:hare_mq, :configuration, %{reconnect_interval_ms: 100})
stop_global(HareMq.Connection)
{:ok, _pid} = Connection.start_link([])
on_exit(fn ->
stop_global(HareMq.Connection)
# Clean up test queues
for queue <- [
"batch_test_queue",
"batch_test_queue.delay",
"batch_test_queue.dead",
"batch_timeout_queue",
"batch_timeout_queue.delay",
"batch_timeout_queue.dead",
"stream_batch_queue",
"batch_retry_queue",
"batch_retry_queue.delay",
"batch_retry_queue.dead"
] do
Mgmt.delete_queue(queue)
end
end)
:ok
end
@tag :capture_log
test "consumes messages in batches" do
:global.register_name(:batch_test_pid, self())
{:ok, pub_pid} = BatchTestPublisher.start_link()
{:ok, cons_pid} = BatchTestConsumer.start_link()
wait_until(fn ->
case GenServer.call({:global, BatchTestConsumer}, :get_channel) do
%{consumer_tag: tag} when is_binary(tag) -> true
_ -> false
end
end)
# Publish first two messages
wait_until(fn -> BatchTestPublisher.publish_message("msg1") == :ok end)
wait_until(fn -> BatchTestPublisher.publish_message("msg2") == :ok end)
# Should NOT have received batch yet
refute_receive {:batched, _}, 500
# Publish third message
wait_until(fn -> BatchTestPublisher.publish_message("msg3") == :ok end)
# Should have received batch now
assert_receive {:batched, ["msg1", "msg2", "msg3"]}, 2000
assert :ok = GenServer.stop(pub_pid)
assert :ok = GenServer.stop(cons_pid)
after
:global.unregister_name(:batch_test_pid)
end
@tag :capture_log
test "flushes partial batch on timeout" do
:global.register_name(:batch_timeout_test_pid, self())
{:ok, pub_pid} = BatchTimeoutPublisher.start_link()
{:ok, cons_pid} = BatchTimeoutConsumer.start_link()
wait_until(fn ->
case GenServer.call({:global, BatchTimeoutConsumer}, :get_channel) do
%{consumer_tag: tag} when is_binary(tag) -> true
_ -> false
end
end)
# Publish only 2 messages (batch_size is 5)
wait_until(fn -> BatchTimeoutPublisher.publish_message("msg1") == :ok end)
wait_until(fn -> BatchTimeoutPublisher.publish_message("msg2") == :ok end)
# Should NOT have received batch yet (not full)
refute_receive {:batched, _}, 500
# Wait for timeout (1000ms) and a bit more for processing
assert_receive {:batched, ["msg1", "msg2"]}, 2000
assert :ok = GenServer.stop(pub_pid)
assert :ok = GenServer.stop(cons_pid)
after
:global.unregister_name(:batch_timeout_test_pid)
end
@tag :capture_log
test "stream queue batch processing" do
:global.register_name(:stream_batch_test_pid, self())
{:ok, pub_pid} = StreamBatchPublisher.start_link()
{:ok, cons_pid} = StreamBatchConsumer.start_link()
wait_until(fn ->
case GenServer.call({:global, StreamBatchConsumer}, :get_channel) do
%{consumer_tag: tag} when is_binary(tag) -> true
_ -> false
end
end)
# Publish batch for stream consumer
wait_until(fn -> StreamBatchPublisher.publish_message("stream_msg1") == :ok end)
wait_until(fn -> StreamBatchPublisher.publish_message("stream_msg2") == :ok end)
wait_until(fn -> StreamBatchPublisher.publish_message("stream_msg3") == :ok end)
# Should receive batch (stream queues auto-ack)
assert_receive {:batched, ["stream_msg1", "stream_msg2", "stream_msg3"]}, 2000
assert :ok = GenServer.stop(pub_pid)
assert :ok = GenServer.stop(cons_pid)
after
:global.unregister_name(:stream_batch_test_pid)
end
@tag :capture_log
test "batch republish on consumer error" do
:global.register_name(:batch_retry_test_pid, self())
{:ok, pub_pid} = BatchRetryPublisher.start_link()
{:ok, cons_pid} = BatchRetryConsumer.start_link()
wait_until(fn ->
case GenServer.call({:global, BatchRetryConsumer}, :get_channel) do
%{consumer_tag: tag} when is_binary(tag) -> true
_ -> false
end
end)
# Publish batch of 2 messages
wait_until(fn -> BatchRetryPublisher.publish_message("retry_msg1") == :ok end)
wait_until(fn -> BatchRetryPublisher.publish_message("retry_msg2") == :ok end)
# Should receive batch (messages sent to consumer)
assert_receive {:batched, ["retry_msg1", "retry_msg2"]}, 2000
# Wait for async republish task to complete - messages should appear in delay queue
delay_queue = "batch_retry_queue.delay"
assert :ok = Mgmt.wait_for_messages(delay_queue, 2, "/", 5000)
# Messages should be republished to delay queue due to :error return
# Verify messages appear in delay queue (batch_retry_queue.delay - single delay value)
assert :ok = GenServer.stop(pub_pid)
assert :ok = GenServer.stop(cons_pid)
after
:global.unregister_name(:batch_retry_test_pid)
end
@tag :capture_log
test "batch messages successfully republish to delay queue" do
:global.register_name(:batch_retry_test_pid, self())
{:ok, pub_pid} = BatchRetryPublisher.start_link()
{:ok, cons_pid} = BatchRetryConsumer.start_link()
wait_until(fn ->
case GenServer.call({:global, BatchRetryConsumer}, :get_channel) do
%{consumer_tag: tag} when is_binary(tag) -> true
_ -> false
end
end)
# Publish batch
wait_until(fn -> BatchRetryPublisher.publish_message("delay_msg1") == :ok end)
wait_until(fn -> BatchRetryPublisher.publish_message("delay_msg2") == :ok end)
# Verify batch was processed by consumer
assert_receive {:batched, ["delay_msg1", "delay_msg2"]}, 2000
# Wait for async republish task to complete - messages should appear in delay queue
delay_queue = "batch_retry_queue.delay"
assert :ok = Mgmt.wait_for_messages(delay_queue, 2, "/", 5000)
# Check delay queue has messages (consumer returns :error)
# Single delay value creates {queue_name}.delay queue
assert {:ok, _} = Mgmt.wait_for_queue(delay_queue, "/", 3000)
assert :ok = Mgmt.wait_for_messages(delay_queue, 2, "/", 3000)
# Verify queue state
{:ok, delay_info} = Mgmt.get_queue(delay_queue)
assert delay_info["messages"] >= 2,
"Expected at least 2 messages in delay queue, got #{delay_info["messages"]}"
assert :ok = GenServer.stop(pub_pid)
assert :ok = GenServer.stop(cons_pid)
after
:global.unregister_name(:batch_retry_test_pid)
end
end