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:
2026-04-25 18:27:30 -04:00
parent edce8e8c4a
commit dbe4170986
13 changed files with 2930 additions and 12 deletions
@@ -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