Skip to content

Commit ca9272d

Browse files
authored
Add test coverage for PR 2873 (#2897)
1 parent 7a7b247 commit ca9272d

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<Compile Include="SqlClientMetaDataCollectionNamesTest.cs" />
4242
<Compile Include="SqlDataAdapterTest.cs" />
4343
<Compile Include="SqlConnectionBasicTests.cs" />
44+
<Compile Include="SqlConnectionReadOnlyRoutingTests.cs" />
4445
<Compile Include="SqlCommandTest.cs" />
4546
<Compile Include="SqlConnectionTest.cs" />
4647
<Compile Include="AADAuthenticationTests.cs" />
@@ -67,6 +68,7 @@
6768
<Compile Include="SqlConnectionStringBuilderTest.cs" />
6869
<Compile Include="SerializeSqlTypesTest.cs" />
6970
<Compile Include="TestTdsServer.cs" />
71+
<Compile Include="TestRoutingTdsServer.cs" />
7072
<Compile Include="SqlHelperTest.cs" />
7173
<Compile Include="..\..\src\Microsoft\Data\Common\MultipartIdentifier.cs" />
7274
</ItemGroup>
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Net;
8+
using System.Threading.Tasks;
9+
using Microsoft.SqlServer.TDS.Servers;
10+
using Xunit;
11+
12+
namespace Microsoft.Data.SqlClient.Tests
13+
{
14+
public class SqlConnectionReadOnlyRoutingTests
15+
{
16+
[Fact]
17+
public void NonRoutedConnection()
18+
{
19+
using TestTdsServer server = TestTdsServer.StartTestServer();
20+
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(server.ConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
21+
using SqlConnection connection = new SqlConnection(builder.ConnectionString);
22+
connection.Open();
23+
}
24+
25+
[Fact]
26+
public async Task NonRoutedAsyncConnection()
27+
{
28+
using TestTdsServer server = TestTdsServer.StartTestServer();
29+
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(server.ConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
30+
using SqlConnection connection = new SqlConnection(builder.ConnectionString);
31+
await connection.OpenAsync();
32+
}
33+
34+
[Fact]
35+
public void RoutedConnection()
36+
=> RecursivelyRoutedConnection(1);
37+
38+
[Fact]
39+
public async Task RoutedAsyncConnection()
40+
=> await RecursivelyRoutedAsyncConnection(1);
41+
42+
[Theory]
43+
[InlineData(2)]
44+
[InlineData(9)]
45+
[InlineData(11)] // The driver rejects more than 10 redirects (11 layers of redirecting servers)
46+
public void RecursivelyRoutedConnection(int layers)
47+
{
48+
TestTdsServer innerServer = TestTdsServer.StartTestServer();
49+
IPEndPoint lastEndpoint = innerServer.Endpoint;
50+
Stack<GenericTDSServer> routingLayers = new(layers + 1);
51+
string lastConnectionString = innerServer.ConnectionString;
52+
53+
try
54+
{
55+
routingLayers.Push(innerServer);
56+
for (int i = 0; i < layers; i++)
57+
{
58+
TestRoutingTdsServer router = TestRoutingTdsServer.StartTestServer(lastEndpoint);
59+
60+
routingLayers.Push(router);
61+
lastEndpoint = router.Endpoint;
62+
lastConnectionString = router.ConnectionString;
63+
}
64+
65+
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(lastConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
66+
using SqlConnection connection = new SqlConnection(builder.ConnectionString);
67+
connection.Open();
68+
}
69+
finally
70+
{
71+
while (routingLayers.Count > 0)
72+
{
73+
GenericTDSServer layer = routingLayers.Pop();
74+
75+
if (layer is IDisposable disp)
76+
{
77+
disp.Dispose();
78+
}
79+
}
80+
}
81+
}
82+
83+
[Theory]
84+
[InlineData(2)]
85+
[InlineData(9)]
86+
[InlineData(11)] // The driver rejects more than 10 redirects (11 layers of redirecting servers)
87+
public async Task RecursivelyRoutedAsyncConnection(int layers)
88+
{
89+
TestTdsServer innerServer = TestTdsServer.StartTestServer();
90+
IPEndPoint lastEndpoint = innerServer.Endpoint;
91+
Stack<GenericTDSServer> routingLayers = new(layers + 1);
92+
string lastConnectionString = innerServer.ConnectionString;
93+
94+
try
95+
{
96+
routingLayers.Push(innerServer);
97+
for (int i = 0; i < layers; i++)
98+
{
99+
TestRoutingTdsServer router = TestRoutingTdsServer.StartTestServer(lastEndpoint);
100+
101+
routingLayers.Push(router);
102+
lastEndpoint = router.Endpoint;
103+
lastConnectionString = router.ConnectionString;
104+
}
105+
106+
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(lastConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly };
107+
using SqlConnection connection = new SqlConnection(builder.ConnectionString);
108+
await connection.OpenAsync();
109+
}
110+
finally
111+
{
112+
while (routingLayers.Count > 0)
113+
{
114+
GenericTDSServer layer = routingLayers.Pop();
115+
116+
if (layer is IDisposable disp)
117+
{
118+
disp.Dispose();
119+
}
120+
}
121+
}
122+
}
123+
124+
[Fact]
125+
public void ConnectionRoutingLimit()
126+
{
127+
SqlException sqlEx = Assert.Throws<SqlException>(() => RecursivelyRoutedConnection(12)); // This will fail on the 11th redirect
128+
129+
Assert.Contains("Too many redirections have occurred.", sqlEx.Message, StringComparison.InvariantCultureIgnoreCase);
130+
}
131+
132+
[Fact]
133+
public async Task AsyncConnectionRoutingLimit()
134+
{
135+
SqlException sqlEx = await Assert.ThrowsAsync<SqlException>(() => RecursivelyRoutedAsyncConnection(12)); // This will fail on the 11th redirect
136+
137+
Assert.Contains("Too many redirections have occurred.", sqlEx.Message, StringComparison.InvariantCultureIgnoreCase);
138+
}
139+
}
140+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Net;
7+
using System.Runtime.CompilerServices;
8+
using Microsoft.SqlServer.TDS.EndPoint;
9+
using Microsoft.SqlServer.TDS.Servers;
10+
11+
namespace Microsoft.Data.SqlClient.Tests
12+
{
13+
internal class TestRoutingTdsServer : RoutingTDSServer, IDisposable
14+
{
15+
private const int DefaultConnectionTimeout = 5;
16+
17+
private TDSServerEndPoint _endpoint = null;
18+
19+
private SqlConnectionStringBuilder _connectionStringBuilder;
20+
21+
public TestRoutingTdsServer(RoutingTDSServerArguments args) : base(args) { }
22+
23+
public static TestRoutingTdsServer StartTestServer(IPEndPoint destinationEndpoint, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "")
24+
{
25+
RoutingTDSServerArguments args = new RoutingTDSServerArguments()
26+
{
27+
Log = enableLog ? Console.Out : null,
28+
RoutingTCPHost = destinationEndpoint.Address.ToString() == IPAddress.Any.ToString() ? IPAddress.Loopback.ToString() : destinationEndpoint.Address.ToString(),
29+
RoutingTCPPort = (ushort)destinationEndpoint.Port,
30+
};
31+
32+
if (enableFedAuth)
33+
{
34+
args.FedAuthRequiredPreLoginOption = SqlServer.TDS.PreLogin.TdsPreLoginFedAuthRequiredOption.FedAuthRequired;
35+
}
36+
if (excludeEncryption)
37+
{
38+
args.Encryption = SqlServer.TDS.PreLogin.TDSPreLoginTokenEncryptionType.None;
39+
}
40+
41+
TestRoutingTdsServer server = new TestRoutingTdsServer(args);
42+
server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) };
43+
server._endpoint.EndpointName = methodName;
44+
// The server EventLog should be enabled as it logs the exceptions.
45+
server._endpoint.EventLog = Console.Out;
46+
server._endpoint.Start();
47+
48+
int port = server._endpoint.ServerEndPoint.Port;
49+
server._connectionStringBuilder = excludeEncryption
50+
// Allow encryption to be set when encryption is to be excluded from pre-login response.
51+
? new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Mandatory }
52+
: new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional };
53+
server.ConnectionString = server._connectionStringBuilder.ConnectionString;
54+
server.Endpoint = server._endpoint.ServerEndPoint;
55+
return server;
56+
}
57+
58+
public void Dispose() => _endpoint?.Stop();
59+
60+
public string ConnectionString { get; private set; }
61+
62+
public IPEndPoint Endpoint { get; private set; }
63+
}
64+
}

src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool
5454
? new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Mandatory }
5555
: new SqlConnectionStringBuilder() { DataSource = "localhost," + port, ConnectTimeout = connectionTimeout, Encrypt = SqlConnectionEncryptOption.Optional };
5656
server.ConnectionString = server._connectionStringBuilder.ConnectionString;
57+
server.Endpoint = server._endpoint.ServerEndPoint;
5758
return server;
5859
}
5960

@@ -65,5 +66,7 @@ public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool ena
6566
public void Dispose() => _endpoint?.Stop();
6667

6768
public string ConnectionString { get; private set; }
69+
70+
public IPEndPoint Endpoint { get; private set; }
6871
}
6972
}

0 commit comments

Comments
 (0)