using System.Net; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; 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 EmailChannelApiTests(TemplateDatabaseFixture fixture) : IClassFixture { [Fact] public async Task SmtpSettingsRoundTripThroughCreateReadUpdateListAndDelete() { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); var request = Content("smtp.example.invalid", 2525, "test-only-user"); var created = await context.Client.PostAsJsonAsync("/api/v1/email-channels", request); Assert.Equal(HttpStatusCode.Created, created.StatusCode); var id = (await created.Content.ReadFromJsonAsync()).GetProperty("data").GetGuid(); var original = (await context.Client.GetFromJsonAsync($"/api/v1/email-channels/{id}")).GetProperty("data"); Assert.Equal("smtp.example.invalid", original.GetProperty("settings").GetProperty("host").GetString()); Assert.Equal(2525, original.GetProperty("settings").GetProperty("port").GetInt32()); Assert.Equal("test-only-user", original.GetProperty("settings").GetProperty("username").GetString()); Assert.Equal(HttpStatusCode.OK, (await context.Client.PutAsJsonAsync($"/api/v1/email-channels/{id}", Content("updated.example.invalid", 2526, "updated-user"))).StatusCode); var all = (await context.Client.GetFromJsonAsync("/api/v1/email-channels/all")).GetProperty("data"); var updated = Assert.Single(all.EnumerateArray()); Assert.Equal("updated.example.invalid", updated.GetProperty("settings").GetProperty("host").GetString()); Assert.Equal("updated-user", updated.GetProperty("settings").GetProperty("username").GetString()); Assert.Equal(2526, updated.GetProperty("settings").GetProperty("port").GetInt32()); Assert.Equal("test-only-value", updated.GetProperty("settings").GetProperty("password").GetString()); Assert.False(updated.GetProperty("settings").GetProperty("useSsl").GetBoolean()); Assert.Equal("sender@example.invalid", updated.GetProperty("settings").GetProperty("fromEmail").GetString()); Assert.Equal("Test sender", updated.GetProperty("settings").GetProperty("fromName").GetString()); var filtered = await context.Client.GetFromJsonAsync("/api/v1/email-channels?serviceName=IndependentChannelService"); Assert.Single(filtered.GetProperty("data").EnumerateArray()); Assert.Equal(HttpStatusCode.OK, (await context.Client.DeleteAsync($"/api/v1/email-channels/{id}")).StatusCode); Assert.Equal(HttpStatusCode.NotFound, (await context.Client.GetAsync($"/api/v1/email-channels/{id}")).StatusCode); Assert.Empty((await context.Client.GetFromJsonAsync("/api/v1/email-channels/all")).GetProperty("data").EnumerateArray()); } [Fact] public async Task GetAllIncludesChannelsWhoseServiceNameHasNoServiceRecord() { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); using var scope = context.App.Services.CreateScope(); var channels = scope.ServiceProvider.GetRequiredService(); foreach (var serviceName in new[] { "TestService", "OrphanChannelService" }) await channels.AddAsync(new EmailChannel { ServiceName = serviceName, EmailChannelType = EmailChannelType.Smtp, Settings = new SmtpChannelSettings { Host = "localhost", FromEmail = "test@example.invalid" } }); await scope.ServiceProvider.GetRequiredService().SaveChangesAsync(); var all = (await context.Client.GetFromJsonAsync("/api/v1/email-channels/all")).GetProperty("data"); Assert.Equal(2, all.GetArrayLength()); Assert.Contains(all.EnumerateArray(), c => c.GetProperty("serviceName").GetString() == "OrphanChannelService"); Assert.All(all.EnumerateArray(), c => Assert.NotEqual(Guid.Empty, c.GetProperty("id").GetGuid())); } [Theory] [InlineData("nullSettings")] [InlineData("invalidPort")] [InlineData("blankHost")] [InlineData("unsupportedType")] [InlineData("blankService")] public async Task InvalidSmtpCreateReturnsValidationEnvelopeWithoutPersisting(string scenario) { await using var context = await DefinitionApiTestContext.CreateAsync(fixture); var body = JsonSerializer.SerializeToNode(Content("smtp.example.invalid", 2525, "test-user"), new JsonSerializerOptions(JsonSerializerDefaults.Web))!.AsObject(); switch (scenario) { case "nullSettings": body["settings"] = null; break; case "invalidPort": body["settings"]!["port"] = 0; break; case "blankHost": body["settings"]!["host"] = " "; break; case "unsupportedType": body["channelType"] = 99; break; case "blankService": body["serviceName"] = " "; break; } var response = await context.Client.PostAsJsonAsync("/api/v1/email-channels", body); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal("InvalidRequest", (await response.Content.ReadFromJsonAsync()).GetProperty("error").GetProperty("code").GetString()); Assert.Empty((await context.Client.GetFromJsonAsync("/api/v1/email-channels/all")).GetProperty("data").EnumerateArray()); } private static object Content(string host, int port, string username) => new { serviceName = "IndependentChannelService", priority = 1, channelType = 1, settings = new { host, port, username, password = "test-only-value", useSsl = false, fromEmail = "sender@example.invalid", fromName = "Test sender" }, dailyLimit = 100, monthlyLimit = 1000, warnThresholdPercent = 90, isActive = true }; }