Skip to content

Commit 1d86660

Browse files
committed
test: add more tests
1 parent a0441c5 commit 1d86660

File tree

7 files changed

+240
-49
lines changed

7 files changed

+240
-49
lines changed

drivers/spanner-ado-net/spanner-ado-net-specification-tests/DbFactoryFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ static DbFactoryFixture()
3434
internal readonly SpannerMockServerFixture MockServerFixture = new ();
3535

3636
public DbProviderFactory Factory => SpannerFactory.Instance;
37-
public string ConnectionString => $"{MockServerFixture.Host}:{MockServerFixture.Port}/projects/p1/instances/i1/databases/d1;UsePlainText=true";
37+
public string ConnectionString => $"Host={MockServerFixture.Host};Port={MockServerFixture.Port};Data Source=projects/p1/instances/i1/databases/d1;UsePlainText=true";
3838

3939
public IReadOnlyCollection<DbType> SupportedDbTypes { get; } = [
4040
DbType.Binary,

drivers/spanner-ado-net/spanner-ado-net-specification-tests/spanner-ado-net-specification-tests.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
<ItemGroup>
1616
<PackageReference Include="AdoNet.Specification.Tests" Version="2.0.0-beta.2" />
1717
<PackageReference Include="Alpha.Google.Cloud.SpannerLib.MockServer" Version="1.0.0-alpha.20251009102337" />
18-
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
18+
<PackageReference Include="coverlet.collector" Version="6.0.4">
19+
<PrivateAssets>all</PrivateAssets>
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
</PackageReference>
1922
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
2023
<PackageReference Include="xunit" Version="2.9.3" />
2124
</ItemGroup>

drivers/spanner-ado-net/spanner-ado-net-tests/AbstractMockServerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static AbstractMockServerTests()
2828

2929
protected SpannerMockServerFixture Fixture;
3030

31-
protected string ConnectionString => $"{Fixture.Host}:{Fixture.Port}/projects/p1/instances/i1/databases/d1;UsePlainText=true";
31+
protected string ConnectionString => $"Host={Fixture.Host};Port={Fixture.Port};Data Source=projects/p1/instances/i1/databases/d1;UsePlainText=true";
3232

3333
[OneTimeSetUp]
3434
public void Setup()

drivers/spanner-ado-net/spanner-ado-net-tests/ConnectionStringBuilderTests.cs

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@
1313
// limitations under the License.
1414

1515
using System.Data;
16+
using Google.Cloud.SpannerLib;
17+
using Google.Cloud.SpannerLib.MockServer;
18+
using Google.Rpc;
19+
using Grpc.Core;
20+
using Status = Grpc.Core.Status;
1621

1722
namespace Google.Cloud.Spanner.DataProvider.Tests;
1823

19-
public class ConnectionStringBuilderTests
24+
public class ConnectionStringBuilderTests : AbstractMockServerTests
2025
{
2126
[Test]
2227
public void Basic()
@@ -167,5 +172,126 @@ public void PropertiesToConnectionString()
167172
Assert.That(builder.ConnectionString, Is.EqualTo("Data Source=projects/project1/instances/instance1/databases/database1;Host=localhost;Port=80;UsePlainText=True;DefaultIsolationLevel=RepeatableRead"));
168173
Assert.That(builder.SpannerLibConnectionString, Is.EqualTo("localhost:80/projects/project1/instances/instance1/databases/database1;UsePlainText=True;DefaultIsolationLevel=RepeatableRead"));
169174
}
175+
176+
[Test]
177+
public void RequiredConnectionStringProperties()
178+
{
179+
using var connection = new SpannerConnection();
180+
Assert.Throws<ArgumentException>(() => connection.ConnectionString = "Host=localhost;Port=80");
181+
}
182+
183+
[Test]
184+
public void FailedConnectThenSucceed()
185+
{
186+
// Close all current pools to ensure that we get a fresh pool.
187+
SpannerPool.CloseSpannerLib();
188+
// TODO: Make this a public property in the mock server.
189+
const string detectDialectQuery =
190+
"select option_value from information_schema.database_options where option_name='database_dialect'";
191+
Fixture.SpannerMock.AddOrUpdateStatementResult(detectDialectQuery, StatementResult.CreateException(new RpcException(new Status(StatusCode.NotFound, "Database not found"))));
192+
using var conn = new SpannerConnection();
193+
conn.ConnectionString = ConnectionString;
194+
var exception = Assert.Throws<SpannerException>(() => conn.Open());
195+
Assert.That(exception.Code, Is.EqualTo(Code.NotFound));
196+
Assert.That(conn.State, Is.EqualTo(ConnectionState.Closed));
197+
198+
// Remove the error and retry.
199+
Fixture.SpannerMock.AddOrUpdateStatementResult(detectDialectQuery, StatementResult.CreateResultSet(new List<Tuple<Google.Cloud.Spanner.V1.TypeCode, string>>
200+
{
201+
Tuple.Create<Google.Cloud.Spanner.V1.TypeCode, string>(V1.TypeCode.String, "option_value")
202+
}, new List<object[]>
203+
{
204+
new object[] { "GOOGLE_STANDARD_SQL" }
205+
}));
206+
conn.Open();
207+
Assert.That(conn.State, Is.EqualTo(ConnectionState.Open));
208+
}
209+
210+
[Test]
211+
[Ignore("Needs connect_timeout property")]
212+
public void OpenTimeout()
213+
{
214+
// TODO: Add connect_timeout property.
215+
var builder = new SpannerConnectionStringBuilder
216+
{
217+
Host = Fixture.Host,
218+
Port = (uint) Fixture.Port,
219+
UsePlainText = true,
220+
DataSource = "projects/project1/instances/instance1/databases/database1",
221+
//ConnectTimeout = TimeSpan.FromMicroseconds(1),
222+
};
223+
using var connection = new SpannerConnection();
224+
connection.ConnectionString = builder.ConnectionString;
225+
var exception = Assert.Throws<SpannerDbException>(() => connection.Open());
226+
Assert.That(exception.ErrorCode, Is.EqualTo((int) Code.DeadlineExceeded));
227+
}
228+
229+
[Test]
230+
[Ignore("OpenAsync must be implemented")]
231+
public async Task OpenCancel()
232+
{
233+
// Close all current pools to ensure that we get a fresh pool.
234+
SpannerPool.CloseSpannerLib();
235+
Fixture.SpannerMock.AddOrUpdateExecutionTime(nameof(Fixture.SpannerMock.CreateSession), ExecutionTime.FromMillis(20, 0));
236+
var builder = new SpannerConnectionStringBuilder
237+
{
238+
Host = Fixture.Host,
239+
Port = (uint) Fixture.Port,
240+
UsePlainText = true,
241+
DataSource = "projects/project1/instances/instance1/databases/database1",
242+
};
243+
await using var connection = new SpannerConnection();
244+
connection.ConnectionString = builder.ConnectionString;
245+
var tokenSource = new CancellationTokenSource(5);
246+
// TODO: Implement actual async opening of connections
247+
Assert.ThrowsAsync<OperationCanceledException>(async () => await connection.OpenAsync(tokenSource.Token));
248+
Assert.That(connection.State, Is.EqualTo(ConnectionState.Closed));
249+
}
250+
251+
[Test]
252+
public void DataSourceProperty()
253+
{
254+
using var conn = new SpannerConnection();
255+
Assert.That(conn.DataSource, Is.EqualTo(string.Empty));
256+
257+
var builder = new SpannerConnectionStringBuilder(ConnectionString);
258+
259+
conn.ConnectionString = builder.ConnectionString;
260+
Assert.That(conn.DataSource, Is.EqualTo("projects/p1/instances/i1/databases/d1"));
261+
}
262+
263+
[Test]
264+
public void SettingConnectionStringWhileOpenThrows()
265+
{
266+
using var conn = new SpannerConnection();
267+
conn.ConnectionString = ConnectionString;
268+
conn.Open();
269+
Assert.That(() => conn.ConnectionString = "", Throws.Exception.TypeOf<InvalidOperationException>());
270+
}
271+
272+
[Test]
273+
public void EmptyConstructor()
274+
{
275+
var conn = new SpannerConnection();
276+
Assert.That(conn.ConnectionTimeout, Is.EqualTo(15));
277+
Assert.That(conn.ConnectionString, Is.SameAs(string.Empty));
278+
Assert.That(() => conn.Open(), Throws.Exception.TypeOf<InvalidOperationException>());
279+
}
280+
281+
[Test]
282+
public void Constructor_with_null_connection_string()
283+
{
284+
var conn = new SpannerConnection(null);
285+
Assert.That(conn.ConnectionString, Is.SameAs(string.Empty));
286+
Assert.That(() => conn.Open(), Throws.Exception.TypeOf<InvalidOperationException>());
287+
}
288+
289+
[Test]
290+
public void Constructor_with_empty_connection_string()
291+
{
292+
var conn = new NpgsqlConnection("");
293+
Assert.That(conn.ConnectionString, Is.SameAs(string.Empty));
294+
Assert.That(() => conn.Open(), Throws.Exception.TypeOf<InvalidOperationException>());
295+
}
170296

171297
}

drivers/spanner-ado-net/spanner-ado-net-tests/spanner-ado-net-tests.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414

1515
<ItemGroup>
1616
<PackageReference Include="Alpha.Google.Cloud.SpannerLib.MockServer" Version="1.0.0-alpha.20251009100446" />
17-
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
17+
<PackageReference Include="coverlet.collector" Version="6.0.4">
18+
<PrivateAssets>all</PrivateAssets>
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
</PackageReference>
1821
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
1922
<PackageReference Include="NUnit" Version="3.14.0"/>
2023
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>

drivers/spanner-ado-net/spanner-ado-net/SpannerConnection.cs

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Reflection;
2121
using System.Threading;
2222
using System.Threading.Tasks;
23+
using Google.Api.Gax;
2324
using Google.Cloud.Spanner.Common.V1;
2425
using Google.Cloud.Spanner.V1;
2526
using Google.Cloud.SpannerLib;
@@ -31,51 +32,49 @@ public class SpannerConnection : DbConnection
3132
public bool UseSharedLibrary { get; set; }
3233

3334
private string _connectionString = string.Empty;
35+
36+
private SpannerConnectionStringBuilder? _connectionStringBuilder;
3437

3538
[AllowNull]
3639
public override string ConnectionString {
3740
get => _connectionString;
3841
set
3942
{
4043
AssertClosed();
41-
if (!IsValidConnectionString(value))
44+
if (string.IsNullOrWhiteSpace(value))
45+
{
46+
_connectionStringBuilder = null;
47+
_connectionString = string.Empty;
48+
}
49+
else
4250
{
43-
throw new ArgumentException($"Invalid connection string: {value}");
51+
var builder = new SpannerConnectionStringBuilder(value);
52+
builder.CheckValid();
53+
_connectionStringBuilder = builder;
54+
_connectionString = value;
4455
}
45-
_connectionString = value ?? string.Empty;
4656
}
4757
}
4858

4959
public override string Database
5060
{
5161
get
5262
{
53-
// TODO: Move this to SpannerLib.
54-
if (String.IsNullOrWhiteSpace(ConnectionString))
63+
if (string.IsNullOrWhiteSpace(ConnectionString) || _connectionStringBuilder == null)
5564
{
5665
return "";
5766
}
58-
var startIndex = ConnectionString.IndexOf("projects/", StringComparison.Ordinal);
59-
if (startIndex == -1)
60-
{
61-
throw new ArgumentException($"Invalid database name in connection string: {ConnectionString}");
62-
}
63-
64-
var endIndex = ConnectionString.IndexOf('?');
65-
if (endIndex == -1)
66-
{
67-
endIndex = ConnectionString.IndexOf(';');
68-
}
69-
if (endIndex == -1)
67+
if (!string.IsNullOrEmpty(_connectionStringBuilder.DataSource))
7068
{
71-
endIndex = ConnectionString.Length;
69+
return _connectionStringBuilder.DataSource;
7270
}
73-
var name = ConnectionString.Substring(startIndex, endIndex);
74-
if (DatabaseName.TryParse(name, false, out var result))
71+
if (!string.IsNullOrEmpty(_connectionStringBuilder.Project) &&
72+
!string.IsNullOrEmpty(_connectionStringBuilder.Instance) &&
73+
!string.IsNullOrEmpty(_connectionStringBuilder.Project))
7574
{
76-
return result.DatabaseId;
75+
return $"projects/{_connectionStringBuilder.Project}/instances/{_connectionStringBuilder.Instance}/databases/{_connectionStringBuilder.Database}";
7776
}
78-
throw new ArgumentException($"Invalid database name in connection string: {ConnectionString}");
77+
return "";
7978
}
8079
}
8180

@@ -92,8 +91,9 @@ private ConnectionState InternalState
9291

9392
public override ConnectionState State => InternalState;
9493
protected override DbProviderFactory DbProviderFactory => SpannerFactory.Instance;
94+
95+
public override string DataSource => _connectionStringBuilder?.DataSource ?? string.Empty;
9596

96-
public override string DataSource { get; } = "";
9797
public override string ServerVersion
9898
{
9999
get
@@ -188,15 +188,23 @@ protected override void Dispose(bool disposing)
188188
public override void Open()
189189
{
190190
AssertClosed();
191-
if (ConnectionString == string.Empty)
191+
if (ConnectionString == string.Empty || _connectionStringBuilder == null)
192192
{
193193
throw new InvalidOperationException("Connection string is empty");
194194
}
195-
196-
InternalState = ConnectionState.Connecting;
197-
Pool = SpannerPool.GetOrCreate(ConnectionString);
198-
_libConnection = Pool.CreateConnection();
199-
InternalState = ConnectionState.Open;
195+
196+
try
197+
{
198+
InternalState = ConnectionState.Connecting;
199+
Pool = SpannerPool.GetOrCreate(_connectionStringBuilder.SpannerLibConnectionString);
200+
_libConnection = Pool.CreateConnection();
201+
InternalState = ConnectionState.Open;
202+
}
203+
catch (Exception)
204+
{
205+
InternalState = ConnectionState.Closed;
206+
throw;
207+
}
200208
}
201209

202210
private void EnsureOpen()
@@ -223,12 +231,6 @@ private void AssertClosed()
223231
}
224232
}
225233

226-
private bool IsValidConnectionString(string? connectionString)
227-
{
228-
// TODO: Move to Spanner lib.
229-
return string.IsNullOrEmpty(connectionString) || connectionString.Contains("projects/");
230-
}
231-
232234
public CommitResponse? WriteMutations(BatchWriteRequest.Types.MutationGroup mutations)
233235
{
234236
EnsureOpen();

0 commit comments

Comments
 (0)