Skip to content

Commit da51431

Browse files
ramonsmitsbording
andauthored
Resolve incorrectly used urls with ManagementClient when using urls with a path (#1701) (#1709)
Co-authored-by: Brandon Ording <bording@gmail.com>
1 parent 5b10f97 commit da51431

File tree

2 files changed

+166
-6
lines changed

2 files changed

+166
-6
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using NServiceBus;
4+
using NServiceBus.Transport.RabbitMQ;
5+
using NServiceBus.Transport.RabbitMQ.ManagementApi;
6+
using NUnit.Framework;
7+
8+
[TestFixture]
9+
public class ManagementClientApiUrlTests
10+
{
11+
TestableManagementClient client = null!;
12+
13+
[SetUp]
14+
public void Setup() => client = new TestableManagementClient();
15+
16+
[TearDown]
17+
public void Teardown() => client.Dispose();
18+
19+
[Test]
20+
public async Task CreatePolicy_PutsToCorrectUrl()
21+
{
22+
await client.CreatePolicy("my-policy", new Policy
23+
{
24+
Pattern = "my-pattern",
25+
ApplyTo = PolicyTarget.Queues,
26+
Definition = new PolicyDefinition()
27+
28+
});
29+
30+
client.VerifyPut("api/policies/%2Fvhosttest/my-policy");
31+
}
32+
33+
[Test]
34+
public async Task GetBindingsForExchange_GetsFromCorrectUrl()
35+
{
36+
await client.GetBindingsForExchange("my-exchange");
37+
38+
client.VerifyGet("api/exchanges/%2Fvhosttest/my-exchange/bindings/destination");
39+
}
40+
41+
[Test]
42+
public async Task GetBindingsForQueue_GetsFromCorrectUrl()
43+
{
44+
await client.GetBindingsForQueue("my-queue");
45+
46+
client.VerifyGet("api/queues/%2Fvhosttest/my-queue/bindings");
47+
}
48+
49+
[Test]
50+
public async Task GetFeatureFlags_GetsFromCorrectUrl()
51+
{
52+
await client.GetFeatureFlags();
53+
54+
client.VerifyGet("api/feature-flags");
55+
}
56+
57+
[Test]
58+
public async Task GetOverview_GetsFromCorrectUrl()
59+
{
60+
await client.GetOverview();
61+
62+
client.VerifyGet("api/overview");
63+
}
64+
65+
[Test]
66+
public async Task GetQueue_GetsFromCorrectUrl()
67+
{
68+
await client.GetQueue("my-queue");
69+
70+
client.VerifyGet("api/queues/%2Fvhosttest/my-queue");
71+
}
72+
73+
[Test]
74+
public async Task GetQueues_GetsFromCorrectUrlWithPagination()
75+
{
76+
await client.GetQueues(page: 2, pageSize: 100);
77+
78+
client.VerifyGet("api/queues/%2Fvhosttest/?page=2&page_size=100");
79+
}
80+
81+
[Test]
82+
public async Task GetQueues_LastPage_ReturnsMorePagesFalse()
83+
{
84+
client.SetupGetResult(new GetQueuesResult
85+
{
86+
Items = [],
87+
Page = 5,
88+
PageCount = 5
89+
});
90+
91+
await client.GetQueues(page: 5, pageSize: 100);
92+
93+
client.VerifyGet("api/queues/%2Fvhosttest/?page=5&page_size=100");
94+
}
95+
96+
[Test]
97+
public async Task GetQueue_WithSpecialCharacters_EscapesCorrectly()
98+
{
99+
await client.GetQueue("queue/with/slashes");
100+
101+
client.VerifyGet("api/queues/%2Fvhosttest/queue%2Fwith%2Fslashes");
102+
}
103+
104+
class TestableManagementClient()
105+
: ManagementClient(
106+
ConnectionConfiguration.Create("host=.;virtualHost=/vhosttest;username=u;password=p;port=12345"),
107+
new ManagementApiConfiguration("http://localhost:12345/xxx", "u", "p")
108+
)
109+
{
110+
string capturedGetUrl;
111+
string capturedPutUrl;
112+
object getResult;
113+
114+
public void SetupGetResult<T>(T result) => getResult = result;
115+
116+
protected override Task<T> Get<T>(string url, CancellationToken cancellationToken = default)
117+
{
118+
capturedGetUrl = url;
119+
120+
if (getResult is T typedResult)
121+
{
122+
return Task.FromResult(typedResult);
123+
}
124+
125+
return Task.FromResult(CreateDefaultResult<T>());
126+
}
127+
128+
protected override Task Put<T>(string url, T value, CancellationToken cancellationToken = default)
129+
{
130+
capturedPutUrl = url;
131+
return Task.CompletedTask;
132+
}
133+
134+
public void VerifyGet(string expectedUrl) => Assert.That(capturedGetUrl, Is.EqualTo(expectedUrl), $"Expected GET to {expectedUrl}, but got {capturedGetUrl}");
135+
136+
public void VerifyPut(string expectedUrl) => Assert.That(capturedPutUrl, Is.EqualTo(expectedUrl), $"Expected PUT to {expectedUrl}, but got {capturedPutUrl}");
137+
138+
static T CreateDefaultResult<T>()
139+
{
140+
if (typeof(T) == typeof(GetQueuesResult))
141+
{
142+
return (T)(object)new GetQueuesResult
143+
{
144+
Items = [],
145+
Page = 1,
146+
PageCount = 1
147+
};
148+
}
149+
150+
return default;
151+
}
152+
}
153+
}

src/NServiceBus.Transport.RabbitMQ/Administration/ManagementApi/ManagementClient.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration, Managem
2929

3030
if (managementApiConfiguration?.Url is not null)
3131
{
32-
uriBuilder = new UriBuilder(managementApiConfiguration.Url)
32+
var uri = managementApiConfiguration.Url;
33+
34+
if (!uri.EndsWith('/'))
35+
{
36+
uri += "/";
37+
}
38+
39+
uriBuilder = new UriBuilder(uri)
3340
{
3441
UserName = managementApiConfiguration.UserName ?? connectionConfiguration.UserName,
3542
Password = managementApiConfiguration.Password ?? connectionConfiguration.Password
@@ -87,7 +94,7 @@ public async Task<List<Binding>> GetBindingsForExchange(string exchangeName, Can
8794
ArgumentException.ThrowIfNullOrWhiteSpace(exchangeName);
8895

8996
var escapedExchangeName = Uri.EscapeDataString(exchangeName);
90-
var response = await Get<List<Binding>>($"/api/exchanges/{escapedVirtualHost}/{escapedExchangeName}/bindings/destination", cancellationToken).ConfigureAwait(false);
97+
var response = await Get<List<Binding>>($"api/exchanges/{escapedVirtualHost}/{escapedExchangeName}/bindings/destination", cancellationToken).ConfigureAwait(false);
9198

9299
return response;
93100
}
@@ -98,7 +105,7 @@ public async Task<List<Binding>> GetBindingsForQueue(string queueName, Cancellat
98105
ArgumentException.ThrowIfNullOrWhiteSpace(queueName);
99106

100107
var escapedQueueName = Uri.EscapeDataString(queueName);
101-
var response = await Get<List<Binding>>($"/api/queues/{escapedVirtualHost}/{escapedQueueName}/bindings", cancellationToken).ConfigureAwait(false);
108+
var response = await Get<List<Binding>>($"api/queues/{escapedVirtualHost}/{escapedQueueName}/bindings", cancellationToken).ConfigureAwait(false);
102109

103110
return response;
104111
}
@@ -124,19 +131,19 @@ public async Task<Queue> GetQueue(string queueName, CancellationToken cancellati
124131
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pageSize);
125132
ArgumentOutOfRangeException.ThrowIfGreaterThan(pageSize, 500);
126133

127-
var response = await Get<GetQueuesResult>($"/api/queues/{escapedVirtualHost}/?page={page}&page_size={pageSize}", cancellationToken).ConfigureAwait(false);
134+
var response = await Get<GetQueuesResult>($"api/queues/{escapedVirtualHost}/?page={page}&page_size={pageSize}", cancellationToken).ConfigureAwait(false);
128135

129136
return (response.Items, response.Page < response.PageCount);
130137
}
131138

132-
async Task<T> Get<T>(string url, CancellationToken cancellationToken)
139+
protected virtual async Task<T> Get<T>(string url, CancellationToken cancellationToken = default)
133140
{
134141
var response = await httpClient.GetFromJsonAsync<T>(url, cancellationToken).ConfigureAwait(false);
135142

136143
return response ?? throw new HttpRequestException("RabbitMQ management API returned success but deserializing the response body returned null.");
137144
}
138145

139-
async Task Put<T>(string url, T value, CancellationToken cancellationToken)
146+
protected virtual async Task Put<T>(string url, T value, CancellationToken cancellationToken = default)
140147
{
141148
using var response = await httpClient.PutAsJsonAsync(url, value, cancellationToken).ConfigureAwait(false);
142149

0 commit comments

Comments
 (0)