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