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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user