Harden paid registration flow and add unit tests
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Core.Enums;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
using PowderCoating.Infrastructure.Repositories;
|
||||
using PowderCoating.Infrastructure.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace PowderCoating.UnitTests;
|
||||
|
||||
public class SubscriptionServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetUserCountAsync_PrefersCompanyOverrideOverPlanDefault()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 7, plan: 1, maxUsers: 3);
|
||||
var company = context.Companies.Local.Single(c => c.Id == 7);
|
||||
company.MaxUsersOverride = 7;
|
||||
|
||||
context.Users.AddRange(
|
||||
new ApplicationUser { Id = "u1", CompanyId = 7, UserName = "u1", Email = "u1@example.com", FirstName = "A", LastName = "One", IsActive = true },
|
||||
new ApplicationUser { Id = "u2", CompanyId = 7, UserName = "u2", Email = "u2@example.com", FirstName = "B", LastName = "Two", IsActive = true });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var (used, max) = await service.GetUserCountAsync(7);
|
||||
|
||||
Assert.Equal(2, used);
|
||||
Assert.Equal(7, max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJobCountAsync_ExcludesTerminalStatuses()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 8, plan: 2, maxActiveJobs: 50);
|
||||
SeedJobStatuses(context, 8);
|
||||
context.Jobs.AddRange(
|
||||
new Job { Id = 1, CompanyId = 8, JobNumber = "JOB-1", CustomerId = 1, Description = "Active", JobStatusId = 1, JobPriorityId = 1 },
|
||||
new Job { Id = 2, CompanyId = 8, JobNumber = "JOB-2", CustomerId = 1, Description = "Done", JobStatusId = 2, JobPriorityId = 1 },
|
||||
new Job { Id = 3, CompanyId = 8, JobNumber = "JOB-3", CustomerId = 1, Description = "Delivered", JobStatusId = 3, JobPriorityId = 1 });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var (used, max) = await service.GetJobCountAsync(8);
|
||||
|
||||
Assert.Equal(1, used);
|
||||
Assert.Equal(50, max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetQuoteCountAsync_CountsOnlyCurrentMonth()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 9, plan: 3, maxQuotes: 5);
|
||||
var currentQuote = new Quote
|
||||
{
|
||||
Id = 1,
|
||||
CompanyId = 9,
|
||||
QuoteNumber = "Q-001",
|
||||
QuoteStatusId = 1
|
||||
};
|
||||
var oldQuote = new Quote
|
||||
{
|
||||
Id = 2,
|
||||
CompanyId = 9,
|
||||
QuoteNumber = "Q-OLD",
|
||||
QuoteStatusId = 1
|
||||
};
|
||||
context.Quotes.AddRange(currentQuote, oldQuote);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
oldQuote.CreatedAt = DateTime.UtcNow.AddMonths(-1);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var (used, max) = await service.GetQuoteCountAsync(9);
|
||||
|
||||
Assert.Equal(1, used);
|
||||
Assert.Equal(5, max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanAddCustomerAsync_CompedCompany_BypassesPlanLimits()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 10, plan: 4, maxCustomers: 0);
|
||||
var company = await context.Companies.FindAsync(10);
|
||||
company!.IsComped = true;
|
||||
context.Customers.Add(new Customer { Id = 1, CompanyId = 10, CompanyName = "Customer A" });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var allowed = await service.CanAddCustomerAsync(10);
|
||||
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanUseAiPhotoQuoteAsync_RequiresFeatureEnabledAndQuotaAvailable()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 11, plan: 5, maxAiPhotoQuotesPerMonth: 2, allowAiPhotoQuotes: true);
|
||||
context.AiItemPredictions.Add(new AiItemPrediction { Id = 1, CompanyId = 11, CreatedAt = DateTime.UtcNow.AddDays(-1) });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var allowed = await service.CanUseAiPhotoQuoteAsync(11);
|
||||
|
||||
Assert.True(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanUseAiPhotoQuoteAsync_ReturnsFalse_WhenPlanDisablesFeature()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 12, plan: 6, maxAiPhotoQuotesPerMonth: 10, allowAiPhotoQuotes: false);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var allowed = await service.CanUseAiPhotoQuoteAsync(12);
|
||||
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
private static ApplicationDbContext CreateContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
return new ApplicationDbContext(options);
|
||||
}
|
||||
|
||||
private static void SeedCompanyAndPlan(
|
||||
ApplicationDbContext context,
|
||||
int companyId,
|
||||
int plan,
|
||||
int maxUsers = -1,
|
||||
int maxActiveJobs = -1,
|
||||
int maxCustomers = -1,
|
||||
int maxQuotes = -1,
|
||||
int maxAiPhotoQuotesPerMonth = -1,
|
||||
bool allowAiPhotoQuotes = true)
|
||||
{
|
||||
context.Companies.Add(new Company
|
||||
{
|
||||
Id = companyId,
|
||||
CompanyId = companyId,
|
||||
CompanyName = $"Company {companyId}",
|
||||
PrimaryContactName = "Owner",
|
||||
PrimaryContactEmail = $"owner{companyId}@example.com",
|
||||
SubscriptionPlan = plan,
|
||||
SubscriptionStatus = SubscriptionStatus.Active,
|
||||
IsActive = true
|
||||
});
|
||||
|
||||
context.SubscriptionPlanConfigs.Add(new SubscriptionPlanConfig
|
||||
{
|
||||
Id = companyId,
|
||||
CompanyId = 0,
|
||||
Plan = plan,
|
||||
DisplayName = $"Plan {plan}",
|
||||
IsActive = true,
|
||||
MaxUsers = maxUsers,
|
||||
MaxActiveJobs = maxActiveJobs,
|
||||
MaxCustomers = maxCustomers,
|
||||
MaxQuotes = maxQuotes,
|
||||
MaxAiPhotoQuotesPerMonth = maxAiPhotoQuotesPerMonth,
|
||||
AllowAiPhotoQuotes = allowAiPhotoQuotes
|
||||
});
|
||||
|
||||
context.JobPriorityLookups.Add(new JobPriorityLookup
|
||||
{
|
||||
Id = companyId,
|
||||
CompanyId = companyId,
|
||||
PriorityCode = "NORMAL",
|
||||
DisplayName = "Normal",
|
||||
DisplayOrder = 1
|
||||
});
|
||||
}
|
||||
|
||||
private static void SeedJobStatuses(ApplicationDbContext context, int companyId)
|
||||
{
|
||||
context.JobStatusLookups.AddRange(
|
||||
new JobStatusLookup
|
||||
{
|
||||
Id = 1,
|
||||
CompanyId = companyId,
|
||||
StatusCode = "Pending",
|
||||
DisplayName = "Pending",
|
||||
DisplayOrder = 1,
|
||||
IsTerminalStatus = false
|
||||
},
|
||||
new JobStatusLookup
|
||||
{
|
||||
Id = 2,
|
||||
CompanyId = companyId,
|
||||
StatusCode = "Completed",
|
||||
DisplayName = "Completed",
|
||||
DisplayOrder = 2,
|
||||
IsTerminalStatus = true
|
||||
},
|
||||
new JobStatusLookup
|
||||
{
|
||||
Id = 3,
|
||||
CompanyId = companyId,
|
||||
StatusCode = "Delivered",
|
||||
DisplayName = "Delivered",
|
||||
DisplayOrder = 3,
|
||||
IsTerminalStatus = true
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user