1631786ce2
Add push contracts, provider delivery and channel diagnostics. Link email channels to services and document migration and validation. Ref: IT-1074
154 lines
9.2 KiB
C#
154 lines
9.2 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using HrynCo.NotificationService.DAL.Abstract.PushChannels;
|
|
using HrynCo.NotificationService.DAL.Abstract.Repositories;
|
|
using HrynCo.NotificationService.DAL.Abstract.Templates;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace HrynCo.NotificationService.Web.IntegrationTests;
|
|
|
|
public sealed class PushChannelAdminTests(TemplateDatabaseFixture fixture) : IClassFixture<TemplateDatabaseFixture>
|
|
{
|
|
[Fact]
|
|
public async Task TestsRequireAntiforgeryAndRejectMissingCredentialsWithoutSaving()
|
|
{
|
|
await using var context = await DefinitionApiTestContext.CreateAsync(fixture);
|
|
using var client = context.App.CreateClient();
|
|
var html = await client.GetStringAsync("/admin/push-channels/create");
|
|
Assert.Contains("Check Firebase authorization", html);
|
|
Assert.Contains("Recipient token", html);
|
|
Assert.Contains("test-authorization", html);
|
|
var fields = TemplateEditorTestFields.Read(html);
|
|
foreach (var action in new[] { "test-authorization", "test-send" })
|
|
{
|
|
var body = new { firebaseProjectId = "test-project", recipientToken = "test-token" };
|
|
Assert.Equal(HttpStatusCode.BadRequest, (await client.PostAsJsonAsync("/admin/push-channels/" + action, body)).StatusCode);
|
|
using var request = new HttpRequestMessage(HttpMethod.Post, "/admin/push-channels/" + action);
|
|
request.Headers.Add("RequestVerificationToken", fields["__RequestVerificationToken"]);
|
|
request.Content = JsonContent.Create(body);
|
|
var response = await client.SendAsync(request);
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var result = await response.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>();
|
|
Assert.False(result.GetProperty("success").GetBoolean());
|
|
Assert.DoesNotContain("test-token", await response.Content.ReadAsStringAsync());
|
|
}
|
|
using var scope = context.App.Services.CreateScope();
|
|
Assert.Empty(await scope.ServiceProvider.GetRequiredService<IPushChannelRepository>().GetAllAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAndEditKeepCredentialsOnServerAndAllowServiceReassignment()
|
|
{
|
|
await using var context = await DefinitionApiTestContext.CreateAsync(fixture);
|
|
using var client = context.App.CreateClient(new() { AllowAutoRedirect = false });
|
|
var credentials = PushChannelTestCredentials.Create();
|
|
var response = await SaveNewAsync(client, context.ServiceId, credentials);
|
|
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
|
|
using var scope = context.App.Services.CreateScope();
|
|
var channels = scope.ServiceProvider.GetRequiredService<IPushChannelRepository>();
|
|
var channel = Assert.Single(await channels.GetAllAsync());
|
|
var other = new NotificationServiceInfo { Name = "Invemory-Push-Test" };
|
|
await scope.ServiceProvider.GetRequiredService<INotificationServiceRepository>().SaveAsync(other, true);
|
|
var html = await client.GetStringAsync($"/admin/push-channels/{channel.Id}");
|
|
Assert.DoesNotContain("PRIVATE KEY", html);
|
|
Assert.DoesNotContain("test@example.invalid", html);
|
|
Assert.Contains("Credentials are saved", html);
|
|
var fields = TemplateEditorTestFields.Read(html);
|
|
fields["ServiceId"] = other.Id.ToString();
|
|
fields["CredentialsJson"] = "";
|
|
fields["IsActive"] = "true";
|
|
Assert.Equal(HttpStatusCode.Redirect, (await client.PostAsync("/admin/push-channels/save", new FormUrlEncodedContent(fields))).StatusCode);
|
|
var saved = (await channels.GetByIdAsync(channel.Id))!;
|
|
Assert.Equal(other.Id, saved.ServiceId);
|
|
Assert.Equal("Invemory-Push-Test", saved.ServiceName);
|
|
Assert.True(System.Text.Json.Nodes.JsonNode.DeepEquals(
|
|
System.Text.Json.Nodes.JsonNode.Parse(credentials),
|
|
System.Text.Json.Nodes.JsonNode.Parse((await channels.GetCredentialsAsync(channel.Id))!)));
|
|
Assert.Null(await channels.GetActiveForServiceAsync(context.ServiceId));
|
|
Assert.NotNull(await channels.GetActiveForServiceAsync(other.Id));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("duplicate")]
|
|
[InlineData("missingService")]
|
|
[InlineData("emptyService")]
|
|
[InlineData("invalidJson")]
|
|
[InlineData("missingCredentials")]
|
|
public async Task InvalidCreateDoesNotExposeSubmittedSecretOrPersistAChannel(string scenario)
|
|
{
|
|
await using var context = await DefinitionApiTestContext.CreateAsync(fixture);
|
|
using var client = context.App.CreateClient(new() { AllowAutoRedirect = false });
|
|
var credentials = PushChannelTestCredentials.Create();
|
|
if (scenario == "duplicate")
|
|
Assert.Equal(HttpStatusCode.Redirect, (await SaveNewAsync(client, context.ServiceId, credentials)).StatusCode);
|
|
var serviceId = scenario switch { "missingService" => Guid.NewGuid(), "emptyService" => Guid.Empty, _ => context.ServiceId };
|
|
var secret = scenario switch { "invalidJson" => "invalid-secret-must-not-render", "missingCredentials" => "", _ => credentials };
|
|
var response = await SaveNewAsync(client, serviceId, secret);
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
var html = await response.Content.ReadAsStringAsync();
|
|
Assert.DoesNotContain("PRIVATE KEY", html);
|
|
Assert.DoesNotContain("invalid-secret-must-not-render", html);
|
|
Assert.Contains("TestService", html);
|
|
using var scope = context.App.Services.CreateScope();
|
|
var channels = await scope.ServiceProvider.GetRequiredService<IPushChannelRepository>().GetAllAsync();
|
|
Assert.Equal(scenario == "duplicate" ? 1 : 0, channels.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BlankCredentialsCannotChangeProjectAndValidationErrorsDoNotEchoSecrets()
|
|
{
|
|
await using var context = await DefinitionApiTestContext.CreateAsync(fixture);
|
|
using var client = context.App.CreateClient(new() { AllowAutoRedirect = false });
|
|
var credentials = PushChannelTestCredentials.Create();
|
|
await SaveNewAsync(client, context.ServiceId, credentials);
|
|
using var scope = context.App.Services.CreateScope();
|
|
var channels = scope.ServiceProvider.GetRequiredService<IPushChannelRepository>();
|
|
var channel = Assert.Single(await channels.GetAllAsync());
|
|
var fields = TemplateEditorTestFields.Read(await client.GetStringAsync($"/admin/push-channels/{channel.Id}"));
|
|
fields["ServiceId"] = context.ServiceId.ToString();
|
|
fields["FirebaseProjectId"] = "other-project";
|
|
fields["CredentialsJson"] = "";
|
|
var response = await client.PostAsync("/admin/push-channels/save", new FormUrlEncodedContent(fields));
|
|
Assert.Contains("Changing project requires new credentials", await response.Content.ReadAsStringAsync());
|
|
Assert.Equal("test-project", (await channels.GetByIdAsync(channel.Id))!.FirebaseProjectId);
|
|
fields["FirebaseProjectId"] = "";
|
|
fields["CredentialsJson"] = credentials;
|
|
response = await client.PostAsync("/admin/push-channels/save", new FormUrlEncodedContent(fields));
|
|
Assert.DoesNotContain("PRIVATE KEY", await response.Content.ReadAsStringAsync());
|
|
Assert.True(System.Text.Json.Nodes.JsonNode.DeepEquals(
|
|
System.Text.Json.Nodes.JsonNode.Parse(credentials),
|
|
System.Text.Json.Nodes.JsonNode.Parse((await channels.GetCredentialsAsync(channel.Id))!)));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenameUpdatesDisplayAndInactiveChannelBlocksServiceDeletion()
|
|
{
|
|
await using var context = await DefinitionApiTestContext.CreateAsync(fixture);
|
|
using var client = context.App.CreateClient(new() { AllowAutoRedirect = false });
|
|
var credentials = PushChannelTestCredentials.Create();
|
|
await SaveNewAsync(client, context.ServiceId, credentials);
|
|
using var scope = context.App.Services.CreateScope();
|
|
var channels = scope.ServiceProvider.GetRequiredService<IPushChannelRepository>();
|
|
var channel = Assert.Single(await channels.GetAllAsync());
|
|
channel.IsActive = false;
|
|
await channels.SaveAsync(channel, credentials, false);
|
|
Assert.Null(await channels.GetActiveForServiceAsync(context.ServiceId));
|
|
var services = scope.ServiceProvider.GetRequiredService<INotificationServiceRepository>();
|
|
var service = (await services.GetAsync(context.ServiceId))!;
|
|
service.Name = "Invemory-Renamed-Push";
|
|
await services.SaveAsync(service, false);
|
|
Assert.Contains(service.Name, await client.GetStringAsync("/admin/push-channels"));
|
|
await Assert.ThrowsAsync<EmailTemplateWriteException>(() => services.DeleteAsync(service.Id, service.Revision));
|
|
}
|
|
|
|
private static async Task<HttpResponseMessage> SaveNewAsync(HttpClient client, Guid serviceId, string credentials)
|
|
{
|
|
var fields = TemplateEditorTestFields.Read(await client.GetStringAsync("/admin/push-channels/create"));
|
|
fields["ServiceId"] = serviceId.ToString();
|
|
fields["FirebaseProjectId"] = "test-project";
|
|
fields["CredentialsJson"] = credentials;
|
|
fields["IsActive"] = "true";
|
|
return await client.PostAsync("/admin/push-channels/save", new FormUrlEncodedContent(fields));
|
|
}
|
|
}
|