Add unit tests for 9 new services/controllers and expand existing test coverage
116 tests passing: JobPhotoService, MeasurementConversionService, PlatformSettingsService, QuoteApprovalController, QuotePhotoService, ShopCapabilityCalculator, StorageMigrationService, TenantContext, UsageQuotaController — plus expanded PricingCalculation, Registration, and Subscription tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,191 @@ public class SubscriptionServiceTests
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatusAsync_ReturnsGracePeriod_WhenSubscriptionRecentlyExpired()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.Companies.Add(new Company
|
||||
{
|
||||
Id = 20,
|
||||
CompanyId = 20,
|
||||
CompanyName = "Grace Co",
|
||||
PrimaryContactName = "Owner",
|
||||
PrimaryContactEmail = "grace@example.com",
|
||||
SubscriptionStatus = SubscriptionStatus.Active,
|
||||
SubscriptionEndDate = DateTime.UtcNow.Date.AddDays(-5),
|
||||
IsActive = true
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var status = await service.GetStatusAsync(20);
|
||||
|
||||
Assert.Equal(SubscriptionStatus.GracePeriod, status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatusAsync_ReturnsExpired_WhenPastGraceWindow()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.Companies.Add(new Company
|
||||
{
|
||||
Id = 21,
|
||||
CompanyId = 21,
|
||||
CompanyName = "Expired Co",
|
||||
PrimaryContactName = "Owner",
|
||||
PrimaryContactEmail = "expired@example.com",
|
||||
SubscriptionStatus = SubscriptionStatus.Active,
|
||||
SubscriptionEndDate = DateTime.UtcNow.Date.AddDays(-15),
|
||||
IsActive = true
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var status = await service.GetStatusAsync(21);
|
||||
|
||||
Assert.Equal(SubscriptionStatus.Expired, status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatusAsync_ReturnsActive_ForCompedCompanyEvenWhenExpired()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.Companies.Add(new Company
|
||||
{
|
||||
Id = 22,
|
||||
CompanyId = 22,
|
||||
CompanyName = "Comped Co",
|
||||
PrimaryContactName = "Owner",
|
||||
PrimaryContactEmail = "comped@example.com",
|
||||
SubscriptionStatus = SubscriptionStatus.Active,
|
||||
SubscriptionEndDate = DateTime.UtcNow.Date.AddDays(-30),
|
||||
IsActive = true,
|
||||
IsComped = true
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var status = await service.GetStatusAsync(22);
|
||||
|
||||
Assert.Equal(SubscriptionStatus.Active, status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsAiInventoryAssistEnabledAsync_RequiresCompanyToggle()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 13, plan: 7, allowAiInventoryAssist: true);
|
||||
var company = await context.Companies.FindAsync(13);
|
||||
company!.AiInventoryAssistEnabled = false;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var enabled = await service.IsAiInventoryAssistEnabledAsync(13);
|
||||
|
||||
Assert.False(enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetJobPhotoCountAsync_ExcludesAiAnalysisPhotos()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 14, plan: 8, maxJobPhotos: 5);
|
||||
context.JobPhotos.AddRange(
|
||||
new JobPhoto
|
||||
{
|
||||
Id = 1,
|
||||
CompanyId = 14,
|
||||
JobId = 100,
|
||||
FilePath = "jobs/100/1.jpg",
|
||||
FileName = "1.jpg",
|
||||
FileSize = 100,
|
||||
ContentType = "image/jpeg",
|
||||
UploadedById = "u1"
|
||||
},
|
||||
new JobPhoto
|
||||
{
|
||||
Id = 2,
|
||||
CompanyId = 14,
|
||||
JobId = 100,
|
||||
FilePath = "jobs/100/2.jpg",
|
||||
FileName = "2.jpg",
|
||||
FileSize = 100,
|
||||
ContentType = "image/jpeg",
|
||||
UploadedById = "u1",
|
||||
IsAiAnalysisPhoto = true
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var (used, max) = await service.GetJobPhotoCountAsync(14, 100);
|
||||
|
||||
Assert.Equal(1, used);
|
||||
Assert.Equal(5, max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetQuotePhotoCountAsync_ExcludesAiAnalysisPhotos()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 15, plan: 9, maxQuotePhotos: 4);
|
||||
context.QuotePhotos.AddRange(
|
||||
new QuotePhoto
|
||||
{
|
||||
Id = 1,
|
||||
CompanyId = 15,
|
||||
QuoteId = 200,
|
||||
TempId = "temp-1",
|
||||
FilePath = "quotes/200/1.jpg",
|
||||
FileName = "1.jpg",
|
||||
FileSize = 100,
|
||||
ContentType = "image/jpeg",
|
||||
IsAiAnalysisPhoto = false
|
||||
},
|
||||
new QuotePhoto
|
||||
{
|
||||
Id = 2,
|
||||
CompanyId = 15,
|
||||
QuoteId = 200,
|
||||
TempId = "temp-2",
|
||||
FilePath = "quotes/200/2.jpg",
|
||||
FileName = "2.jpg",
|
||||
FileSize = 100,
|
||||
ContentType = "image/jpeg",
|
||||
IsAiAnalysisPhoto = true
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var (used, max) = await service.GetQuotePhotoCountAsync(15, 200);
|
||||
|
||||
Assert.Equal(1, used);
|
||||
Assert.Equal(4, max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanUseAiPhotoQuoteAsync_ReturnsFalse_WhenUsageEqualsLimit()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
SeedCompanyAndPlan(context, companyId: 16, plan: 10, maxAiPhotoQuotesPerMonth: 2, allowAiPhotoQuotes: true);
|
||||
context.AiItemPredictions.AddRange(
|
||||
new AiItemPrediction { Id = 1, CompanyId = 16, CreatedAt = DateTime.UtcNow.AddDays(-1) },
|
||||
new AiItemPrediction { Id = 2, CompanyId = 16, CreatedAt = DateTime.UtcNow.AddDays(-2) });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new SubscriptionService(new UnitOfWork(context), context);
|
||||
|
||||
var allowed = await service.CanUseAiPhotoQuoteAsync(16);
|
||||
|
||||
Assert.False(allowed);
|
||||
}
|
||||
|
||||
private static ApplicationDbContext CreateContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
@@ -148,7 +333,10 @@ public class SubscriptionServiceTests
|
||||
int maxCustomers = -1,
|
||||
int maxQuotes = -1,
|
||||
int maxAiPhotoQuotesPerMonth = -1,
|
||||
bool allowAiPhotoQuotes = true)
|
||||
int maxJobPhotos = -1,
|
||||
int maxQuotePhotos = -1,
|
||||
bool allowAiPhotoQuotes = true,
|
||||
bool allowAiInventoryAssist = true)
|
||||
{
|
||||
context.Companies.Add(new Company
|
||||
{
|
||||
@@ -173,8 +361,11 @@ public class SubscriptionServiceTests
|
||||
MaxActiveJobs = maxActiveJobs,
|
||||
MaxCustomers = maxCustomers,
|
||||
MaxQuotes = maxQuotes,
|
||||
MaxJobPhotos = maxJobPhotos,
|
||||
MaxQuotePhotos = maxQuotePhotos,
|
||||
MaxAiPhotoQuotesPerMonth = maxAiPhotoQuotesPerMonth,
|
||||
AllowAiPhotoQuotes = allowAiPhotoQuotes
|
||||
AllowAiPhotoQuotes = allowAiPhotoQuotes,
|
||||
AllowAiInventoryAssist = allowAiInventoryAssist
|
||||
});
|
||||
|
||||
context.JobPriorityLookups.Add(new JobPriorityLookup
|
||||
|
||||
Reference in New Issue
Block a user