From 0bfe61cbb80bc2f8dac096f8880accef9adf1852 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 15:43:53 +0100 Subject: [PATCH 1/5] chore(release): bump version via gitversion for 1.0.8-rc7 --- src/PostIt/PostIt.Android/PostIt.Android.csproj | 2 +- src/PostIt/PostIt.Browser/PostIt.Browser.csproj | 2 +- src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj | 2 +- src/PostIt/PostIt.Tests/PostIt.Tests.csproj | 2 +- src/PostIt/PostIt/PostIt.csproj | 2 +- src/Yavsc.Abstract/Yavsc.Abstract.csproj | 2 +- src/Yavsc.Api.Client/Yavsc.Api.Client.csproj | 2 +- src/Yavsc.Api/Yavsc.Api.csproj | 2 +- src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj | 2 +- src/Yavsc.Blogs/Yavsc.Blogs.csproj | 2 +- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 2 +- src/Yavsc.Org/Yavsc.Org.csproj | 2 +- src/Yavsc.Server/Yavsc.Server.csproj | 2 +- src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj | 2 +- src/cli/cli.csproj | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index f3b57e87..3db9a8cb 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -11,7 +11,7 @@ false 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index 96857947..b7698cf1 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,7 +6,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index 0f6c5614..45c9d82b 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,7 +7,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj index f38bf43f..9d371fdb 100644 --- a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index e5ee0d40..b1a5ab55 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -5,7 +5,7 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 554cb196..ee0315d0 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -11,7 +11,7 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 \ No newline at end of file diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index db7aa466..e1a1f8b8 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -17,7 +17,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index 0f8054e4..34d773a9 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index 96616e99..c16bde3f 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 7f262340..377f4853 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 8be07946..d3406595 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index d4ae0787..1ee78b70 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -9,7 +9,7 @@ https://github.com/pazof/yavsc 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index c35a24d4..645ba8b7 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 89698853..c496db34 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -14,7 +14,7 @@ --> 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 0bb14811..6ff1e9f8 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+172.Branch.release-1.0.8-rc6.Sha.c5941e04ced8547b1a25e8c43eb24ddef608b428 + 1.1.0-beta.1+177.Branch.release-1.0.8-rc7.Sha.1b237fa5404368bc891ac6c599fc3920bbf83c1c 1.1.0-beta.1 From 6a6cc3a967468dd4b84d711fda2648dc3255e496 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 16:23:58 +0100 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=85=20Validation=20de=20l=E2=80=99ema?= =?UTF-8?q?il=20d=E2=80=99inscription=20ajout=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vvalidation au point le plus sûr : le modèle de formulaire, avec un garde-fou côté contrôleur pour normaliser la valeur avant création du compte. * Ajout de [EmailAddress] dans RegisterModel.cs * Nettoyage de model.Email avec Trim() avant le ModelState.IsValid dans AccountController.cs * Ajout d’un test de régression dans EMailling.cs Additionnellement, le job de test est corrigé pour laisser vivre le test en plateforme Android, hors CI --- .forgejo/workflows/buildAndTest.yml | 6 ++++- .github/workflows/docker-publish-backend.yml | 2 +- .../PostIt.Tests/AndroidAppLaunchTests.cs | 5 ++-- .../Authentication/RegisterModel.cs | 4 ++-- .../NonRegression/EMailling.cs | 24 +++++++++++++++++++ .../Accounting/AccountController.cs | 2 ++ 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index cb18656d..ea58d2fe 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -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 ($?)!" diff --git a/.github/workflows/docker-publish-backend.yml b/.github/workflows/docker-publish-backend.yml index 6c2431ae..d8466bdb 100644 --- a/.github/workflows/docker-publish-backend.yml +++ b/.github/workflows/docker-publish-backend.yml @@ -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 diff --git a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs index d4980d26..25630c83 100644 --- a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs +++ b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs @@ -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. /// +[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()) diff --git a/src/Yavsc.Abstract/Authentication/RegisterModel.cs b/src/Yavsc.Abstract/Authentication/RegisterModel.cs index 2ebb3297..3bf9876b 100644 --- a/src/Yavsc.Abstract/Authentication/RegisterModel.cs +++ b/src/Yavsc.Abstract/Authentication/RegisterModel.cs @@ -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; } diff --git a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs index 5b1b33e9..d5599842 100644 --- a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs +++ b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs @@ -1,8 +1,10 @@ +using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Yavsc.Interface; using Yavsc.Interfaces; using Yavsc.Org.Tests.Fakes; +using Yavsc.ViewModels.Account; namespace Yavsc.Org.Tests { @@ -56,5 +58,27 @@ 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(); + var valid = Validator.TryValidateObject( + model, + new ValidationContext(model), + results, + validateAllProperties: true); + + Assert.False(valid); + Assert.Contains(results, r => r.MemberNames.Contains(nameof(RegisterModel.Email))); + } + } } diff --git a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs index c562736d..05cb55b5 100644 --- a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs +++ b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs @@ -564,6 +564,8 @@ IHtmlLocalizerFactory htmlLocalizerFactory, [ValidateAntiForgeryToken] public async Task Register(RegisterModel model) { + model.Email = model.Email?.Trim(); + if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.UserName, Email = model.Email }; From 40437982ebd90245c82f2ad1e2d137521cccf387 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 17:21:40 +0100 Subject: [PATCH 3/5] ixes invalid registration email formats are still rejected before the user is created SMTP recipient rejections are logged and treated as a soft failure instead of crashing the request --- .../NonRegression/EMailling.cs | 78 +++++++++++++++++++ src/Yavsc.Server/Services/MailSender.cs | 8 +- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs index d5599842..fd1fe501 100644 --- a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs +++ b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs @@ -1,9 +1,18 @@ 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 @@ -80,5 +89,74 @@ namespace Yavsc.Org.Tests 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 + { + 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 GetAllStrings(bool includeParentCultures) + => Enumerable.Empty(); + + 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; + } + } } diff --git a/src/Yavsc.Server/Services/MailSender.cs b/src/Yavsc.Server/Services/MailSender.cs index d8b3c46c..592a43ba 100644 --- a/src/Yavsc.Server/Services/MailSender.cs +++ b/src/Yavsc.Server/Services/MailSender.cs @@ -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); From 52eedefb4a63b00d1933f66400b043594fcbd4ac Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 18:13:58 +0100 Subject: [PATCH 4/5] Release 1.0.8-rc7 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b454a3f8..d64fe9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ ### Fixed -* [Yavsc.Org][TODO] La forme de l'email de l'utilisateur est maintenant validée avant l'envoi du formulaire d'enregistrement +* [Yavsc.Org] (Ticket #45) La forme de l'email de l'utilisateur est maintenant validée avant l'envoi du formulaire d'enregistrement ## [1.0.8-rc6] - unstable From 6cff3db32ecf72c0d2d430b7002fa7816a34e070 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 19:07:02 +0100 Subject: [PATCH 5/5] PostIt publishes --- src/PostIt/PostIt.Tests/MainPageSaveTests.cs | 2 +- .../PostIt.Tests/PostItViewModelTests.cs | 45 +++++++++++++++++++ src/PostIt/PostIt/ViewModels/MainViewModel.cs | 22 ++++++--- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/PostIt/PostIt.Tests/MainPageSaveTests.cs b/src/PostIt/PostIt.Tests/MainPageSaveTests.cs index cef67f0f..0fd5627c 100644 --- a/src/PostIt/PostIt.Tests/MainPageSaveTests.cs +++ b/src/PostIt/PostIt.Tests/MainPageSaveTests.cs @@ -79,7 +79,7 @@ public class MainPageSaveTests // whose Title is exactly what the user typed. The bug // fails this assertion with Title == string.Empty. Assert.NotEmpty(recorder.Calls); - var (method, path, body) = recorder.FirstCall; + var (method, path, body) = recorder.Calls[1]; Assert.Equal(HttpMethod.Post, method); Assert.Equal("blogspot", path); var sent = Assert.IsType(body); diff --git a/src/PostIt/PostIt.Tests/PostItViewModelTests.cs b/src/PostIt/PostIt.Tests/PostItViewModelTests.cs index d2c5d78e..1a867bd6 100644 --- a/src/PostIt/PostIt.Tests/PostItViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/PostItViewModelTests.cs @@ -55,6 +55,21 @@ public class PostItViewModelTests Assert.Equal("Hello", posts[0].Title); } + [Fact] + public async Task TogglePublishCommand_uses_the_current_checked_state_without_inverting_it() + { + var api = new RecordingPublishApi(); + var blog = new BlogApiClient(api, "http://localhost/"); + var viewModel = new MainViewModel(blog); + + viewModel.SelectedPost = new BlogPostDto { Id = 42, IsPublished = false }; + + await viewModel.SetPublishStateAsync(true); + + Assert.True(api.LastPublishValue); + Assert.True(viewModel.DraftIsPublished); + } + /// Test fake that always throws if the API is invoked. private sealed class ThrowingYavscApiClient : YavscApiClient { @@ -103,4 +118,34 @@ public class PostItViewModelTests return Task.FromResult(default(T)!); } } + + private sealed class RecordingPublishApi : IYavscApiClient + { + public bool LastPublishValue { get; private set; } + public HttpClient Http { get; } = new(); + + public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) + { + if (method == HttpMethod.Put && path.Contains("/publish", StringComparison.OrdinalIgnoreCase)) + { + var publish = body?.GetType().GetProperty("publish")?.GetValue(body) is bool value && value; + LastPublishValue = publish; + } + + return Task.FromResult(default(T)!); + } + + public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) + { + if (method == HttpMethod.Put && path.Contains("/publish", StringComparison.OrdinalIgnoreCase)) + { + var publish = body?.GetType().GetProperty("publish")?.GetValue(body) is bool value && value; + LastPublishValue = publish; + } + + return Task.CompletedTask; + } + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + } } diff --git a/src/PostIt/PostIt/ViewModels/MainViewModel.cs b/src/PostIt/PostIt/ViewModels/MainViewModel.cs index 2b065cae..84e10cfb 100644 --- a/src/PostIt/PostIt/ViewModels/MainViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/MainViewModel.cs @@ -190,8 +190,7 @@ public partial class MainViewModel : ViewModelBase /// overload; the dedicated endpoint keeps the wire /// contract clean. /// - [RelayCommand] - internal async Task TogglePublishAsync() + public async Task SetPublishStateAsync(bool publish) { if (SelectedPost is null || SelectedPost.Id == 0) { @@ -201,20 +200,29 @@ public partial class MainViewModel : ViewModelBase await ExecuteAsync(async () => { - var desired = !DraftIsPublished; - await BlogClient!.SetPublishAsync(SelectedPost.Id, desired); - DraftIsPublished = desired; + // The checkbox updates DraftIsPublished before the command is + // executed. Using the current bound value avoids the + // double-toggle bug in which the UI has already flipped the + // state and the command flips it again. + await BlogClient!.SetPublishAsync(SelectedPost.Id, publish); + DraftIsPublished = publish; // Mirror into the selected post so a subsequent // RefreshPostsAsync() doesn't blow away the // locally flipped state until the round-trip // re-hydrates it. - SelectedPost.IsPublished = desired; - StatusMessage = desired + SelectedPost.IsPublished = publish; + StatusMessage = publish ? $"Billet {SelectedPost.Id} publié." : $"Billet {SelectedPost.Id} remis en brouillon."; }); } + [RelayCommand] + internal async Task TogglePublishAsync() + { + await SetPublishStateAsync(DraftIsPublished); + } + /// /// DEV ONLY: open the signature capture page. The production /// entry point is a SignalR push from Yavsc.Org ("devis