using PnvPanel.Application.Admin.Maintenance; using PnvPanel.Application.Tests.TestSupport; using PnvPanel.Domain.Configs; using PnvPanel.Domain.Inbounds; using PnvPanel.Domain.Nodes; using Xunit; namespace PnvPanel.Application.Tests.Admin.Maintenance; public class DeleteConfigsCommandHandlerTests { private static Node CreateNode() => Node.Register( "node-1", new Uri("https://node-1.example.com"), new NodeCredentials("admin", "protected"), null ); private static VpnConfig CreateConfig(Guid inboundId) => VpnConfig.Create(Guid.NewGuid(), inboundId, VpnProtocol.Vless, null); [Fact] public async Task DeleteRevoked_DeletesRevokedConfigsAndTheirTraffic_KeepsOthers() { using var dbContext = InMemoryDbContextFactory.Create(); var node = CreateNode(); var inbound = Inbound.FromRemote(node.Id, "1", VpnProtocol.Vless, "remark", 443); var revoked = CreateConfig(inbound.Id); revoked.Revoke(); var active = CreateConfig(inbound.Id); dbContext.Nodes.Add(node); dbContext.Inbounds.Add(inbound); dbContext.VpnConfigs.AddRange(revoked, active); dbContext.TrafficSamples.AddRange( TrafficSample.Create(revoked.Id, DateTimeOffset.UtcNow, 1, 2), TrafficSample.Create(active.Id, DateTimeOffset.UtcNow, 3, 4) ); await dbContext.SaveChangesAsync(CancellationToken.None); var handler = new DeleteRevokedConfigsCommandHandler( dbContext, FakeCurrentUser.Authenticated(Guid.NewGuid(), "admin") ); var result = await handler.Handle(new DeleteRevokedConfigsCommand(), CancellationToken.None); // Хендлер не коммитит сам (в проде это делает UnitOfWorkBehavior после диспетчера) — коммитим явно. await dbContext.SaveChangesAsync(CancellationToken.None); Assert.True(result.IsSuccess); Assert.Equal(1, result.Value); Assert.Equal([active.Id], dbContext.VpnConfigs.Select(c => c.Id).ToList()); Assert.Equal([active.Id], dbContext.TrafficSamples.Select(s => s.ConfigId).ToList()); } [Fact] public async Task DeleteOrphaned_DeletesConfigsWithoutInboundOrNode_KeepsLiveOnes() { using var dbContext = InMemoryDbContextFactory.Create(); var node = CreateNode(); var liveInbound = Inbound.FromRemote(node.Id, "1", VpnProtocol.Vless, "live", 443); // Инбаунд, чья нода удалена. var orphanInbound = Inbound.FromRemote(Guid.NewGuid(), "2", VpnProtocol.Vless, "orphan", 444); var live = CreateConfig(liveInbound.Id); var onDeletedNode = CreateConfig(orphanInbound.Id); var onDeletedInbound = CreateConfig(Guid.NewGuid()); dbContext.Nodes.Add(node); dbContext.Inbounds.AddRange(liveInbound, orphanInbound); dbContext.VpnConfigs.AddRange(live, onDeletedNode, onDeletedInbound); dbContext.TrafficSamples.Add( TrafficSample.Create(onDeletedInbound.Id, DateTimeOffset.UtcNow, 1, 2) ); await dbContext.SaveChangesAsync(CancellationToken.None); var handler = new DeleteOrphanedConfigsCommandHandler( dbContext, FakeCurrentUser.Authenticated(Guid.NewGuid(), "admin") ); var result = await handler.Handle(new DeleteOrphanedConfigsCommand(), CancellationToken.None); await dbContext.SaveChangesAsync(CancellationToken.None); Assert.True(result.IsSuccess); Assert.Equal(2, result.Value); Assert.Equal([live.Id], dbContext.VpnConfigs.Select(c => c.Id).ToList()); Assert.Empty(dbContext.TrafficSamples); } [Fact] public async Task DeleteOrphaned_WhenNothingToDelete_ReturnsZero() { using var dbContext = InMemoryDbContextFactory.Create(); var node = CreateNode(); var inbound = Inbound.FromRemote(node.Id, "1", VpnProtocol.Vless, "live", 443); dbContext.Nodes.Add(node); dbContext.Inbounds.Add(inbound); dbContext.VpnConfigs.Add(CreateConfig(inbound.Id)); await dbContext.SaveChangesAsync(CancellationToken.None); var handler = new DeleteOrphanedConfigsCommandHandler( dbContext, FakeCurrentUser.Authenticated(Guid.NewGuid(), "admin") ); var result = await handler.Handle(new DeleteOrphanedConfigsCommand(), CancellationToken.None); Assert.True(result.IsSuccess); Assert.Equal(0, result.Value); Assert.Single(dbContext.VpnConfigs); } }