Skip to content

Commit bb26abf

Browse files
authored
Merge pull request #10 from OpenMatchmaking/hotfix-send
Refactoring send/send_and_wait/consume GenServer calls
2 parents ce1d4d5 + d162062 commit bb26abf

File tree

4 files changed

+186
-26
lines changed

4 files changed

+186
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The package can be installed via adding the `spotter` dependency to your list of
1414

1515
```elixir
1616
def deps do
17-
[{:spotter, "~> 0.5.0"}]
17+
[{:spotter, "~> 0.6.0"}]
1818
end
1919
```
2020

lib/testing/client.ex

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,43 +94,112 @@ defmodule Spotter.Testing.AmqpBlockingClient do
9494

9595
@doc """
9696
Sends a new message without waiting for a response.
97+
98+
The `data` parameter represents a payload, added to the message body.
99+
The `ops` parameter represented as a keyword, that can contain keys:
100+
101+
* `:request_exchange` - Exchange key, through which will be published message.
102+
* `:request_routing_key` - Routing key, used for pushing message to the certain queue.
103+
* `:mandatory` - If set, returns an error if the broker can't route the message to a queue (default `false`);
104+
* `:immediate` - If set, returns an error if the broker can't deliver te message to a consumer immediately (default `false`);
105+
* `:content_type` - MIME Content type;
106+
* `:content_encoding` - MIME Content encoding;
107+
* `:headers` - Custom message headers;
108+
* `:persistent` - Determines delivery mode. Messages marked as `persistent` and delivered to `durable` \
109+
queues will be logged to disk;
110+
* `:correlation_id` - Application correlation identifier;
111+
* `:priority` - Message priority, ranging from 0 to 9;
112+
* `:reply_to` - Name of the reply queue;
113+
* `:expiration` - How long the message is valid (in milliseconds);
114+
* `:message_id` - Message identifier;
115+
* `:timestamp` - Timestamp associated with this message (epoch time);
116+
* `:type` - Message type (as a string);
117+
* `:user_id` - User ID. Validated by RabbitMQ against the active connection user;
118+
* `:app_id` - Publishing application ID.
119+
120+
The `call_timeout` parameter determines maximum amount time in milliseconds before exit from the method by timeout.
97121
"""
98122
def send(pid, data, opts, call_timeout \\ 5000) do
99123
GenServer.call(pid, {:send, data, opts}, call_timeout)
100124
end
101125

102126
@doc """
103127
Sends a new message and wait for result.
128+
129+
The `data` parameter represents a payload, added to the message body.
130+
The `ops` parameter represented as a keyword, that can contain keys:
131+
132+
* `:request_exchange` - Exchange key, through which will be published message. Required.
133+
* `:request_routing_key` - Routing key, used for pushing message to the certain queue. Required.
134+
* `:response_queue` - The name of the queue which will be used for tracking responses. Required.
135+
* `:channel_opts` - Keyword list which is used for creating response queue and linking it with the exchange. Required.
136+
* `:mandatory` - If set, returns an error if the broker can't route the message to a queue (default `false`);
137+
* `:immediate` - If set, returns an error if the broker can't deliver te message to a consumer immediately (default `false`);
138+
* `:content_type` - MIME Content type;
139+
* `:content_encoding` - MIME Content encoding;
140+
* `:headers` - Custom message headers;
141+
* `:persistent` - Determines delivery mode. Messages marked as `persistent` and delivered to `durable` \
142+
queues will be logged to disk;
143+
* `:correlation_id` - Application correlation identifier;
144+
* `:priority` - Message priority, ranging from 0 to 9;
145+
* `:reply_to` - Name of the reply queue;
146+
* `:expiration` - How long the message is valid (in milliseconds);
147+
* `:message_id` - Message identifier;
148+
* `:timestamp` - Timestamp associated with this message (epoch time);
149+
* `:type` - Message type (as a string);
150+
* `:user_id` - User ID. Validated by RabbitMQ against the active connection user;
151+
* `:app_id` - Publishing application ID.
152+
153+
The `timeout` parameter determines amout of time before doing next attempt to extract the message.
154+
The `attemps` parameter determines the general amount of attempts to extract the message.
155+
The `call_timeout` parameter determines maximum amount time in milliseconds before exit from the method by timeout.
104156
"""
105157
def send_and_wait(pid, data, opts, timeout \\ 1000, attempts \\ 5, call_timeout \\ 5000) do
106-
GenServer.call(pid, {:send_and_wait, data, opts, timeout, attempts}, call_timeout)
158+
try do
159+
GenServer.call(pid, {:send_and_wait, data, opts, timeout, attempts}, call_timeout)
160+
catch
161+
:exit, _reason -> {:empty, nil}
162+
end
107163
end
108164

109165
@doc """
110166
Returns the message from the certain queue if it exists.
167+
168+
The `queue` parameter represents the name of the queue which will be used for tracking responses.
169+
The `timeout` parameter determines amout of time before doing next attempt to extract the message.
170+
The `attemps` parameter determines the general amount of attempts to extract the message.
171+
The `call_timeout` parameter determines maximum amount time in milliseconds before exit from the method by timeout.
111172
"""
112173
def consume(pid, queue, timeout \\ 1000, attempts \\ 5, call_timeout \\ 500) do
113-
GenServer.call(pid, {:consume_response, queue, timeout, attempts}, call_timeout)
174+
try do
175+
GenServer.call(pid, {:consume_response, queue, timeout, attempts}, call_timeout)
176+
catch
177+
:exit, _reason -> {:empty, nil}
178+
end
114179
end
115180

116181
@doc """
117182
Initializes QoS, a queue and an exchanges for the channel.
183+
184+
The `channel_opts` parameters stores generic information about the response queue and the linked exchange.
185+
The `call_timeout` parameter determines maximum amount time in milliseconds before exit from the method by timeout.
118186
"""
119187
def configure_channel(pid, channel_opts, call_timeout \\ 500) do
120188
GenServer.call(pid, {:configure_channel, channel_opts}, call_timeout)
121189
end
122190

123191
# Internal stuff
124192

125-
defp send_message(channel, routing_key, data, opts) do
126-
exchange_request = Keyword.get(opts, :exchange_request, "")
127-
queue_request = Keyword.get(opts, :queue_request, "")
193+
defp send_message(channel, data, opts) do
194+
request_exchange = Keyword.get(opts, :request_exchange, "")
195+
request_routing_key = Keyword.get(opts, :request_routing_key, "")
196+
response_queue = Keyword.get(opts, :response_queue, "")
128197
publish_options = Keyword.merge(opts, [
129198
persistent: Keyword.get(opts, :persistent, true),
130-
reply_to: routing_key,
199+
reply_to: response_queue,
131200
content_type: Keyword.get(opts, :content_type, "application/json")
132201
])
133-
AMQP.Basic.publish(channel, exchange_request, queue_request, data, publish_options)
202+
AMQP.Basic.publish(channel, request_exchange, request_routing_key, data, publish_options)
134203
end
135204

136205
defp consume_response(channel, queue_name, timeout, attempts) do
@@ -158,20 +227,22 @@ defmodule Spotter.Testing.AmqpBlockingClient do
158227
# Private API
159228

160229
def handle_call({:send, data, opts}, _from, state) do
161-
{:reply, send_message(state[:channel], :undefined, data, opts), state}
230+
{:reply, send_message(state[:channel], data, opts), state}
162231
end
163232

164233
def handle_call({:send_and_wait, data, opts, timeout, attempts}, _from, state) do
165234
channel = state[:channel]
166235
channel_opts = state[:channel_opts]
167-
queue_name = Keyword.get(channel_opts[:queue] || [], :name, :undefined)
168-
routing_key = Keyword.get(channel_opts[:queue] || [], :routing_key, :undefined)
236+
response_queue = Keyword.get(opts, :response_queue, "")
169237

170238
configure(channel, channel_opts)
171-
send_message(channel, routing_key, data, opts)
172-
response = consume_response(state[:channel], queue_name, timeout, attempts)
239+
send_message(channel, data, opts)
240+
response = consume_response(state[:channel], response_queue, timeout, attempts)
241+
242+
if response_queue != "" do
243+
AMQP.Queue.delete(channel, response_queue)
244+
end
173245

174-
AMQP.Queue.delete(channel, queue_name)
175246
{:reply, response, state}
176247
end
177248

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Spotter.MixProject do
22
use Mix.Project
33

4-
@version "0.5.1"
4+
@version "0.6.0"
55

66
def project do
77
[

test/blocking_amqp_client_test.exs

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule SpotterTestingAmqpBlockingClientTest do
44

55
@generic_exchange "test.direct"
66
@queue_name "blocking_client_test"
7+
@secondary_queue_name "secondary_queue"
78

89
@custom_amqp_opts [
910
username: "user",
@@ -29,19 +30,47 @@ defmodule SpotterTestingAmqpBlockingClientTest do
2930
]
3031
]
3132

33+
@secondary_amqp_opts [
34+
username: "user",
35+
password: "password",
36+
host: "rabbitmq",
37+
port: 5672,
38+
virtual_host: "/",
39+
queue: [
40+
name: @secondary_queue_name,
41+
routing_key: @secondary_queue_name,
42+
durable: true,
43+
passive: false,
44+
auto_delete: true
45+
],
46+
exchange: [
47+
name: @generic_exchange,
48+
type: :direct,
49+
durable: true,
50+
passive: true
51+
],
52+
qos: [
53+
prefetch_count: 10
54+
]
55+
]
56+
3257
setup do
33-
{:ok, pid} = start_supervised({AmqpBlockingClient, @custom_amqp_opts})
34-
{:ok, [client: pid]}
58+
{:ok, client} = start_supervised({AmqpBlockingClient, @custom_amqp_opts})
59+
AmqpBlockingClient.configure_channel(client, @custom_amqp_opts)
60+
AmqpBlockingClient.configure_channel(client, @secondary_amqp_opts)
61+
{:ok, [client: client]}
3562
end
3663

37-
test "A AMQP blocking client sends the message and consumes the message from the queue", state do
64+
test "AMQP blocking client sends the message and consumes the message from the queue", state do
3865
client = state[:client]
3966
message = "test"
40-
AmqpBlockingClient.configure_channel(client, @custom_amqp_opts)
4167

4268
send_result = AmqpBlockingClient.send(
4369
client, message,
44-
[queue_request: @queue_name, exchange_request: @generic_exchange]
70+
[
71+
request_exchange: @generic_exchange,
72+
request_routing_key: @queue_name
73+
]
4574
)
4675
assert send_result == :ok
4776

@@ -51,25 +80,85 @@ defmodule SpotterTestingAmqpBlockingClientTest do
5180
stop_supervised(client)
5281
end
5382

54-
test "A AMQP blocking client consumes the message from the queue and returns empty results", state do
83+
test "AMQP blocking client consumes the message from the queue and returns empty results", state do
5584
client = state[:client]
56-
message = "test"
5785
AmqpBlockingClient.configure_channel(client, @custom_amqp_opts)
5886

5987
{:empty, nil} = AmqpBlockingClient.consume(client, @queue_name, 1, 100)
6088
stop_supervised(client)
6189
end
6290

63-
test "A AMQP blocking client sends the message and waits for the response", state do
91+
test "AMQP blocking client sends the message and waits for the response", state do
92+
client = state[:client]
93+
message = "test"
94+
95+
opts = Keyword.merge(
96+
@custom_amqp_opts,
97+
[
98+
request_exchange: @generic_exchange,
99+
request_routing_key: @queue_name,
100+
response_queue: @queue_name
101+
]
102+
)
103+
{response, _meta} = AmqpBlockingClient.send_and_wait(client, message, opts)
104+
assert response == message
105+
106+
stop_supervised(client)
107+
end
108+
109+
test "AMQP blocking client receive {:empty, nil} by timeout for consuming messages", state do
110+
client = state[:client]
111+
112+
{response, meta} = AmqpBlockingClient.consume(client, @queue_name, 100, 1, 1)
113+
assert response == :empty
114+
assert meta == nil
115+
116+
stop_supervised(client)
117+
end
118+
119+
test "AMQP blocking client receive {:empty, nil} by timeout for sent_and_wait call", state do
120+
client = state[:client]
121+
message = "test"
122+
123+
opts = Keyword.merge(
124+
@custom_amqp_opts,
125+
[
126+
request_exchange: @generic_exchange,
127+
request_routing_key: @queue_name,
128+
response_queue: @secondary_queue_name
129+
]
130+
)
131+
{response, meta} = AmqpBlockingClient.send_and_wait(client, message, opts, 100)
132+
assert response == :empty
133+
assert meta == nil
134+
135+
{response_2, _meta_2} = AmqpBlockingClient.consume(client, @queue_name, 100)
136+
assert response_2 == message
137+
stop_supervised(client)
138+
end
139+
140+
test "AMQP blocking client receive message with custom headers", state do
64141
client = state[:client]
65142
message = "test"
66143

67-
channel_options = Keyword.merge(
144+
opts = Keyword.merge(
68145
@custom_amqp_opts,
69-
[queue_request: @queue_name, exchange_request: @generic_exchange]
146+
[
147+
request_exchange: @generic_exchange,
148+
request_routing_key: @queue_name,
149+
response_queue: @queue_name,
150+
headers: [
151+
om_permissions: "matchmaking.test.test; matchmaking.test.api",
152+
om_request_url: "/api/v1/matchmaking/search",
153+
]
154+
]
70155
)
71-
{response, _meta} = AmqpBlockingClient.send_and_wait(client, message, channel_options)
156+
{response, meta} = AmqpBlockingClient.send_and_wait(client, message, opts, 100)
72157
assert response == message
158+
assert meta[:headers] == [
159+
{"om_permissions", :longstr, "matchmaking.test.test; matchmaking.test.api"},
160+
{"om_request_url", :longstr, "/api/v1/matchmaking/search"}
161+
]
73162

74163
stop_supervised(client)
75164
end

0 commit comments

Comments
 (0)