Skip to content

Commit ffc2588

Browse files
authored
Merge pull request #5 from mt89vein/integration-tests
feat: add integration tests
2 parents 109f7e2 + 8d012df commit ffc2588

61 files changed

Lines changed: 1198 additions & 58 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

BookLibrary.Api/ApiModule.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
using BookLibrary.Api.Auth;
1+
using BookLibrary.Api.Auth;
22
using BookLibrary.Api.ProblemDetails;
33
using BookLibrary.Api.Swagger;
44
using FluentValidation.AspNetCore;
55
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.CookiePolicy;
7+
using Microsoft.AspNetCore.Http;
68
using Microsoft.AspNetCore.Mvc.Formatters;
79
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
810
using Microsoft.Extensions.DependencyInjection;
@@ -35,6 +37,12 @@ public static void MapEndpoints(WebApplication app)
3537
/// </summary>
3638
private static void AddMockAuthentication(this WebApplicationBuilder builder)
3739
{
40+
builder.Services.AddCookiePolicy(p =>
41+
{
42+
p.HttpOnly = HttpOnlyPolicy.Always;
43+
p.Secure = CookieSecurePolicy.Always;
44+
});
45+
3846
builder.Services
3947
.AddAuthentication()
4048
.AddScheme<MockEmailAuthenticationOptions, MockEmailAuthenticationHandler>(

BookLibrary.Api/Auth/AccountController.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,7 @@ protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationPr
119119
{
120120
Context.Response.Cookies.Append(
121121
key: MockAuthenticationConstants.SESSION_COOKIE_NAME,
122-
value: JsonSerializer.Serialize(user.Claims.Select(x => new MyClaim(x.Type, x.Value))),
123-
new CookieOptions
124-
{
125-
HttpOnly = true,
126-
Secure = true,
127-
SameSite = SameSiteMode.Strict
128-
});
122+
value: JsonSerializer.Serialize(user.Claims.Select(x => new MyClaim(x.Type, x.Value))));
129123

130124
return Task.CompletedTask;
131125
}

BookLibrary.Application/BookLibrary.Application.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
<ItemGroup>
44
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" />
5+
<PackageReference Include="Microsoft.FeatureManagement" />
56
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
67
<PackageReference Include="JetBrains.Annotations" />
78
</ItemGroup>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace BookLibrary.Application;
2+
3+
/// <summary>
4+
/// Application feature flags.
5+
/// </summary>
6+
public static class FeatureFlags
7+
{
8+
/// <summary>
9+
/// Is allowed to automatically apply migration on application startup.
10+
/// </summary>
11+
public const string AUTO_MIGRATIONS_ENABLED = "AutoMigrationsEnabled";
12+
}

BookLibrary.Application/Features/Books/BorrowBook/BorrowBookUseCase.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,12 @@ private async Task<Result<Book>> FetchBookAsync(BorrowBookCommand command, Cance
117117
{
118118
if (command.BookId.HasValue)
119119
{
120+
var bookId = new BookId(command.BookId.Value);
120121
var bookById = await _ctx.Books
121122
.AsTracking()
122123
.TagWithFileMember()
123124
.FirstOrDefaultAsync(
124-
x => x.Id == new BookId(command.BookId.Value),
125+
x => x.Id == bookId,
125126
ct
126127
);
127128

@@ -130,13 +131,17 @@ private async Task<Result<Book>> FetchBookAsync(BorrowBookCommand command, Cance
130131
: ErrorCodes.BookNotFound.ToDomainError();
131132
}
132133

134+
var isbn = new Isbn(command.Isbn!);
135+
var publicationDate = command.PublicationDate.HasValue
136+
? new BookPublicationDate(command.PublicationDate.Value)
137+
: null;
138+
133139
var book = await _ctx.Books
134140
.AsTracking()
135141
.TagWithFileMember()
136142
.FirstOrDefaultAsync(
137-
x => x.Isbn == new Isbn(command.Isbn!) &&
138-
(command.PublicationDate == null ||
139-
x.PublicationDate == new BookPublicationDate(command.PublicationDate.Value)) &&
143+
x => x.Isbn == isbn &&
144+
(publicationDate == null || x.PublicationDate == publicationDate) &&
140145
x.BorrowInfo == null,
141146
ct
142147
);

BookLibrary.Host/Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
app.UseExceptionHandler();
4040
app.UseRouting();
4141

42+
app.UseCookiePolicy();
4243
app.UseAuthentication();
4344
app.UseAuthorization();
4445

@@ -60,4 +61,6 @@
6061
finally
6162
{
6263
await Log.CloseAndFlushAsync();
63-
}
64+
}
65+
66+
public class BookLibraryEntryPoint;

BookLibrary.Host/appsettings.Development.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@
1313
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
1414
}
1515
}
16+
},
17+
"FeatureManagement": {
18+
"AutoMigrationsEnabled": true
1619
}
1720
}

BookLibrary.Host/appsettings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@
1010
}
1111
}
1212
},
13+
"FeatureManagement": {
14+
"AutoMigrationsEnabled": false
15+
},
1316
"AllowedHosts": "*"
1417
}

BookLibrary.Infrastructure/EntityFramework/ServiceCollectionExtensions.cs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using BookLibrary.Application.Infrastructure;
1+
using BookLibrary.Application.Infrastructure;
22
using BookLibrary.Infrastructure.ValueConverters;
33
using EntityFramework.Exceptions.PostgreSQL;
44
using Microsoft.EntityFrameworkCore;
@@ -8,6 +8,7 @@
88
using Microsoft.Extensions.Hosting;
99
using Microsoft.Extensions.Options;
1010
using Npgsql;
11+
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
1112

1213
namespace BookLibrary.Infrastructure.EntityFramework;
1314

@@ -20,8 +21,6 @@ internal static class ServiceCollectionExtensions
2021
/// <returns>Service registrator.</returns>
2122
public static IServiceCollection AddEntityFramework(this IServiceCollection services)
2223
{
23-
NpgsqlDataSource? npgsqlDataSource = null; // should be singleton
24-
2524
services
2625
.AddTransient<IConfigureOptions<ApplicationContextSettings>, ConfigureApplicationContextSettings>()
2726
.AddOptions<ApplicationContextSettings>()
@@ -44,24 +43,43 @@ public static IServiceCollection AddEntityFramework(this IServiceCollection serv
4443
.EnableSensitiveDataLogging();
4544
}
4645

47-
options.ConfigureWarnings(w => w.Ignore(CoreEventId.FirstWithoutOrderByAndFilterWarning));
48-
49-
npgsqlDataSource ??= new NpgsqlDataSourceBuilder(config.Value.ConnectionString)
50-
.EnableDynamicJson()
51-
.BuildMultiHost()
52-
.WithTargetSession(TargetSessionAttributes.Primary);
53-
5446
options
47+
.ConfigureWarnings(w => w.Ignore(CoreEventId.FirstWithoutOrderByAndFilterWarning))
5548
.ReplaceService<IValueConverterSelector, ValueObjectsConverterSelectorByConvention>()
5649
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
57-
.UseNpgsql(npgsqlDataSource, builder =>
58-
{
59-
builder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
60-
builder.MigrationsAssembly(typeof(ApplicationContext).Assembly.GetName().Name);
61-
builder.MigrationsHistoryTable("__EFMigrationHistory", ApplicationContext.DefaultScheme);
62-
})
6350
.UseSnakeCaseNamingConvention()
6451
.UseExceptionProcessor();
52+
53+
var accessor = sp.GetService<NpgsqlDataSourceAccessor>();
54+
55+
if (accessor is not null)
56+
{
57+
options.UseNpgsql(accessor.NpgsqlDataSource, b =>
58+
{
59+
b.ConfigureNpgsql();
60+
});
61+
}
62+
else
63+
{
64+
options.UseNpgsql(config.Value.ConnectionString, b =>
65+
{
66+
b.ConfigureNpgsql();
67+
b.ConfigureDataSource(ds => ds
68+
.EnableDynamicJson()
69+
.BuildMultiHost()
70+
.WithTargetSession(TargetSessionAttributes.Primary)
71+
);
72+
});
73+
}
6574
});
6675
}
67-
}
76+
77+
private static void ConfigureNpgsql(this NpgsqlDbContextOptionsBuilder b)
78+
{
79+
b.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
80+
b.MigrationsAssembly(typeof(ApplicationContext).Assembly.GetName().Name);
81+
b.MigrationsHistoryTable("__EFMigrationHistory", ApplicationContext.DefaultScheme);
82+
}
83+
}
84+
85+
public sealed record NpgsqlDataSourceAccessor(NpgsqlDataSource NpgsqlDataSource);

BookLibrary.Infrastructure/Extensions/HostExtensions.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using BookLibrary.Application;
12
using Microsoft.EntityFrameworkCore;
23
using Microsoft.Extensions.DependencyInjection;
34
using Microsoft.Extensions.Hosting;
5+
using Microsoft.FeatureManagement;
46
using System.Diagnostics.CodeAnalysis;
57

68
namespace BookLibrary.Infrastructure;
@@ -20,9 +22,14 @@ public static async Task MigrateAsync(this IHost host, CancellationToken ct = de
2022
{
2123
ArgumentNullException.ThrowIfNull(host);
2224

23-
using var scope = host.Services.CreateScope();
24-
await using var ctx = scope.ServiceProvider.GetRequiredService<ApplicationContext>();
25+
var featureManager = host.Services.GetRequiredService<IFeatureManager>();
2526

26-
await ctx.Database.MigrateAsync(ct);
27+
if (await featureManager.IsEnabledAsync(FeatureFlags.AUTO_MIGRATIONS_ENABLED))
28+
{
29+
using var scope = host.Services.CreateScope();
30+
await using var ctx = scope.ServiceProvider.GetRequiredService<ApplicationContext>();
31+
32+
await ctx.Database.MigrateAsync(ct);
33+
}
2734
}
2835
}

0 commit comments

Comments
 (0)