feat(push): add service-owned firebase channels and delivery

Add push contracts, provider delivery and channel diagnostics. Link email channels to services and document migration and validation.

Ref: IT-1074
This commit is contained in:
2026-10-24 12:31:35 +03:00
parent b30dba93e8
commit 1631786ce2
105 changed files with 3838 additions and 147 deletions
@@ -34,7 +34,7 @@ public sealed class ServiceDeletionApiTests(TemplateDatabaseFixture fixture) : I
}
[Fact]
public async Task ExplicitCleanupDeletesDefinitionsThenServiceAndLanguages_ButRetainsChannels()
public async Task ExplicitCleanupRequiresReassigningChannelsBeforeDeletingService()
{
await using var context = await DefinitionApiTestContext.CreateAsync(fixture);
await SeedChannelAsync(context);
@@ -53,10 +53,21 @@ public sealed class ServiceDeletionApiTests(TemplateDatabaseFixture fixture) : I
Assert.Equal(service.GetProperty("revision").GetGuid(), afterDefinitionDeletion.GetProperty("revision").GetGuid());
Assert.Equal(service.GetProperty("supportedLanguageCodes").GetRawText(), afterDefinitionDeletion.GetProperty("supportedLanguageCodes").GetRawText());
Assert.All(afterDefinitionDeletion.GetProperty("languages").EnumerateArray(), l => Assert.Equal(0, l.GetProperty("versionCount").GetInt32()));
Assert.Equal(HttpStatusCode.Conflict, (await context.Client.DeleteAsync(context.ServiceRoot + "?revision=" + service.GetProperty("revision").GetGuid())).StatusCode);
using (var reassignment = context.App.Services.CreateScope())
{
var other = new NotificationServiceInfo { Name = "OtherService" };
await reassignment.ServiceProvider.GetRequiredService<INotificationServiceRepository>().SaveAsync(other, true);
var channels = reassignment.ServiceProvider.GetRequiredService<IEmailChannelRepository>();
var channel = Assert.Single(await channels.GetAllAsync());
channel.ServiceId = other.Id;
await channels.UpdateAsync(channel);
await reassignment.ServiceProvider.GetRequiredService<NotificationDbContext>().SaveChangesAsync();
}
Assert.Equal(HttpStatusCode.OK, (await context.Client.DeleteAsync(context.ServiceRoot + "?revision=" + service.GetProperty("revision").GetGuid())).StatusCode);
Assert.Equal(HttpStatusCode.NotFound, (await context.Client.GetAsync(context.ServiceRoot)).StatusCode);
var all = await context.Client.GetFromJsonAsync<JsonElement>("/api/v1/services/all");
Assert.Empty(all.GetProperty("data").EnumerateArray());
Assert.Single(all.GetProperty("data").EnumerateArray());
using var scope = context.App.Services.CreateScope();
Assert.Single(await scope.ServiceProvider.GetRequiredService<IEmailChannelRepository>().GetAllAsync());
Assert.Equal(HttpStatusCode.NotFound, (await context.Client.DeleteAsync(context.ServiceRoot + "?revision=" + service.GetProperty("revision").GetGuid())).StatusCode);
@@ -145,7 +156,7 @@ public sealed class ServiceDeletionApiTests(TemplateDatabaseFixture fixture) : I
using var scope = context.App.Services.CreateScope();
await scope.ServiceProvider.GetRequiredService<IEmailChannelRepository>().AddAsync(new EmailChannel
{
ServiceName = "TestService", EmailChannelType = EmailChannelType.Smtp,
ServiceId = context.ServiceId, EmailChannelType = EmailChannelType.Smtp,
Settings = new SmtpChannelSettings { Host = "localhost", FromEmail = "test@example.invalid" }
});
await scope.ServiceProvider.GetRequiredService<NotificationDbContext>().SaveChangesAsync();