Merge pull request 'email sending, a bug fix' (#46) from fix/register-valid-email into release/1.0.8-rc7
All checks were successful
Dotnet build and test / build (pull_request) Successful in 10m57s

Reviewed-on: #46
This commit is contained in:
Paul Schneider 2026-08-29 18:00:00 +01:00
commit 51dc24de34
7 changed files with 122 additions and 7 deletions

View file

@ -44,4 +44,8 @@ jobs:
- name: Test
run: |
echo "🚀 Lancement des tests..."
cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !" || echo "❌ Fail ($?)!"
cd /src/_src && dotnet test \
--verbosity normal \
--filter="Category!=Platform-Android" \
--logger "xunit;LogFileName=test-results.xml" \
&& echo "✅ Success !" || echo "❌ Fail ($?)!"

View file

@ -26,7 +26,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Test
run: dotnet test --no-build --verbosity normal
run: dotnet test --no-build --verbosity normal --filter="Category!=Platform-Android"
# 4. Build et Push de l'image de production finale
- name: Build and push production image
uses: docker/build-push-action@v7

View file

@ -12,6 +12,7 @@ namespace PostIt.Tests;
/// Skip conditions: the package is not installed on the connected device,
/// or no device is connected via adb.
/// </summary>
[Trait("Category", "Platform-Android")]
public class AndroidAppLaunchTests
{
private const string PackageName = "fr.pschneider.postit";
@ -23,8 +24,8 @@ public class AndroidAppLaunchTests
_output = output;
}
// https://twosixtech.com/blog/integrating-docker-and-adb/
// FIXME ala hosted shared resource adb server - [Fact]
// TODO https://twosixtech.com/blog/integrating-docker-and-adb/
[Fact]
public void PostIt_starts_and_draws_a_first_frame_on_the_emulator()
{
if (!IsPackageInstalledOnAnyDevice())

View file

@ -12,8 +12,8 @@ namespace Yavsc.ViewModels.Account
public string UserName { get; set; }
[Required()]
[StringLength( maximumLength:102, MinimumLength = 5)]
// [EmailAddress]
[StringLength(maximumLength: 102, MinimumLength = 5)]
[EmailAddress(ErrorMessage = "L'adresse e-mail n'est pas valide.")]
[Display(Name = "Email", Description = "E-Mail")]
public string Email { get; set; }

View file

@ -1,8 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using MailKit.Net.Smtp;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MimeKit;
using Yavsc.Interface;
using Yavsc.Interfaces;
using Yavsc.Models.Relationship;
using Yavsc.Org.Tests.Fakes;
using Yavsc.Services;
using Yavsc.Settings;
using Yavsc.ViewModels.Account;
namespace Yavsc.Org.Tests
{
@ -56,5 +67,96 @@ namespace Yavsc.Org.Tests
Assert.Equal(_serverFixture.SiteSettings.Owner.EMail, client.LastSentMessage?.To.Mailboxes.First().Address);
}
[Fact]
public void RegisterModel_rejects_invalid_email_format()
{
var model = new RegisterModel
{
UserName = "alice",
Email = "this is not an email",
Password = "Password123!",
ConfirmPassword = "Password123!"
};
var results = new List<ValidationResult>();
var valid = Validator.TryValidateObject(
model,
new ValidationContext(model),
results,
validateAllProperties: true);
Assert.False(valid);
Assert.Contains(results, r => r.MemberNames.Contains(nameof(RegisterModel.Email)));
}
[Fact]
public async Task SendEmailAsync_ignores_smtp_recipient_rejection()
{
var sender = new MailSender(
Options.Create(new SiteSettings
{
Title = "Test",
Authority = "example.com",
Owner = new StaticContact { Name = "Test Owner", EMail = "owner@example.com" }
}),
Options.Create(new SmtpSettings
{
Host = "smtp.test.local",
Port = 465,
UserName = "test-user",
Password = "secret"
}),
NullLoggerFactory.Instance,
new TestStringLocalizer(),
new RejectingSmtpClientFactory());
var result = await sender.SendEmailAsync(
"Alice",
"contact@pschneider.fr",
"Welcome",
"hello");
Assert.Equal(string.Empty, result);
}
private sealed class RejectingSmtpClientFactory : ISmtpClientFactory
{
public Yavsc.Interfaces.ISmtpClient CreateClient() => new RejectingSmtpClient();
}
private sealed class RejectingSmtpClient : Yavsc.Interfaces.ISmtpClient
{
public int Timeout { get; set; }
public void Connect(string host, int port, MailKit.Security.SecureSocketOptions options) { }
public void Authenticate(string userName, string password) { }
public Task SendAsync(MimeMessage message, CancellationToken cancellationToken = default)
{
throw new SmtpCommandException(
SmtpErrorCode.RecipientNotAccepted,
SmtpStatusCode.MailboxUnavailable,
"Recipient address rejected: User unknown in local recipient table");
}
public void Disconnect(bool quit) { }
public void Dispose() { }
}
private sealed class TestStringLocalizer : IStringLocalizer<MailSender>
{
public LocalizedString this[string name] => new(name, name);
public LocalizedString this[string name, params object[] arguments] => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments));
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
=> Enumerable.Empty<LocalizedString>();
public LocalizedString GetString(string name)
=> new(name, name);
public LocalizedString GetString(string name, params object[] arguments)
=> new(name, string.Format(CultureInfo.InvariantCulture, name, arguments));
public IStringLocalizer WithCulture(CultureInfo culture)
=> this;
}
}
}

View file

@ -564,6 +564,8 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterModel model)
{
model.Email = model.Email?.Trim();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };

View file

@ -1,3 +1,4 @@
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -114,7 +115,7 @@ namespace Yavsc.Services
msg.MessageId = MimeKit.Utils.MimeUtils.GenerateMessageId(
siteSettings.Authority
);
using ISmtpClient sc = _smtpClientFactory.CreateClient();
using Yavsc.Interfaces.ISmtpClient sc = _smtpClientFactory.CreateClient();
{
sc.Timeout = 30000;
sc.Connect(
@ -139,6 +140,11 @@ namespace Yavsc.Services
logger.LogError(ex, "Refusing to send email because the recipient or sender address is malformed. To={To}, From={From}", email, siteSettings.Owner.EMail);
return string.Empty;
}
catch (SmtpCommandException ex)
{
logger.LogError(ex, "SMTP rejected the recipient or sender address. To={To}, Subject={Subject}, Status={Status}, Error={Error}", email, subject, ex.StatusCode, ex.Message);
return string.Empty;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to send email. To={To}, Subject={Subject}", email, subject);