diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 00000000..d5ccdd00
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,12 @@
+# Datasync Community Toolkit
+
+This is a set of instructions for the GitHub Copilot to support the Datasync Community Toolkit.
+
+## Project Layout
+
+* `/docs` - documentation, mostly Markdown supporting `mkdocs` document generation.
+* `/infra` - infrastructure definition to support live testing.
+* `/samples` - sample code for the library.
+* `/src` - source code for the NuGet packages.
+* `/templates` - the template source code.
+* `/tests` - the test projects
diff --git a/Directory.Packages.props b/Directory.Packages.props
index b695c1d4..5f844ff2 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -40,6 +40,7 @@
+
diff --git a/infra/modules/mysql.bicep b/infra/modules/mysql.bicep
deleted file mode 100644
index 8d0397eb..00000000
--- a/infra/modules/mysql.bicep
+++ /dev/null
@@ -1,89 +0,0 @@
-targetScope = 'resourceGroup'
-
-@description('The list of firewall rules to install')
-param firewallRules FirewallRule[] = [
- { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
-]
-
-@minLength(1)
-@description('The name of the test database to create')
-param databaseName string = 'unittests'
-
-@minLength(1)
-@description('Primary location for all resources')
-param location string = resourceGroup().location
-
-@description('The name of the SQL Server to create.')
-param sqlServerName string
-
-@description('Optional - the SQL Server administrator password. If not provided, the username will be \'appadmin\'.')
-param sqlAdminUsername string = 'appadmin'
-
-@secure()
-@description('Optional - SQL Server administrator password. If not provided, a random password will be generated.')
-param sqlAdminPassword string = newGuid()
-
-@description('The list of tags to apply to all resources.')
-param tags object = {}
-
-/*********************************************************************************/
-
-resource mysql_server 'Microsoft.DBforMySQL/flexibleServers@2024-10-01-preview' = {
- name: sqlServerName
- location: location
- tags: tags
- sku: {
- name: 'Standard_B1ms'
- tier: 'Burstable'
- }
- properties: {
- administratorLogin: sqlAdminUsername
- administratorLoginPassword: sqlAdminPassword
- createMode: 'Default'
- authConfig: {
- activeDirectoryAuth: 'Disabled'
- passwordAuth: 'Enabled'
- }
- backup: {
- backupRetentionDays: 7
- geoRedundantBackup: 'Disabled'
- }
- highAvailability: {
- mode: 'Disabled'
- }
- storage: {
- storageSizeGB: 32
- autoGrow: 'Disabled'
- }
- version: '8.0.21'
- }
-
- resource fw 'firewallRules@2023-12-30' = [ for (fwRule, idx) in firewallRules : {
- name: 'fw${idx}'
- properties: {
- startIpAddress: fwRule.startIpAddress
- endIpAddress: fwRule.endIpAddress
- }
- }]
-}
-
-resource mysql_database 'Microsoft.DBforMySQL/flexibleServers/databases@2023-12-30' = {
- name: databaseName
- parent: mysql_server
- properties: {
- charset: 'ascii'
- collation: 'ascii_general_ci'
- }
-}
-
-/*********************************************************************************/
-
-#disable-next-line outputs-should-not-contain-secrets
-output MYSQL_CONNECTIONSTRING string = 'server=${mysql_server.properties.fullyQualifiedDomainName};database=${mysql_database.name};user=${mysql_server.properties.administratorLogin};password=${sqlAdminPassword}'
-
-/*********************************************************************************/
-
-type FirewallRule = {
- startIpAddress: string
- endIpAddress: string
-}
diff --git a/infra/resources.bicep b/infra/resources.bicep
index 3be157e9..42b2501c 100644
--- a/infra/resources.bicep
+++ b/infra/resources.bicep
@@ -34,7 +34,6 @@ var appServiceName = 'web-${resourceToken}'
var azsqlServerName = 'sql-${resourceToken}'
var cosmosServerName = 'cosmos-${resourceToken}'
var pgsqlServerName = 'pgsql-${resourceToken}'
-var mysqlServerName = 'mysql-${resourceToken}'
var mongoServerName = 'mongo-${resourceToken}'
var mongoaciServerName = 'mongoaci-${resourceToken}'
@@ -81,19 +80,6 @@ module pgsql './modules/postgresql.bicep' = {
}
}
-module mysql './modules/mysql.bicep' = {
- name: 'mysql-deployment-${resourceToken}'
- params: {
- location: location
- tags: tags
- databaseName: testDatabaseName
- firewallRules: clientIpFirewallRules
- sqlServerName: mysqlServerName
- sqlAdminUsername: sqlAdminUsername
- sqlAdminPassword: sqlAdminPassword
- }
-}
-
module cosmos './modules/cosmos.bicep' = {
name: 'cosmos-deployment-${resourceToken}'
params: {
@@ -150,6 +136,5 @@ output AZSQL_CONNECTIONSTRING string = azuresql.outputs.AZSQL_CONNECTIONSTRING
output COSMOS_CONNECTIONSTRING string = cosmos.outputs.COSMOS_CONNECTIONSTRING
output MONGO_CONNECTIONSTRING string = mongodb.outputs.MONGO_CONNECTIONSTRING
output MONGOACI_CONNECTIONSTRING string = mongoaci.outputs.MONGO_CONNECTIONSTRING
-output MYSQL_CONNECTIONSTRING string = mysql.outputs.MYSQL_CONNECTIONSTRING
output PGSQL_CONNECTIONSTRING string = pgsql.outputs.PGSQL_CONNECTIONSTRING
output SERVICE_ENDPOINT string = app_service.outputs.SERVICE_ENDPOINT
diff --git a/tests/CommunityToolkit.Datasync.Server.EntityFrameworkCore.Test/MySqlEntityTableRepository_Tests.cs b/tests/CommunityToolkit.Datasync.Server.EntityFrameworkCore.Test/MySqlEntityTableRepository_Tests.cs
index 3e233001..e9df8a0d 100644
--- a/tests/CommunityToolkit.Datasync.Server.EntityFrameworkCore.Test/MySqlEntityTableRepository_Tests.cs
+++ b/tests/CommunityToolkit.Datasync.Server.EntityFrameworkCore.Test/MySqlEntityTableRepository_Tests.cs
@@ -4,6 +4,7 @@
using CommunityToolkit.Datasync.TestCommon;
using CommunityToolkit.Datasync.TestCommon.Databases;
+using CommunityToolkit.Datasync.TestCommon.Fixtures;
using Microsoft.EntityFrameworkCore;
using Xunit.Abstractions;
@@ -13,7 +14,7 @@ namespace CommunityToolkit.Datasync.Server.EntityFrameworkCore.Test;
[ExcludeFromCodeCoverage]
[Collection("LiveTestsCollection")]
-public class MysqlEntityTableRepository_Tests(DatabaseFixture fixture, ITestOutputHelper output) : RepositoryTests, IAsyncLifetime
+public class MysqlEntityTableRepository_Tests(MySqlDatabaseFixture fixture, ITestOutputHelper output) : RepositoryTests, IClassFixture, IAsyncLifetime
{
#region Setup
private readonly Random random = new();
@@ -21,11 +22,8 @@ public class MysqlEntityTableRepository_Tests(DatabaseFixture fixture, ITestOutp
public async Task InitializeAsync()
{
- if (!string.IsNullOrEmpty(ConnectionStrings.MySql))
- {
- Context = await MysqlDbContext.CreateContextAsync(ConnectionStrings.MySql, output);
- this.movies = await Context.Movies.AsNoTracking().ToListAsync();
- }
+ Context = await MysqlDbContext.CreateContextAsync(fixture.ConnectionString, output);
+ this.movies = await Context.Movies.AsNoTracking().ToListAsync();
}
public async Task DisposeAsync()
@@ -38,7 +36,7 @@ public async Task DisposeAsync()
private MysqlDbContext Context { get; set; }
- protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(ConnectionStrings.MySql);
+ protected override bool CanRunLiveTests() => true;
protected override async Task GetEntityAsync(string id)
=> await Context.Movies.AsNoTracking().SingleOrDefaultAsync(m => m.Id == id);
diff --git a/tests/CommunityToolkit.Datasync.Server.Test/Live/MySQL_Controller_Tests.cs b/tests/CommunityToolkit.Datasync.Server.Test/Live/MySQL_Controller_Tests.cs
index d0f03e24..4d6dcc0e 100644
--- a/tests/CommunityToolkit.Datasync.Server.Test/Live/MySQL_Controller_Tests.cs
+++ b/tests/CommunityToolkit.Datasync.Server.Test/Live/MySQL_Controller_Tests.cs
@@ -5,6 +5,7 @@
using CommunityToolkit.Datasync.Server.EntityFrameworkCore;
using CommunityToolkit.Datasync.Server.Test.Helpers;
using CommunityToolkit.Datasync.TestCommon.Databases;
+using CommunityToolkit.Datasync.TestCommon.Fixtures;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Xunit.Abstractions;
@@ -13,7 +14,7 @@ namespace CommunityToolkit.Datasync.Server.Test.Live;
[ExcludeFromCodeCoverage]
[Collection("LiveTestsCollection")]
-public class MySQL_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : LiveControllerTests, IAsyncLifetime
+public class MySQL_Controller_Tests(MySqlDatabaseFixture fixture, ITestOutputHelper output) : LiveControllerTests, IClassFixture, IAsyncLifetime
{
#region Setup
private readonly Random random = new();
@@ -21,16 +22,8 @@ public class MySQL_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper o
public async Task InitializeAsync()
{
- if (!string.IsNullOrEmpty(ConnectionStrings.MySql))
- {
- // Note: we don't clear entities on every run to speed up the test runs. This can only be done because
- // the tests are read-only (associated with the query and get capabilities). If the test being run writes
- // to the database then change clearEntities to true.
- output.WriteLine($"MysqlIsInitialized = {fixture.MysqlIsInitialized}");
- Context = await MysqlDbContext.CreateContextAsync(ConnectionStrings.MySql, output, clearEntities: !fixture.MysqlIsInitialized);
- this.movies = await Context.Movies.AsNoTracking().ToListAsync();
- fixture.MysqlIsInitialized = true;
- }
+ Context = await MysqlDbContext.CreateContextAsync(fixture.ConnectionString, output);
+ this.movies = await Context.Movies.AsNoTracking().ToListAsync();
}
public async Task DisposeAsync()
@@ -45,7 +38,7 @@ public async Task DisposeAsync()
protected override string DriverName { get; } = "MySQL";
- protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(ConnectionStrings.MySql);
+ protected override bool CanRunLiveTests() => true;
protected override async Task GetEntityAsync(string id)
=> await Context.Movies.AsNoTracking().SingleOrDefaultAsync(m => m.Id == id);
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj b/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj
index 6c0154d8..7695fb3b 100644
--- a/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj
+++ b/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj
@@ -1,4 +1,4 @@
-
+
false
@@ -20,6 +20,7 @@
+
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/Databases/ConnectionStrings.cs b/tests/CommunityToolkit.Datasync.TestCommon/Databases/ConnectionStrings.cs
index 47113a2f..dfca137f 100644
--- a/tests/CommunityToolkit.Datasync.TestCommon/Databases/ConnectionStrings.cs
+++ b/tests/CommunityToolkit.Datasync.TestCommon/Databases/ConnectionStrings.cs
@@ -11,9 +11,7 @@ public static class ConnectionStrings
public static readonly string CosmosDb = Environment.GetEnvironmentVariable("COSMOS_CONNECTION_STRING");
public static readonly string MongoCommunity = Environment.GetEnvironmentVariable("MONGOACI_CONNECTION_STRING");
public static readonly string CosmosMongo = Environment.GetEnvironmentVariable("MONGO_CONNECTION_STRING");
- public static readonly string MySql = Environment.GetEnvironmentVariable("MYSQL_CONNECTION_STRING");
public static readonly string PgSql = Environment.GetEnvironmentVariable("PGSQL_CONNECTION_STRING");
-
public static readonly string Service = Environment.GetEnvironmentVariable("SERVICE_ENDPOINT");
public static readonly bool EnableLogging = (Environment.GetEnvironmentVariable("ENABLE_SQL_LOGGING") ?? "false") == "true";
}
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/Fixtures/MySqlDatabaseFixture.cs b/tests/CommunityToolkit.Datasync.TestCommon/Fixtures/MySqlDatabaseFixture.cs
new file mode 100644
index 00000000..faaaf8c3
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.TestCommon/Fixtures/MySqlDatabaseFixture.cs
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Testcontainers.MySql;
+using Xunit;
+
+namespace CommunityToolkit.Datasync.TestCommon.Fixtures;
+
+///
+/// A test fixture for impementing a MySQL database using Testcontainers.
+///
+[ExcludeFromCodeCoverage]
+public class MySqlDatabaseFixture : IAsyncLifetime
+{
+ private readonly MySqlContainer _container;
+
+ public MySqlDatabaseFixture()
+ {
+ this._container = new MySqlBuilder()
+ .WithImage("mysql:lts-oracle")
+ .WithCleanUp(true)
+ .WithUsername("testuser")
+ .WithPassword("testpassword")
+ .WithDatabase("testdb")
+ .Build();
+ }
+
+ ///
+ public async Task DisposeAsync()
+ {
+ if (this._container is not null)
+ {
+ await this._container.DisposeAsync();
+ }
+ }
+
+ ///
+ public async Task InitializeAsync()
+ {
+ await this._container.StartAsync();
+ ConnectionString = this._container.GetConnectionString();
+ }
+
+ ///
+ /// The connection string for the MySQL database.
+ ///
+ public string ConnectionString { get; private set; } = string.Empty;
+}
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/Fixtures/MySqlTestFixture.cs b/tests/CommunityToolkit.Datasync.TestCommon/Fixtures/MySqlTestFixture.cs
new file mode 100644
index 00000000..e69de29b