dbe4170986
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>
214 lines
7.5 KiB
C#
214 lines
7.5 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|