This commit is contained in:
Paul Schneider 2026-04-20 00:35:51 +01:00
commit 6cc0c519d2
15 changed files with 81 additions and 187 deletions

2
.gitignore vendored
View file

@ -45,3 +45,5 @@ yavsc-pre
*.env *.env
generated/ generated/
*.lscache *.lscache
*.Development.json
*.log

View file

@ -33,7 +33,6 @@
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="2.3.9" /> <PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="2.3.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.6" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.6" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.6" />

View file

@ -67,6 +67,7 @@ internal class Program
}); });
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
services.AddTransient<ITrueEmailSender, MailSender>() services.AddTransient<ITrueEmailSender, MailSender>()

View file

@ -85,18 +85,9 @@ public static class HostingExtensions
options.ResourcesPath = "Resources"; options.ResourcesPath = "Resources";
}).AddDataAnnotationsLocalization(); }).AddDataAnnotationsLocalization();
bool useTestEmailSender = builder.Configuration.GetValue<bool>("UseTestEmailSender", false);
if (useTestEmailSender)
{
services.AddTransient<ITrueEmailSender, TestMailSender>()
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, TestMailSender>();
}
else
{
services.AddTransient<ITrueEmailSender, MailSender>() services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>(); .AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>();
}
services.AddTransient<IYavscMessageSender, YavscMessageSender>() services.AddTransient<IYavscMessageSender, YavscMessageSender>()
.AddTransient<IBillingService, BillingService>() .AddTransient<IBillingService, BillingService>()
@ -147,21 +138,12 @@ public static class HostingExtensions
public static IdentityBuilder AddIdentityDBAndStores(this WebApplicationBuilder builder) public static IdentityBuilder AddIdentityDBAndStores(this WebApplicationBuilder builder)
{ {
IServiceCollection services = builder.Services; IServiceCollection services = builder.Services;
bool useInMemory = builder.Configuration.GetValue<bool>("UseInMemoryDatabase", false);
if (useInMemory)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite("Data Source=file::memory:?cache=shared"));
}
else
{
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
{ {
options.UseNpgsql(builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName), options.UseNpgsql(builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName),
options => options.MigrationsAssembly(typeof(Program).Assembly)); options => options.MigrationsAssembly(typeof(Program).Assembly));
}); });
}
var identityBuilder = services.AddIdentity<ApplicationUser, IdentityRole>( var identityBuilder = services.AddIdentity<ApplicationUser, IdentityRole>(
options => options =>
@ -280,8 +262,8 @@ public static class HostingExtensions
}); });
var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name;
var connectionString = builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName); var connectionString = builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName);
bool useInMemory = builder.Configuration.GetValue<bool>("UseInMemoryDatabase", false);
string sqliteInMemoryConnectionString = "Data Source=file::memory:?cache=shared"; string sqliteConnectionString = $"Data Source={Path.Combine(Path.GetTempPath(), "yavsc_test.db")}";
var identityServerBuilder = builder.Services.AddIdentityServer(options => var identityServerBuilder = builder.Services.AddIdentityServer(options =>
{ {
@ -300,41 +282,16 @@ public static class HostingExtensions
.AddCorsPolicyService<CorsPolicyService>() .AddCorsPolicyService<CorsPolicyService>()
.AddResourceStore<ResourceStore>() .AddResourceStore<ResourceStore>()
.AddConfigurationStore(options => .AddConfigurationStore(options =>
{
if (useInMemory)
{
options.ConfigureDbContext = b => b.UseSqlite(sqliteInMemoryConnectionString);
}
else
{ {
options.ConfigureDbContext = b => b.UseNpgsql(connectionString, options.ConfigureDbContext = b => b.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly)) sql => sql.MigrationsAssembly(migrationsAssembly))
.UseSeeding((context, _) => .UseSeeding(EnsureDefaultApplicationScopes());
{
foreach (String scope in new string[] { "blog", "admin", "contract", "com"})
{
var testBlog = context.Set<ApiScope>().FirstOrDefault(b => b.Name == scope);
if (testBlog == null)
{
context.Set<ApiScope>().Add(new ApiScope { Name = scope });
context.SaveChanges();
}
}
});
}
}) })
.AddOperationalStore(options => .AddOperationalStore(options =>
{
if (useInMemory)
{
options.ConfigureDbContext = b => b.UseSqlite(sqliteInMemoryConnectionString);
}
else
{ {
options.ConfigureDbContext = b => b.UseNpgsql(connectionString, options.ConfigureDbContext = b => b.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly)); sql => sql.MigrationsAssembly(migrationsAssembly));
}
}); });
if (builder.Environment.IsDevelopment()) if (builder.Environment.IsDevelopment())
@ -344,6 +301,23 @@ public static class HostingExtensions
return identityServerBuilder; return identityServerBuilder;
} }
private static Action<DbContext, bool> EnsureDefaultApplicationScopes()
{
return (context, _) =>
{
foreach (String scope in new string[] { "blog", "admin", "contract", "com" })
{
var existentScope = context.Set<ApiScope>().FirstOrDefault(b => b.Name == scope);
if (existentScope == null)
{
context.Set<ApiScope>().Add(new ApiScope { Name = scope });
context.SaveChanges();
}
}
};
}
private static void ConfigureRequestLocalization(IServiceCollection services) private static void ConfigureRequestLocalization(IServiceCollection services)
{ {
services.Configure<RequestLocalizationOptions>(options => services.Configure<RequestLocalizationOptions>(options =>

View file

@ -31,8 +31,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
<PackageReference Include="Google.Apis.Compute.v1" /> <PackageReference Include="Google.Apis.Compute.v1" />

View file

@ -47,9 +47,9 @@ namespace Yavsc.Services
/// <returns>a MessageWithPayloadResponse, /// <returns>a MessageWithPayloadResponse,
/// <c>bool somethingsent = (response.failure == 0 &amp;&amp; response.success > 0)</c> /// <c>bool somethingsent = (response.failure == 0 &amp;&amp; response.success > 0)</c>
/// </returns> /// </returns>
public async Task SendEmailAsync(string email, string subject, string htmlMessage) public Task SendEmailAsync(string email, string subject, string htmlMessage)
{ {
await SendEmailAsync("", email, subject, htmlMessage); return SendEmailAsync("", email, subject, htmlMessage);
} }
public async Task<string> SendEmailAsync(string name, string email, string subject, string htmlMessage) public async Task<string> SendEmailAsync(string name, string email, string subject, string htmlMessage)
@ -71,8 +71,9 @@ namespace Yavsc.Services
); );
using (SmtpClient sc = new()) using (SmtpClient sc = new())
{ {
sc.Timeout = 30000;
sc.Connect( sc.Connect(
smtpSettings.Server, smtpSettings.Host,
smtpSettings.Port, smtpSettings.Port,
SecureSocketOptions.Auto SecureSocketOptions.Auto
); );

View file

@ -1,30 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Logging;
using Yavsc.Interface;
namespace Yavsc.Services
{
public class TestMailSender : ITrueEmailSender, IEmailSender
{
private readonly ILogger<TestMailSender> logger;
public TestMailSender(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<TestMailSender>();
}
public Task SendEmailAsync(string email, string subject, string htmlMessage)
{
logger.LogInformation("[TestMailSender] SendEmailAsync to {Email} subject={Subject}", email, subject);
return Task.CompletedTask;
}
public Task<string> SendEmailAsync(string name, string email, string subject, string htmlMessage)
{
logger.LogInformation("[TestMailSender] SendEmailAsync to {Email} subject={Subject} name={Name}", email, subject, name);
return Task.FromResult($"test-message-{Guid.NewGuid()}");
}
}
}

View file

@ -2,17 +2,13 @@ namespace Yavsc.Settings
{ {
public class SmtpSettings public class SmtpSettings
{ {
public string Server { get; set; }
public string Host public string Host
{ {
get => Server; get ;
set => Server = value; set ;
} }
public int Port { get; set; } public int Port { get; set; }
public string SenderName { get; set; }
public string SenderEmail { get; set; }
public string UserName { get; set; } public string UserName { get; set; }
public string Password { get; set; } public string Password { get; set; }
} }

View file

@ -53,8 +53,6 @@ namespace yavscTests
public void HaveConfigurationRoot() public void HaveConfigurationRoot()
{ {
var builder = new ConfigurationBuilder(); var builder = new ConfigurationBuilder();
builder.AddJsonFile( "appsettings.json", false);
builder.AddJsonFile( "appsettings.Development.json", true);
configurationRoot = builder.Build(); configurationRoot = builder.Build();
} }

View file

@ -25,8 +25,7 @@ namespace yavscTests
[Fact] [Fact]
public void SendEMailSynchrone() public void SendEMailSynchrone()
{ {
AssertAsync.CompletesIn(2, () =>
{
using IServiceScope scope = _serverFixture.Services.CreateScope(); using IServiceScope scope = _serverFixture.Services.CreateScope();
ITrueEmailSender mailSender = scope.ServiceProvider.GetRequiredService<ITrueEmailSender>(); ITrueEmailSender mailSender = scope.ServiceProvider.GetRequiredService<ITrueEmailSender>();
@ -38,7 +37,6 @@ namespace yavscTests
$"monthly email", $"monthly email",
"test boby monthly email").Wait(); "test boby monthly email").Wait();
});
} }
} }
} }

View file

@ -1,37 +0,0 @@
using System;
using System.Threading.Tasks;
namespace yavscTests {
public static class AssertAsync {
/// <summary>
/// Completes In
/// </summary>
/// <param name="timeoutFromSecond"></param>
/// <param name="action"></param>
public static void CompletesIn(int timeoutFromSecond, Action action)
{
var task = Task.Run(action);
var completedInTime = Task.WaitAll(new[] { task }, TimeSpan.FromSeconds(timeoutFromSecond));
if (task.Exception != null)
{
if (task.Exception.InnerExceptions.Count == 1)
{
throw task.Exception.InnerExceptions[0];
}
throw task.Exception;
}
if (!completedInTime)
{
throw new TimeoutException($"Task did not complete in {timeoutFromSecond} seconds.");
}
}
}
}

View file

@ -1,4 +1,4 @@
using IdentityServer8.EntityFramework.DbContexts;
using IdentityServer8.EntityFramework.Entities; using IdentityServer8.EntityFramework.Entities;
using IdentityServer8.Models; using IdentityServer8.Models;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -137,28 +137,16 @@ namespace isnd.tests
var builder = WebApplication.CreateBuilder(); var builder = WebApplication.CreateBuilder();
builder.Environment.EnvironmentName = "Development"; builder.Environment.EnvironmentName = "Development";
// Set ContentRoot to the Yavsc.Org project directory so WebRootPath resolves correctly // Set ContentRoot to the Yavsc.Org project directory so WebRootPath resolves correctly
var testAssemblyLocation = AppDomain.CurrentDomain.BaseDirectory; var testAssemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
var yavscOrgPath = Path.GetFullPath(Path.Combine(testAssemblyLocation, "../../src/Yavsc.Org")); var yavscOrgPath = Path.GetFullPath(Path.Combine(testAssemblyLocation, "../../src/Yavsc.Org"));
builder.Environment.ContentRootPath = yavscOrgPath; builder.Environment.ContentRootPath = yavscOrgPath;
ConfigureLogger(); ConfigureLogger();
builder.Configuration var config = builder.Configuration
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: false) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: false, reloadOnChange: false)
.AddEnvironmentVariables() .AddEnvironmentVariables().Build();
.AddInMemoryCollection(new Dictionary<string, string?>
{
["UseInMemoryDatabase"] = "true",
["UseTestEmailSender"] = "true",
["Smtp:Host"] = "localhost",
["Smtp:Port"] = "25",
["Smtp:SenderName"] = "Yavsc Test",
["Smtp:SenderEmail"] = "test@example.com",
["Site:Audience"] = "https://localhost",
["Site:Authority"] = "https://localhost"
});
// Configure Kestrel for HTTPS with self-signed certificate on a dynamic port // Configure Kestrel for HTTPS with self-signed certificate on a dynamic port
builder.WebHost.ConfigureKestrel(options => builder.WebHost.ConfigureKestrel(options =>
@ -174,6 +162,7 @@ namespace isnd.tests
_app = builder.ConfigureWebAppServices(); _app = builder.ConfigureWebAppServices();
Services = _app.Services; Services = _app.Services;
SiteSettings = _app.Services.GetRequiredService<IOptions<SiteSettings>>().Value; SiteSettings = _app.Services.GetRequiredService<IOptions<SiteSettings>>().Value;
String cxStr = config.GetConnectionString(Constants.YavscConnectionStringName) ?? throw new InvalidOperationException("DefaultConnection string is not configured.");
using (var migrationScope = _app.Services.CreateScope()) using (var migrationScope = _app.Services.CreateScope())
{ {
@ -181,7 +170,7 @@ namespace isnd.tests
db.Database.EnsureDeleted(); db.Database.EnsureDeleted();
db.Database.EnsureCreated(); db.Database.EnsureCreated();
TestingUserName = "Tester"; TestingUserName = "Tester";
TestingUserPassword = "tesT456+*"; TestingUserPassword = "Test123!";
TestClientId = "testClientId"; TestClientId = "testClientId";
TestingUserEmail = "test@no-reply.com"; TestingUserEmail = "test@no-reply.com";
TestingUser = null; TestingUser = null;

View file

@ -1,6 +1,7 @@
{ {
"Site": { "Site": {
"Authority": "https://localhost", "Audience": "https://localhost",
"Authority": "https://mercure.pschneider.fr",
"Title": "Yavsc dev", "Title": "Yavsc dev",
"Slogan": "Yavsc : WIP.", "Slogan": "Yavsc : WIP.",
"Banner": "/images/yavsc.png", "Banner": "/images/yavsc.png",
@ -26,14 +27,11 @@
} }
}, },
"Smtp": { "Smtp": {
"Host": "localhost", "Server": "localhost",
"Port": 25, "Port": 465
"EnableSSL": false
}, },
"Logging": { "Logging": {
"IncludeScopes": { "IncludeScopes": {},
},
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Debug",
"System": "Warning", "System": "Warning",
@ -41,7 +39,7 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"YavscConnection": "Server=lame-NpgsqlHostName;Port=5432;Database=lame-DataBase;Username=lame-Username;Password=lame-dbPassword;" "YavscConnection": "Server=localhost;Port=5432;Database=testingYavsc;Username=lame-Username;Password=lame-dbPassword;"
}, },
"DataProtection": { "DataProtection": {
"Keys": { "Keys": {

View file

@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<RootNamespace>Yavsc.Tests</RootNamespace> <RootNamespace>Yavsc.Tests</RootNamespace>
<UserSecretsId>78a4efec-68dc-4745-ba06-d8545ef9ee91</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" /> <PackageReference Include="coverlet.collector" />
@ -29,6 +30,11 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="appsettings.*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Yavsc.Org\Yavsc.Org.csproj" /> <ProjectReference Include="..\..\src\Yavsc.Org\Yavsc.Org.csproj" />
<ProjectReference Include="..\..\src\Yavsc.Abstract\Yavsc.Abstract.csproj" /> <ProjectReference Include="..\..\src\Yavsc.Abstract\Yavsc.Abstract.csproj" />
@ -37,4 +43,5 @@
<ItemGroup> <ItemGroup>
<Using Include="Xunit" /> <Using Include="Xunit" />
</ItemGroup> </ItemGroup>
</Project> </Project>