Skip to content

Commit 5e53c9f

Browse files
author
Adrian Hall
committed
(#215) MySQL Test Suite
1 parent 2285c74 commit 5e53c9f

File tree

8 files changed

+263
-0
lines changed

8 files changed

+263
-0
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<PackageVersion Include="NSubstitute" Version="5.3.0" />
2828
<PackageVersion Include="NSwag.AspNetCore" Version="14.2.0" />
2929
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.3" />
30+
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
3031
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.2.0" />
3132
<PackageVersion Include="System.Formats.Asn1" Version="9.0.1" />
3233
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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.TestCommon;
6+
using CommunityToolkit.Datasync.TestCommon.Databases;
7+
using Microsoft.EntityFrameworkCore;
8+
using Xunit.Abstractions;
9+
10+
namespace CommunityToolkit.Datasync.Server.EntityFrameworkCore.Test;
11+
12+
[ExcludeFromCodeCoverage]
13+
[Collection("LiveTestsCollection")]
14+
public class MysqlEntityTableRepository_Tests : RepositoryTests<MysqlEntityMovie>
15+
{
16+
#region Setup
17+
private readonly DatabaseFixture _fixture;
18+
private readonly Random random = new();
19+
private readonly string connectionString;
20+
private readonly List<MysqlEntityMovie> movies;
21+
private readonly Lazy<MysqlDbContext> _context;
22+
23+
public MysqlEntityTableRepository_Tests(DatabaseFixture fixture, ITestOutputHelper output) : base()
24+
{
25+
this._fixture = fixture;
26+
this.connectionString = Environment.GetEnvironmentVariable("DATASYNC_MYSQL_CONNECTIONSTRING");
27+
if (!string.IsNullOrEmpty(this.connectionString))
28+
{
29+
this._context = new Lazy<MysqlDbContext>(() => MysqlDbContext.CreateContext(this.connectionString, output));
30+
this.movies = Context.Movies.AsNoTracking().ToList();
31+
}
32+
}
33+
34+
private MysqlDbContext Context { get => this._context.Value; }
35+
36+
protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(this.connectionString);
37+
38+
protected override Task<MysqlEntityMovie> GetEntityAsync(string id)
39+
=> Task.FromResult(Context.Movies.AsNoTracking().SingleOrDefault(m => m.Id == id));
40+
41+
protected override Task<int> GetEntityCountAsync()
42+
=> Task.FromResult(Context.Movies.Count());
43+
44+
protected override Task<IRepository<MysqlEntityMovie>> GetPopulatedRepositoryAsync()
45+
=> Task.FromResult<IRepository<MysqlEntityMovie>>(new EntityTableRepository<MysqlEntityMovie>(Context));
46+
47+
protected override Task<string> GetRandomEntityIdAsync(bool exists)
48+
=> Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString());
49+
#endregion
50+
51+
[SkippableFact]
52+
public void EntityTableRepository_BadDbSet_Throws()
53+
{
54+
Skip.IfNot(CanRunLiveTests());
55+
Action act = () => _ = new EntityTableRepository<EntityTableData>(Context);
56+
act.Should().Throw<ArgumentException>();
57+
}
58+
59+
[SkippableFact]
60+
public void EntityTableRepository_GoodDbSet_Works()
61+
{
62+
Skip.IfNot(CanRunLiveTests());
63+
Action act = () => _ = new EntityTableRepository<MysqlEntityMovie>(Context);
64+
act.Should().NotThrow();
65+
}
66+
67+
[SkippableFact]
68+
public async Task WrapExceptionAsync_ThrowsConflictException_WhenDbConcurrencyUpdateExceptionThrown()
69+
{
70+
Skip.IfNot(CanRunLiveTests());
71+
EntityTableRepository<MysqlEntityMovie> repository = await GetPopulatedRepositoryAsync() as EntityTableRepository<MysqlEntityMovie>;
72+
string id = await GetRandomEntityIdAsync(true);
73+
MysqlEntityMovie expectedPayload = await GetEntityAsync(id);
74+
75+
static Task innerAction() => throw new DbUpdateConcurrencyException("Concurrency exception");
76+
77+
Func<Task> act = async () => await repository.WrapExceptionAsync(id, innerAction);
78+
(await act.Should().ThrowAsync<HttpException>()).WithStatusCode(409).And.WithPayload(expectedPayload);
79+
}
80+
81+
[SkippableFact]
82+
public async Task WrapExceptionAsync_ThrowsRepositoryException_WhenDbUpdateExceptionThrown()
83+
{
84+
Skip.IfNot(CanRunLiveTests());
85+
EntityTableRepository<MysqlEntityMovie> repository = await GetPopulatedRepositoryAsync() as EntityTableRepository<MysqlEntityMovie>;
86+
string id = await GetRandomEntityIdAsync(true);
87+
MysqlEntityMovie expectedPayload = await GetEntityAsync(id);
88+
89+
static Task innerAction() => throw new DbUpdateException("Non-concurrency exception");
90+
91+
Func<Task> act = async () => await repository.WrapExceptionAsync(id, innerAction);
92+
await act.Should().ThrowAsync<RepositoryException>();
93+
}
94+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class DatabaseFixture
1111
{
1212
public bool AzureSqlIsInitialized { get; set; } = false;
1313
public bool CosmosIsInitialized { get; set; } = false;
14+
public bool MysqlIsInitialized { get; set; } = false;
1415
public bool PgIsInitialized { get; set; } = false;
1516
}
1617

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 MySQL_Controller_Tests : LiveControllerTests<MysqlEntityMovie>
16+
{
17+
#region Setup
18+
private readonly DatabaseFixture _fixture;
19+
private readonly Random random = new();
20+
private readonly string connectionString;
21+
private readonly List<MysqlEntityMovie> movies;
22+
23+
public MySQL_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : base()
24+
{
25+
this._fixture = fixture;
26+
this.connectionString = Environment.GetEnvironmentVariable("DATASYNC_MYSQL_CONNECTIONSTRING");
27+
if (!string.IsNullOrEmpty(this.connectionString))
28+
{
29+
output.WriteLine($"MysqlIsInitialized = {this._fixture.MysqlIsInitialized}");
30+
Context = MysqlDbContext.CreateContext(this.connectionString, output, clearEntities: !this._fixture.MysqlIsInitialized);
31+
this.movies = Context.Movies.AsNoTracking().ToList();
32+
this._fixture.MysqlIsInitialized = true;
33+
}
34+
}
35+
36+
private MysqlDbContext Context { get; set; }
37+
38+
protected override string DriverName { get; } = "PgSQL";
39+
40+
protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(this.connectionString);
41+
42+
protected override Task<MysqlEntityMovie> GetEntityAsync(string id)
43+
=> Task.FromResult(Context.Movies.AsNoTracking().SingleOrDefault(m => m.Id == id));
44+
45+
protected override Task<int> GetEntityCountAsync()
46+
=> Task.FromResult(Context.Movies.Count());
47+
48+
protected override Task<IRepository<MysqlEntityMovie>> GetPopulatedRepositoryAsync()
49+
=> Task.FromResult<IRepository<MysqlEntityMovie>>(new EntityTableRepository<MysqlEntityMovie>(Context));
50+
51+
protected override Task<string> GetRandomEntityIdAsync(bool exists)
52+
=> Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString());
53+
#endregion
54+
}

tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
2020
<PackageReference Include="Microsoft.Spatial" />
2121
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
22+
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
2223
</ItemGroup>
2324

2425
<ItemGroup>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 Microsoft.EntityFrameworkCore;
6+
using Xunit.Abstractions;
7+
8+
namespace CommunityToolkit.Datasync.TestCommon.Databases;
9+
10+
[ExcludeFromCodeCoverage]
11+
public class MysqlDbContext(DbContextOptions<MysqlDbContext> options) : BaseDbContext<MysqlDbContext, MysqlEntityMovie>(options)
12+
{
13+
public static MysqlDbContext CreateContext(string connectionString, ITestOutputHelper output = null, bool clearEntities = true)
14+
{
15+
if (string.IsNullOrEmpty(connectionString))
16+
{
17+
throw new ArgumentNullException(nameof(connectionString));
18+
}
19+
20+
DbContextOptionsBuilder<MysqlDbContext> optionsBuilder = new DbContextOptionsBuilder<MysqlDbContext>()
21+
.UseMySql(connectionString: connectionString, serverVersion: ServerVersion.AutoDetect(connectionString))
22+
.EnableLogging(output);
23+
MysqlDbContext context = new(optionsBuilder.Options);
24+
25+
context.InitializeDatabase(clearEntities);
26+
context.PopulateDatabase();
27+
return context;
28+
}
29+
30+
internal void InitializeDatabase(bool clearEntities)
31+
{
32+
Database.EnsureCreated();
33+
34+
if (clearEntities)
35+
{
36+
ExecuteRawSqlOnEachEntity(@"DELETE FROM ""{0}""");
37+
}
38+
}
39+
40+
protected override void OnModelCreating(ModelBuilder modelBuilder)
41+
{
42+
modelBuilder.Entity<MysqlEntityMovie>().Property(m => m.UpdatedAt)
43+
.ValueGeneratedOnAddOrUpdate();
44+
45+
base.OnModelCreating(modelBuilder);
46+
}
47+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.TestCommon.Models;
7+
using System.ComponentModel.DataAnnotations;
8+
9+
namespace CommunityToolkit.Datasync.TestCommon.Databases;
10+
11+
[ExcludeFromCodeCoverage]
12+
public class MysqlEntityMovie : EntityTableData, IMovie, IEquatable<IMovie>
13+
{
14+
/// <summary>
15+
/// True if the movie won the oscar for Best Picture
16+
/// </summary>
17+
public bool BestPictureWinner { get; set; }
18+
19+
/// <summary>
20+
/// The running time of the movie
21+
/// </summary>
22+
[Required]
23+
[Range(60, 360)]
24+
public int Duration { get; set; }
25+
26+
/// <summary>
27+
/// The MPAA rating for the movie, if available.
28+
/// </summary>
29+
public MovieRating Rating { get; set; } = MovieRating.Unrated;
30+
31+
/// <summary>
32+
/// The release date of the movie.
33+
/// </summary>
34+
[Required]
35+
public DateOnly ReleaseDate { get; set; }
36+
37+
/// <summary>
38+
/// The title of the movie.
39+
/// </summary>
40+
[Required]
41+
[StringLength(128, MinimumLength = 2)]
42+
public string Title { get; set; } = string.Empty;
43+
44+
/// <summary>
45+
/// The year that the movie was released.
46+
/// </summary>
47+
[Required]
48+
[Range(1920, 2030)]
49+
public int Year { get; set; }
50+
51+
/// <summary>
52+
/// Determines if this movie has the same content as another movie.
53+
/// </summary>
54+
/// <param name="other">The other movie</param>
55+
/// <returns>true if the content is the same</returns>
56+
public bool Equals(IMovie other)
57+
=> other != null
58+
&& other.BestPictureWinner == BestPictureWinner
59+
&& other.Duration == Duration
60+
&& other.Rating == Rating
61+
&& other.ReleaseDate == ReleaseDate
62+
&& other.Title == Title
63+
&& other.Year == Year;
64+
}

tests/CommunityToolkit.Datasync.TestService/CommunityToolkit.Datasync.TestService.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
1919
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
2020
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
21+
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
2122
</ItemGroup>
2223
</Project>

0 commit comments

Comments
 (0)