using System.Net; using HrynCo.NotificationService.DAL.Abstract.Providers; using HrynCo.NotificationService.DAL.Abstract.Repositories; using HrynCo.NotificationService.DAL.EF; using Microsoft.Extensions.DependencyInjection; namespace HrynCo.NotificationService.Web.IntegrationTests; public sealed class AdminSmtpSaveTests(TemplateDatabaseFixture fixture) : IClassFixture { [Fact] public async Task BlankPasswordKeepsSavedValueWhenOnlySenderChanges() { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); var channel = await SeedChannel(context); using var client = context.App.CreateClient(new() { AllowAutoRedirect = false }); var fields = TemplateEditorTestFields.Read(await client.GetStringAsync($"/admin/channels/{channel.Id}")); fields["ServiceId"] = context.ServiceId.ToString(); fields["UseSsl"] = "true"; fields["Password"] = ""; fields["FromName"] = "Updated sender"; var response = await client.PostAsync("/admin/channels/save", new FormUrlEncodedContent(fields)); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); var saved = await ReadSettings(context, channel.Id); Assert.Equal("saved-test-secret", saved.Password); Assert.Equal("Updated sender", saved.FromName); } [Theory] [InlineData("Host", "other.example.invalid")] [InlineData("Port", "2525")] [InlineData("Username", "other-user")] [InlineData("UseSsl", "false")] public async Task ChangedConnectionWithBlankPasswordIsRejected(string field, string value) { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); var channel = await SeedChannel(context); using var client = context.App.CreateClient(new() { AllowAutoRedirect = false }); var fields = TemplateEditorTestFields.Read(await client.GetStringAsync($"/admin/channels/{channel.Id}")); fields["ServiceId"] = context.ServiceId.ToString(); fields["UseSsl"] = "true"; fields["Password"] = ""; fields[field] = value; var response = await client.PostAsync("/admin/channels/save", new FormUrlEncodedContent(fields)); var html = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains("Enter the password to save changed SMTP connection settings.", html); Assert.DoesNotContain("saved-test-secret", html); var saved = await ReadSettings(context, channel.Id); Assert.Equal("saved-test-secret", saved.Password); Assert.Equal("smtp.example.invalid", saved.Host); } [Fact] public async Task ExplicitPasswordCanReplaceSavedValue() { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); var channel = await SeedChannel(context); using var client = context.App.CreateClient(new() { AllowAutoRedirect = false }); var fields = TemplateEditorTestFields.Read(await client.GetStringAsync($"/admin/channels/{channel.Id}")); fields["ServiceId"] = context.ServiceId.ToString(); fields["UseSsl"] = "true"; fields["Password"] = "replacement-test-secret"; fields["Host"] = "other.example.invalid"; var response = await client.PostAsync("/admin/channels/save", new FormUrlEncodedContent(fields)); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); var saved = await ReadSettings(context, channel.Id); Assert.Equal("replacement-test-secret", saved.Password); Assert.Equal("other.example.invalid", saved.Host); } [Fact] public async Task NewChannelStillRequiresPassword() { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); using var client = context.App.CreateClient(new() { AllowAutoRedirect = false }); var fields = TemplateEditorTestFields.Read(await client.GetStringAsync("/admin/channels/create")); fields["ServiceId"] = context.ServiceId.ToString(); fields["Host"] = "smtp.example.invalid"; fields["Username"] = "test-user"; fields["FromEmail"] = "sender@example.invalid"; fields["FromName"] = "Sender"; fields["Password"] = ""; var response = await client.PostAsync("/admin/channels/save", new FormUrlEncodedContent(fields)); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains("The Password field is required.", await response.Content.ReadAsStringAsync()); } [Fact] public async Task InvalidFormDoesNotRenderEnteredPassword() { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); var channel = await SeedChannel(context); using var client = context.App.CreateClient(new() { AllowAutoRedirect = false }); var fields = TemplateEditorTestFields.Read(await client.GetStringAsync($"/admin/channels/{channel.Id}")); fields["Password"] = "replacement-test-secret"; fields["FromEmail"] = "invalid-address"; var response = await client.PostAsync("/admin/channels/save", new FormUrlEncodedContent(fields)); var html = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.DoesNotContain("replacement-test-secret", html); Assert.Equal("saved-test-secret", (await ReadSettings(context, channel.Id)).Password); } private static async Task SeedChannel(DefinitionApiTestContext context) { using var scope = context.App.Services.CreateScope(); var channel = new EmailChannel { ServiceId = context.ServiceId, EmailChannelType = EmailChannelType.Smtp, Settings = new SmtpChannelSettings { Host = "smtp.example.invalid", Port = 587, Username = "test-user", Password = "saved-test-secret", UseSsl = true, FromEmail = "sender@example.invalid", FromName = "Original sender" } }; await scope.ServiceProvider.GetRequiredService().AddAsync(channel); await scope.ServiceProvider.GetRequiredService().SaveChangesAsync(); return channel; } private static async Task ReadSettings(DefinitionApiTestContext context, Guid id) { using var scope = context.App.Services.CreateScope(); var channel = await scope.ServiceProvider.GetRequiredService().GetByIdAsync(id); return Assert.IsType(channel?.Settings); } }