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
@@ -0,0 +1,213 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using PowderCoating.Application.Configuration;
using PowderCoating.Application.Interfaces;
using PowderCoating.Application.Services;
namespace PowderCoating.UnitTests;
public class StorageMigrationServiceTests
{
[Fact]
public async Task MigrateFilesystemToAzureAsync_ReturnsError_WhenDirectoryMissing()
{
var service = CreateService();
var missingPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
var result = await service.MigrateFilesystemToAzureAsync(missingPath);
Assert.Equal(0, result.Failed);
Assert.Equal(0, result.Total);
Assert.Contains("Media directory not found", result.Errors.Single());
Assert.True(result.HasErrors);
}
[Fact]
public async Task MigrateFilesystemToAzureAsync_MarksUnknownPathsAsFailed()
{
var mediaRoot = CreateTempMediaRoot();
try
{
var unknownPath = Path.Combine(mediaRoot, "1", "misc");
Directory.CreateDirectory(unknownPath);
await File.WriteAllTextAsync(Path.Combine(unknownPath, "file.bin"), "abc");
var service = CreateService();
var result = await service.MigrateFilesystemToAzureAsync(mediaRoot);
Assert.Equal(1, result.Failed);
Assert.Contains(result.Errors, e => e.Contains("Unknown file type"));
Assert.Equal(MigrationFileStatus.Failed, result.Files.Single().Status);
}
finally
{
SafeDeleteDirectory(mediaRoot);
}
}
[Fact]
public async Task MigrateFilesystemToAzureAsync_SkipsExistingBlobs()
{
var mediaRoot = CreateTempMediaRoot();
try
{
var filePath = WriteMediaFile(mediaRoot, "1/profile-photos/user.jpg", "profile");
var relativePath = "1/profile-photos/user.jpg";
var blobService = new Mock<IAzureBlobStorageService>();
blobService
.Setup(x => x.ExistsAsync("profileimages", relativePath))
.ReturnsAsync(true);
var service = CreateService(blobService);
var result = await service.MigrateFilesystemToAzureAsync(mediaRoot);
Assert.Equal(1, result.Skipped);
Assert.Equal(0, result.Migrated);
Assert.Equal(MigrationFileStatus.Skipped, result.Files.Single().Status);
Assert.True(File.Exists(filePath));
}
finally
{
SafeDeleteDirectory(mediaRoot);
}
}
[Fact]
public async Task MigrateFilesystemToAzureAsync_RecordsUploadFailures()
{
var mediaRoot = CreateTempMediaRoot();
try
{
WriteMediaFile(mediaRoot, "1/job-photos/9/photo.png", "photo");
const string relativePath = "1/job-photos/9/photo.png";
var blobService = new Mock<IAzureBlobStorageService>();
blobService.Setup(x => x.ExistsAsync("jobimages", relativePath)).ReturnsAsync(false);
blobService
.Setup(x => x.UploadAsync("jobimages", relativePath, It.IsAny<Stream>(), "image/png"))
.ReturnsAsync((false, "upload failed"));
var service = CreateService(blobService);
var result = await service.MigrateFilesystemToAzureAsync(mediaRoot);
Assert.Equal(1, result.Failed);
Assert.Contains(result.Errors, e => e.Contains("upload failed"));
Assert.Equal(MigrationFileStatus.Failed, result.Files.Single().Status);
}
finally
{
SafeDeleteDirectory(mediaRoot);
}
}
[Fact]
public async Task MigrateFilesystemToAzureAsync_DeletesLocalFileAfterSuccessfulMigration_WhenRequested()
{
var mediaRoot = CreateTempMediaRoot();
try
{
var fullPath = WriteMediaFile(mediaRoot, "1/company-logo.png", "logo");
const string relativePath = "1/company-logo.png";
var blobService = new Mock<IAzureBlobStorageService>();
blobService.Setup(x => x.ExistsAsync("companylogos", relativePath)).ReturnsAsync(false);
blobService
.Setup(x => x.UploadAsync("companylogos", relativePath, It.IsAny<Stream>(), "image/png"))
.ReturnsAsync((true, string.Empty));
var service = CreateService(blobService);
var result = await service.MigrateFilesystemToAzureAsync(mediaRoot, deleteLocalAfterMigration: true);
Assert.Equal(1, result.Migrated);
Assert.Contains(result.Files, f => f.RelativePath == relativePath && f.Status == MigrationFileStatus.Migrated);
Assert.False(File.Exists(fullPath));
}
finally
{
SafeDeleteDirectory(mediaRoot);
}
}
[Fact]
public async Task MigrateFilesystemToAzureAsync_ContinuesAfterPerFileException()
{
var mediaRoot = CreateTempMediaRoot();
try
{
WriteMediaFile(mediaRoot, "1/profile-photos/user.jpg", "profile");
WriteMediaFile(mediaRoot, "1/equipment-manuals/manual.pdf", "manual");
var blobService = new Mock<IAzureBlobStorageService>();
blobService
.Setup(x => x.ExistsAsync("profileimages", "1/profile-photos/user.jpg"))
.ThrowsAsync(new InvalidOperationException("broken exists"));
blobService
.Setup(x => x.ExistsAsync("manuals", "1/equipment-manuals/manual.pdf"))
.ReturnsAsync(false);
blobService
.Setup(x => x.UploadAsync("manuals", "1/equipment-manuals/manual.pdf", It.IsAny<Stream>(), "application/pdf"))
.ReturnsAsync((true, string.Empty));
var service = CreateService(blobService);
var result = await service.MigrateFilesystemToAzureAsync(mediaRoot);
Assert.Equal(1, result.Failed);
Assert.Equal(1, result.Migrated);
Assert.Contains(result.Errors, e => e.Contains("broken exists"));
Assert.Equal(2, result.Total);
}
finally
{
SafeDeleteDirectory(mediaRoot);
}
}
private static StorageMigrationService CreateService(Mock<IAzureBlobStorageService>? blobService = null)
{
var settings = Options.Create(new StorageSettings
{
Containers = new StorageContainers
{
ProfileImages = "profileimages",
JobImages = "jobimages",
Manuals = "manuals",
CompanyLogos = "companylogos"
}
});
return new StorageMigrationService(
(blobService ?? new Mock<IAzureBlobStorageService>()).Object,
settings,
Mock.Of<ILogger<StorageMigrationService>>());
}
private static string CreateTempMediaRoot()
{
var path = Path.Combine(Path.GetTempPath(), "pca-tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(path);
return path;
}
private static string WriteMediaFile(string mediaRoot, string relativePath, string content)
{
var fullPath = Path.Combine(mediaRoot, relativePath.Replace("/", Path.DirectorySeparatorChar.ToString()));
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
File.WriteAllText(fullPath, content);
return fullPath;
}
private static void SafeDeleteDirectory(string path)
{
if (Directory.Exists(path))
{
Directory.Delete(path, recursive: true);
}
}
}