using Microsoft.Extensions.Logging; using NSubstitute; using PnvPanel.Application.Admin.Billing; using PnvPanel.Application.Admin.Users; using PnvPanel.Application.Billing; using PnvPanel.Application.Common.Interfaces; using PnvPanel.Application.Common.Models; using PnvPanel.Application.Tests.TestSupport; using Xunit; namespace PnvPanel.Application.Tests.Admin.Billing; public class GrantBillingGiftCommandHandlerTests { private readonly IIdentityService _identityService = Substitute.For(); private readonly IXuiPanelGateway _gateway = Substitute.For(); private readonly IRealtimeNotifier _notifier = Substitute.For(); private readonly ITelegramNotifier _telegramNotifier = Substitute.For(); private readonly ILogger _logger = Substitute.For< ILogger >(); private static CurrentUserProfile Profile(Guid userId, bool billingEnabled, DateTimeOffset? paidUntil) => new( userId, "alice", Guid.NewGuid(), "premium", IsActivated: true, IsBlocked: false, MaxConfigs: 5, MaxIpLimit: 3, SubscriptionToken: "sub-token", BillingEnabled: billingEnabled, BillingPaidUntil: paidUntil, BillingSuspended: false ); [Fact] public async Task Handle_WhenBillingEnabled_ExtendsPaidUntilAndNotifiesUser() { using var dbContext = InMemoryDbContextFactory.Create(); var adminId = Guid.NewGuid(); var userId = Guid.NewGuid(); _identityService .GetProfileAsync(userId, Arg.Any()) .Returns(Profile(userId, billingEnabled: true, paidUntil: null)); _identityService .ExtendBillingPaidUntilAsync(userId, Arg.Any(), Arg.Any()) .Returns(Result.Success()); var handler = new GrantBillingGiftCommandHandler( dbContext, _identityService, _gateway, _notifier, _telegramNotifier, FakeCurrentUser.Authenticated(adminId, "admin"), _logger ); var before = DateTimeOffset.UtcNow; var result = await handler.Handle(new GrantBillingGiftCommand(userId, 30), CancellationToken.None); Assert.True(result.IsSuccess); await _identityService .Received(1) .ExtendBillingPaidUntilAsync( userId, Arg.Is(d => d >= before.AddDays(30).AddMinutes(-1)), Arg.Any() ); await _telegramNotifier .Received(1) .NotifyUserAsync( userId, Arg.Is(m => m!.Contains("30")), Arg.Any(), Arg.Any() ); } [Fact] public async Task Handle_WhenBillingNotEnabled_ReturnsNotEnabled() { using var dbContext = InMemoryDbContextFactory.Create(); var userId = Guid.NewGuid(); _identityService .GetProfileAsync(userId, Arg.Any()) .Returns(Profile(userId, billingEnabled: false, paidUntil: null)); var handler = new GrantBillingGiftCommandHandler( dbContext, _identityService, _gateway, _notifier, _telegramNotifier, FakeCurrentUser.Authenticated(Guid.NewGuid(), "admin"), _logger ); var result = await handler.Handle(new GrantBillingGiftCommand(userId, 30), CancellationToken.None); Assert.False(result.IsSuccess); Assert.Equal(BillingErrors.NotEnabled, result.Error); } [Fact] public async Task Handle_WhenUserNotFound_ReturnsUserNotFound() { using var dbContext = InMemoryDbContextFactory.Create(); var userId = Guid.NewGuid(); _identityService .GetProfileAsync(userId, Arg.Any()) .Returns((CurrentUserProfile?)null); var handler = new GrantBillingGiftCommandHandler( dbContext, _identityService, _gateway, _notifier, _telegramNotifier, FakeCurrentUser.Authenticated(Guid.NewGuid(), "admin"), _logger ); var result = await handler.Handle(new GrantBillingGiftCommand(userId, 30), CancellationToken.None); Assert.False(result.IsSuccess); Assert.Equal(UserErrors.NotFound, result.Error); } }