Skip to content

Commit ba6651c

Browse files
author
Adrian Hall
committed
(#204) WIP: Cosmos, with single time db setup for test performance.
1 parent 704529d commit ba6651c

File tree

9 files changed

+112
-21
lines changed

9 files changed

+112
-21
lines changed

infra/modules/cosmos.bicep

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ resource cosmos_container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/co
7373
compositeIndexes: [
7474
[
7575
{ path: '/UpdatedAt', order: 'ascending' }
76-
{ path: '/Id', order: 'ascending' }
76+
{ path: '/id', order: 'ascending' }
77+
]
78+
[
79+
{ path: '/Title', order: 'ascending' }
80+
{ path: '/id', order: 'ascending' }
81+
]
82+
[
83+
{ path: '/Title', order: 'descending' }
84+
{ path: '/id', order: 'ascending' }
7785
]
7886
]
7987
}

tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveTestsCollection.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ namespace CommunityToolkit.Datasync.Server.Test.Helpers;
99
/// </summary>
1010
public class DatabaseFixture
1111
{
12-
12+
public bool AzureSqlIsInitialized { get; set; } = false;
13+
public bool CosmosIsInitialized { get; set; } = false;
14+
public bool PgIsInitialized { get; set; } = false;
1315
}
1416

1517
[CollectionDefinition("LiveTestsCollection", DisableParallelization = true)]

tests/CommunityToolkit.Datasync.Server.Test/Live/AzureSQL_Controller_Tests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ public class AzureSQL_Controller_Tests : LiveControllerTests<AzureSqlEntityMovie
1919
private readonly Random random = new();
2020
private readonly string connectionString;
2121
private readonly List<AzureSqlEntityMovie> movies;
22-
private readonly Lazy<AzureSqlDbContext> _context;
2322

2423
public AzureSQL_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : base()
2524
{
2625
this._fixture = fixture;
2726
this.connectionString = Environment.GetEnvironmentVariable("DATASYNC_AZSQL_CONNECTIONSTRING");
2827
if (!string.IsNullOrEmpty(this.connectionString))
2928
{
30-
this._context = new Lazy<AzureSqlDbContext>(() => AzureSqlDbContext.CreateContext(this.connectionString, output));
29+
Context = AzureSqlDbContext.CreateContext(this.connectionString, output, clearEntities: this._fixture.AzureSqlIsInitialized);
3130
this.movies = [.. Context.Movies.AsNoTracking()];
31+
this._fixture.AzureSqlIsInitialized = true;
3232
}
3333
}
3434

35-
private AzureSqlDbContext Context { get => this._context.Value; }
35+
private AzureSqlDbContext Context { get; set; }
3636

3737
protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(this.connectionString);
3838

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 CommunityToolkit.Datasync.Server.EntityFrameworkCore;
6+
using CommunityToolkit.Datasync.Server.Test.Helpers;
7+
using CommunityToolkit.Datasync.TestCommon.Databases;
8+
using Microsoft.EntityFrameworkCore;
9+
using Xunit.Abstractions;
10+
11+
namespace CommunityToolkit.Datasync.Server.Test.Live;
12+
13+
[ExcludeFromCodeCoverage]
14+
[Collection("LiveTestsCollection")]
15+
public class Cosmos_Controller_Tests : LiveControllerTests<CosmosEntityMovie>
16+
{
17+
#region Setup
18+
private readonly DatabaseFixture _fixture;
19+
private readonly Random random = new();
20+
private readonly string connectionString;
21+
private readonly List<CosmosEntityMovie> movies;
22+
23+
public Cosmos_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : base()
24+
{
25+
this._fixture = fixture;
26+
this.connectionString = Environment.GetEnvironmentVariable("DATASYNC_COSMOS_CONNECTIONSTRING");
27+
if (!string.IsNullOrEmpty(this.connectionString))
28+
{
29+
// Note: we don't clear entities on every run to speed up the test runs. This can only be done because
30+
// the tests are read-only (associated with the query and get capabilities). If the test being run writes
31+
// to the database then change clearEntities to true.
32+
Context = CosmosDbContext.CreateContext(this.connectionString, output, clearEntities: this._fixture.CosmosIsInitialized);
33+
this.movies = [.. Context.Movies.AsNoTracking()];
34+
this._fixture.CosmosIsInitialized = true;
35+
}
36+
}
37+
38+
private CosmosDbContext Context { get; set; }
39+
40+
protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(this.connectionString);
41+
42+
/// <summary>
43+
/// There are a set of tests that are not supported by CosmosDB, so we disable them
44+
/// </summary>
45+
protected override bool CanRunMathQueryTests() => false;
46+
47+
protected override Task<CosmosEntityMovie> GetEntityAsync(string id)
48+
=> Task.FromResult(Context.Movies.AsNoTracking().SingleOrDefault(m => m.Id == id));
49+
50+
protected override Task<int> GetEntityCountAsync()
51+
=> Task.FromResult(Context.Movies.Count());
52+
53+
protected override Task<IRepository<CosmosEntityMovie>> GetPopulatedRepositoryAsync()
54+
=> Task.FromResult<IRepository<CosmosEntityMovie>>(new EntityTableRepository<CosmosEntityMovie>(Context));
55+
56+
protected override Task<string> GetRandomEntityIdAsync(bool exists)
57+
=> Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString());
58+
#endregion
59+
}

tests/CommunityToolkit.Datasync.Server.Test/Live/PgSQL_Controller_Tests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ public class PgSQL_Controller_Tests : LiveControllerTests<PgEntityMovie>
1919
private readonly Random random = new();
2020
private readonly string connectionString;
2121
private readonly List<PgEntityMovie> movies;
22-
private readonly Lazy<PgDbContext> _context;
2322

2423
public PgSQL_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : base()
2524
{
2625
this._fixture = fixture;
2726
this.connectionString = Environment.GetEnvironmentVariable("DATASYNC_PGSQL_CONNECTIONSTRING");
2827
if (!string.IsNullOrEmpty(this.connectionString))
2928
{
30-
this._context = new Lazy<PgDbContext>(() => PgDbContext.CreateContext(this.connectionString, output));
29+
Context = PgDbContext.CreateContext(this.connectionString, output, clearEntities: this._fixture.PgIsInitialized);
3130
this.movies = Context.Movies.AsNoTracking().ToList();
31+
this._fixture.PgIsInitialized = true;
3232
}
3333
}
3434

35-
private PgDbContext Context { get => this._context.Value; }
35+
private PgDbContext Context { get; set; }
3636

3737
protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(this.connectionString);
3838

tests/CommunityToolkit.Datasync.TestCommon/Databases/AzureSql/AzureSqlDbContext.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace CommunityToolkit.Datasync.TestCommon.Databases;
1111
[ExcludeFromCodeCoverage]
1212
public class AzureSqlDbContext(DbContextOptions<AzureSqlDbContext> options) : BaseDbContext<AzureSqlDbContext, AzureSqlEntityMovie>(options)
1313
{
14-
public static AzureSqlDbContext CreateContext(string connectionString, ITestOutputHelper output = null)
14+
public static AzureSqlDbContext CreateContext(string connectionString, ITestOutputHelper output = null, bool clearEntities = true)
1515
{
1616
if (string.IsNullOrEmpty(connectionString))
1717
{
@@ -23,12 +23,12 @@ public static AzureSqlDbContext CreateContext(string connectionString, ITestOutp
2323
.EnableLogging(output);
2424
AzureSqlDbContext context = new(optionsBuilder.Options);
2525

26-
context.InitializeDatabase();
26+
context.InitializeDatabase(clearEntities);
2727
context.PopulateDatabase();
2828
return context;
2929
}
3030

31-
internal void InitializeDatabase()
31+
internal void InitializeDatabase(bool clearEntities)
3232
{
3333
const string datasyncTrigger = @"
3434
CREATE OR ALTER TRIGGER [dbo].[{0}_datasync] ON [dbo].[{0}] AFTER INSERT, UPDATE AS
@@ -44,8 +44,12 @@ internal void InitializeDatabase()
4444
";
4545

4646
Database.EnsureCreated();
47-
ExecuteRawSqlOnEachEntity("DELETE FROM [dbo].[{0}]");
4847
ExecuteRawSqlOnEachEntity(datasyncTrigger);
48+
49+
if (clearEntities)
50+
{
51+
ExecuteRawSqlOnEachEntity("DELETE FROM [dbo].[{0}]");
52+
}
4953
}
5054

5155
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)

tests/CommunityToolkit.Datasync.TestCommon/Databases/Base/BaseDbContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,15 @@ protected void ExecuteRawSqlOnEachEntity(string format)
4848
/// <summary>
4949
/// Populates the database with the core set of movies. Ensures that we have the same data for all tests.
5050
/// </summary>
51+
[SuppressMessage("Performance", "CA1827:Do not use Count() or LongCount() when Any() can be used", Justification = "CosmosDB does not support .Any()")]
5152
protected void PopulateDatabase()
5253
{
54+
bool hasEntities = Movies.Count() > 0;
55+
if (hasEntities)
56+
{
57+
return;
58+
}
59+
5360
List<TEntity> movies = [.. TestData.Movies.OfType<TEntity>()];
5461
MovieIds = movies.ConvertAll(m => m.Id);
5562

tests/CommunityToolkit.Datasync.TestCommon/Databases/CosmosDb/CosmosDbContext.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace CommunityToolkit.Datasync.TestCommon.Databases;
1212
[ExcludeFromCodeCoverage]
1313
public class CosmosDbContext(DbContextOptions<CosmosDbContext> options) : BaseDbContext<CosmosDbContext, CosmosEntityMovie>(options)
1414
{
15-
public static CosmosDbContext CreateContext(string connectionString, ITestOutputHelper output = null)
15+
public static CosmosDbContext CreateContext(string connectionString, ITestOutputHelper output = null, bool clearEntities = true)
1616
{
1717
if (string.IsNullOrEmpty(connectionString))
1818
{
@@ -24,15 +24,18 @@ public static CosmosDbContext CreateContext(string connectionString, ITestOutput
2424
.EnableLogging(output);
2525
CosmosDbContext context = new(optionsBuilder.Options);
2626

27-
context.InitializeDatabase();
27+
context.InitializeDatabase(clearEntities);
2828
context.PopulateDatabase();
2929
return context;
3030
}
3131

32-
internal void InitializeDatabase()
32+
internal void InitializeDatabase(bool clearEntities)
3333
{
34-
RemoveRange(Movies.ToList());
35-
SaveChanges();
34+
if (clearEntities)
35+
{
36+
RemoveRange(Movies.ToList());
37+
SaveChanges();
38+
}
3639
}
3740

3841
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
@@ -48,6 +51,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
4851
builder.HasNoDiscriminator();
4952
builder.HasPartitionKey(model => model.Id);
5053
builder.Property(model => model.EntityTag).IsETagConcurrency();
54+
55+
// Note that the composite indices needed for Cosmos are defined in the bicep
56+
// See infra/modules/cosmos.bicep
57+
5158
});
5259
base.OnModelCreating(modelBuilder);
5360
}

tests/CommunityToolkit.Datasync.TestCommon/Databases/Postgresql/PgDbContext.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace CommunityToolkit.Datasync.TestCommon.Databases;
1010
[ExcludeFromCodeCoverage]
1111
public class PgDbContext(DbContextOptions<PgDbContext> options) : BaseDbContext<PgDbContext, PgEntityMovie>(options)
1212
{
13-
public static PgDbContext CreateContext(string connectionString, ITestOutputHelper output = null)
13+
public static PgDbContext CreateContext(string connectionString, ITestOutputHelper output = null, bool clearEntities = true)
1414
{
1515
if (string.IsNullOrEmpty(connectionString))
1616
{
@@ -22,12 +22,12 @@ public static PgDbContext CreateContext(string connectionString, ITestOutputHelp
2222
.EnableLogging(output);
2323
PgDbContext context = new(optionsBuilder.Options);
2424

25-
context.InitializeDatabase();
25+
context.InitializeDatabase(clearEntities);
2626
context.PopulateDatabase();
2727
return context;
2828
}
2929

30-
internal void InitializeDatabase()
30+
internal void InitializeDatabase(bool clearEntities)
3131
{
3232
const string datasyncTrigger = @"
3333
CREATE OR REPLACE FUNCTION {0}_datasync() RETURNS trigger AS $$
@@ -47,8 +47,12 @@ FOR EACH ROW EXECUTE PROCEDURE
4747
";
4848

4949
Database.EnsureCreated();
50-
ExecuteRawSqlOnEachEntity(@"DELETE FROM ""{0}""");
5150
ExecuteRawSqlOnEachEntity(datasyncTrigger);
51+
52+
if (clearEntities)
53+
{
54+
ExecuteRawSqlOnEachEntity(@"DELETE FROM ""{0}""");
55+
}
5256
}
5357

5458
protected override void OnModelCreating(ModelBuilder modelBuilder)

0 commit comments

Comments
 (0)