-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* (#215) Added MySQL to list of resources. * (#215) MySQL Test Suite * (#215) Use Pomelo package instead of official MySQL package. * (#215) Enable connection retry on failure
- Loading branch information
1 parent
b155848
commit b7b68ff
Showing
13 changed files
with
375 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
...unityToolkit.Datasync.Server.EntityFrameworkCore.Test/MySqlEntityTableRepository_Tests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// 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 CommunityToolkit.Datasync.TestCommon; | ||
using CommunityToolkit.Datasync.TestCommon.Databases; | ||
using Microsoft.EntityFrameworkCore; | ||
using Xunit.Abstractions; | ||
|
||
namespace CommunityToolkit.Datasync.Server.EntityFrameworkCore.Test; | ||
|
||
[ExcludeFromCodeCoverage] | ||
[Collection("LiveTestsCollection")] | ||
public class MysqlEntityTableRepository_Tests : RepositoryTests<MysqlEntityMovie> | ||
{ | ||
#region Setup | ||
private readonly DatabaseFixture _fixture; | ||
private readonly Random random = new(); | ||
private readonly string connectionString; | ||
private readonly List<MysqlEntityMovie> movies; | ||
private readonly Lazy<MysqlDbContext> _context; | ||
|
||
public MysqlEntityTableRepository_Tests(DatabaseFixture fixture, ITestOutputHelper output) : base() | ||
{ | ||
this._fixture = fixture; | ||
this.connectionString = Environment.GetEnvironmentVariable("DATASYNC_MYSQL_CONNECTIONSTRING"); | ||
if (!string.IsNullOrEmpty(this.connectionString)) | ||
{ | ||
this._context = new Lazy<MysqlDbContext>(() => MysqlDbContext.CreateContext(this.connectionString, output)); | ||
this.movies = Context.Movies.AsNoTracking().ToList(); | ||
} | ||
} | ||
|
||
private MysqlDbContext Context { get => this._context.Value; } | ||
|
||
protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(this.connectionString); | ||
|
||
protected override Task<MysqlEntityMovie> GetEntityAsync(string id) | ||
=> Task.FromResult(Context.Movies.AsNoTracking().SingleOrDefault(m => m.Id == id)); | ||
|
||
protected override Task<int> GetEntityCountAsync() | ||
=> Task.FromResult(Context.Movies.Count()); | ||
|
||
protected override Task<IRepository<MysqlEntityMovie>> GetPopulatedRepositoryAsync() | ||
=> Task.FromResult<IRepository<MysqlEntityMovie>>(new EntityTableRepository<MysqlEntityMovie>(Context)); | ||
|
||
protected override Task<string> GetRandomEntityIdAsync(bool exists) | ||
=> Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString()); | ||
#endregion | ||
|
||
[SkippableFact] | ||
public void EntityTableRepository_BadDbSet_Throws() | ||
{ | ||
Skip.IfNot(CanRunLiveTests()); | ||
Action act = () => _ = new EntityTableRepository<EntityTableData>(Context); | ||
act.Should().Throw<ArgumentException>(); | ||
} | ||
|
||
[SkippableFact] | ||
public void EntityTableRepository_GoodDbSet_Works() | ||
{ | ||
Skip.IfNot(CanRunLiveTests()); | ||
Action act = () => _ = new EntityTableRepository<MysqlEntityMovie>(Context); | ||
act.Should().NotThrow(); | ||
} | ||
|
||
[SkippableFact] | ||
public async Task WrapExceptionAsync_ThrowsConflictException_WhenDbConcurrencyUpdateExceptionThrown() | ||
{ | ||
Skip.IfNot(CanRunLiveTests()); | ||
EntityTableRepository<MysqlEntityMovie> repository = await GetPopulatedRepositoryAsync() as EntityTableRepository<MysqlEntityMovie>; | ||
string id = await GetRandomEntityIdAsync(true); | ||
MysqlEntityMovie expectedPayload = await GetEntityAsync(id); | ||
|
||
static Task innerAction() => throw new DbUpdateConcurrencyException("Concurrency exception"); | ||
|
||
Func<Task> act = async () => await repository.WrapExceptionAsync(id, innerAction); | ||
(await act.Should().ThrowAsync<HttpException>()).WithStatusCode(409).And.WithPayload(expectedPayload); | ||
} | ||
|
||
[SkippableFact] | ||
public async Task WrapExceptionAsync_ThrowsRepositoryException_WhenDbUpdateExceptionThrown() | ||
{ | ||
Skip.IfNot(CanRunLiveTests()); | ||
EntityTableRepository<MysqlEntityMovie> repository = await GetPopulatedRepositoryAsync() as EntityTableRepository<MysqlEntityMovie>; | ||
string id = await GetRandomEntityIdAsync(true); | ||
MysqlEntityMovie expectedPayload = await GetEntityAsync(id); | ||
|
||
static Task innerAction() => throw new DbUpdateException("Non-concurrency exception"); | ||
|
||
Func<Task> act = async () => await repository.WrapExceptionAsync(id, innerAction); | ||
await act.Should().ThrowAsync<RepositoryException>(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
tests/CommunityToolkit.Datasync.Server.Test/Live/MySQL_Controller_Tests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// 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 CommunityToolkit.Datasync.Server.EntityFrameworkCore; | ||
using CommunityToolkit.Datasync.Server.Test.Helpers; | ||
using CommunityToolkit.Datasync.TestCommon.Databases; | ||
using Microsoft.EntityFrameworkCore; | ||
using Xunit.Abstractions; | ||
|
||
namespace CommunityToolkit.Datasync.Server.Test.Live; | ||
|
||
[ExcludeFromCodeCoverage] | ||
[Collection("LiveTestsCollection")] | ||
public class MySQL_Controller_Tests : LiveControllerTests<MysqlEntityMovie> | ||
{ | ||
#region Setup | ||
private readonly DatabaseFixture _fixture; | ||
private readonly Random random = new(); | ||
private readonly string connectionString; | ||
private readonly List<MysqlEntityMovie> movies; | ||
|
||
public MySQL_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : base() | ||
{ | ||
this._fixture = fixture; | ||
this.connectionString = Environment.GetEnvironmentVariable("DATASYNC_MYSQL_CONNECTIONSTRING"); | ||
if (!string.IsNullOrEmpty(this.connectionString)) | ||
{ | ||
output.WriteLine($"MysqlIsInitialized = {this._fixture.MysqlIsInitialized}"); | ||
Context = MysqlDbContext.CreateContext(this.connectionString, output, clearEntities: !this._fixture.MysqlIsInitialized); | ||
this.movies = Context.Movies.AsNoTracking().ToList(); | ||
this._fixture.MysqlIsInitialized = true; | ||
} | ||
} | ||
|
||
private MysqlDbContext Context { get; set; } | ||
|
||
protected override string DriverName { get; } = "PgSQL"; | ||
|
||
protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(this.connectionString); | ||
|
||
protected override Task<MysqlEntityMovie> GetEntityAsync(string id) | ||
=> Task.FromResult(Context.Movies.AsNoTracking().SingleOrDefault(m => m.Id == id)); | ||
|
||
protected override Task<int> GetEntityCountAsync() | ||
=> Task.FromResult(Context.Movies.Count()); | ||
|
||
protected override Task<IRepository<MysqlEntityMovie>> GetPopulatedRepositoryAsync() | ||
=> Task.FromResult<IRepository<MysqlEntityMovie>>(new EntityTableRepository<MysqlEntityMovie>(Context)); | ||
|
||
protected override Task<string> GetRandomEntityIdAsync(bool exists) | ||
=> Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString()); | ||
#endregion | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
tests/CommunityToolkit.Datasync.TestCommon/Databases/MySQL/MysqlDbContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// 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 Microsoft.EntityFrameworkCore; | ||
using Xunit.Abstractions; | ||
|
||
namespace CommunityToolkit.Datasync.TestCommon.Databases; | ||
|
||
[ExcludeFromCodeCoverage] | ||
public class MysqlDbContext(DbContextOptions<MysqlDbContext> options) : BaseDbContext<MysqlDbContext, MysqlEntityMovie>(options) | ||
{ | ||
public static MysqlDbContext CreateContext(string connectionString, ITestOutputHelper output = null, bool clearEntities = true) | ||
{ | ||
if (string.IsNullOrEmpty(connectionString)) | ||
{ | ||
throw new ArgumentNullException(nameof(connectionString)); | ||
} | ||
|
||
DbContextOptionsBuilder<MysqlDbContext> optionsBuilder = new DbContextOptionsBuilder<MysqlDbContext>() | ||
.UseMySql(connectionString: connectionString, serverVersion: ServerVersion.AutoDetect(connectionString), options => options.EnableRetryOnFailure()) | ||
.EnableLogging(output); | ||
MysqlDbContext context = new(optionsBuilder.Options); | ||
|
||
context.InitializeDatabase(clearEntities); | ||
context.PopulateDatabase(); | ||
return context; | ||
} | ||
|
||
internal void InitializeDatabase(bool clearEntities) | ||
{ | ||
Database.EnsureCreated(); | ||
|
||
if (clearEntities) | ||
{ | ||
ExecuteRawSqlOnEachEntity(@"DELETE FROM {0}"); | ||
} | ||
} | ||
|
||
protected override void OnModelCreating(ModelBuilder modelBuilder) | ||
{ | ||
modelBuilder.Entity<MysqlEntityMovie>().Property(m => m.UpdatedAt) | ||
.ValueGeneratedOnAddOrUpdate(); | ||
|
||
modelBuilder.Entity<MysqlEntityMovie>().Property(m => m.Version) | ||
.IsRowVersion(); | ||
|
||
base.OnModelCreating(modelBuilder); | ||
} | ||
} |
Oops, something went wrong.