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,239 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
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 QuotePhotoServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SaveTempPhotoAsync_ReturnsError_WhenFileMissing()
|
||||
{
|
||||
var service = CreateService();
|
||||
|
||||
var result = await service.SaveTempPhotoAsync(null!, companyId: 1);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("No file provided.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveTempPhotoAsync_ReturnsError_WhenFileTooLarge()
|
||||
{
|
||||
var service = CreateService();
|
||||
var file = CreateFormFile("huge.jpg", 10 * 1024 * 1024 + 1);
|
||||
|
||||
var result = await service.SaveTempPhotoAsync(file, companyId: 1);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("File exceeds the 10 MB limit.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveTempPhotoAsync_ReturnsError_WhenExtensionNotAllowed()
|
||||
{
|
||||
var service = CreateService();
|
||||
var file = CreateFormFile("photo.bmp");
|
||||
|
||||
var result = await service.SaveTempPhotoAsync(file, companyId: 1);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("File type '.bmp' is not allowed.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveTempPhotoAsync_ReturnsBlobError_WhenUploadFails()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.UploadAsync("quoteimages", It.IsAny<string>(), It.IsAny<Stream>(), "image/png"))
|
||||
.ReturnsAsync((false, "blob upload failed"));
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
var file = CreateFormFile("photo.png");
|
||||
|
||||
var result = await service.SaveTempPhotoAsync(file, companyId: 1);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("blob upload failed", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveTempPhotoAsync_UploadsToTempPath_WhenValid()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.UploadAsync("quoteimages", It.IsAny<string>(), It.IsAny<Stream>(), "image/jpeg"))
|
||||
.ReturnsAsync((true, string.Empty));
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
var file = CreateFormFile("photo.jpg");
|
||||
|
||||
var result = await service.SaveTempPhotoAsync(file, companyId: 5);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.False(string.IsNullOrWhiteSpace(result.TempId));
|
||||
Assert.StartsWith($"temp/{result.TempId}/", result.FilePath);
|
||||
Assert.EndsWith(".jpg", result.FilePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PromoteTempPhotoAsync_ReturnsError_WhenTempPhotoMissing()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.ListBlobsByPrefixAsync("quoteimages", "temp/temp123/"))
|
||||
.ReturnsAsync(Array.Empty<string>());
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
|
||||
var result = await service.PromoteTempPhotoAsync("temp123", quoteId: 10, companyId: 3);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("Temp photo not found.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PromoteTempPhotoAsync_ReturnsError_WhenTempDownloadFails()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.ListBlobsByPrefixAsync("quoteimages", "temp/temp123/"))
|
||||
.ReturnsAsync(new[] { "temp/temp123/original.png" });
|
||||
blobService
|
||||
.Setup(x => x.DownloadAsync("quoteimages", "temp/temp123/original.png"))
|
||||
.ReturnsAsync((false, Array.Empty<byte>(), string.Empty, "download failed"));
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
|
||||
var result = await service.PromoteTempPhotoAsync("temp123", quoteId: 10, companyId: 3);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("Failed to read temp photo.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PromoteTempPhotoAsync_ReturnsError_WhenPermanentUploadFails()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.ListBlobsByPrefixAsync("quoteimages", "temp/temp123/"))
|
||||
.ReturnsAsync(new[] { "temp/temp123/original.webp" });
|
||||
blobService
|
||||
.Setup(x => x.DownloadAsync("quoteimages", "temp/temp123/original.webp"))
|
||||
.ReturnsAsync((true, new byte[] { 1, 2, 3 }, "image/webp", string.Empty));
|
||||
blobService
|
||||
.Setup(x => x.UploadAsync("quoteimages", It.IsAny<string>(), It.IsAny<Stream>(), "image/webp"))
|
||||
.ReturnsAsync((false, "upload failed"));
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
|
||||
var result = await service.PromoteTempPhotoAsync("temp123", quoteId: 10, companyId: 3);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("Failed to save permanent photo.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PromoteTempPhotoAsync_PromotesAndDeletesTempBlob_WhenSuccessful()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.ListBlobsByPrefixAsync("quoteimages", "temp/temp123/"))
|
||||
.ReturnsAsync(new[] { "temp/temp123/original.png" });
|
||||
blobService
|
||||
.Setup(x => x.DownloadAsync("quoteimages", "temp/temp123/original.png"))
|
||||
.ReturnsAsync((true, new byte[] { 1, 2, 3 }, "image/png", string.Empty));
|
||||
blobService
|
||||
.Setup(x => x.UploadAsync("quoteimages", It.IsAny<string>(), It.IsAny<Stream>(), "image/png"))
|
||||
.ReturnsAsync((true, string.Empty));
|
||||
blobService
|
||||
.Setup(x => x.DeleteAsync("quoteimages", "temp/temp123/original.png"))
|
||||
.ReturnsAsync((true, string.Empty));
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
|
||||
var result = await service.PromoteTempPhotoAsync("temp123", quoteId: 10, companyId: 3);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.StartsWith("3/quote-photos/10/", result.FilePath);
|
||||
Assert.EndsWith(".png", result.FilePath);
|
||||
blobService.Verify(x => x.DeleteAsync("quoteimages", "temp/temp123/original.png"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadTempPhotosAsync_ReturnsOnlySuccessfulDownloads()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.ListBlobsByPrefixAsync("quoteimages", "temp/temp123/"))
|
||||
.ReturnsAsync(new[] { "temp/temp123/one.jpg", "temp/temp123/two.jpg" });
|
||||
blobService
|
||||
.Setup(x => x.DownloadAsync("quoteimages", "temp/temp123/one.jpg"))
|
||||
.ReturnsAsync((true, new byte[] { 1 }, "image/jpeg", string.Empty));
|
||||
blobService
|
||||
.Setup(x => x.DownloadAsync("quoteimages", "temp/temp123/two.jpg"))
|
||||
.ReturnsAsync((false, Array.Empty<byte>(), string.Empty, "failed"));
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
|
||||
var result = await service.ReadTempPhotosAsync("temp123");
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal("one.jpg", result[0].FileName);
|
||||
Assert.Equal("image/jpeg", result[0].ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CleanupTempAsync_ContinuesDeleting_WhenOneDeleteThrows()
|
||||
{
|
||||
var blobService = new Mock<IAzureBlobStorageService>();
|
||||
blobService
|
||||
.Setup(x => x.ListBlobsByPrefixAsync("quoteimages", "temp/temp123/"))
|
||||
.ReturnsAsync(new[] { "temp/temp123/one.jpg", "temp/temp123/two.jpg" });
|
||||
blobService
|
||||
.Setup(x => x.DeleteAsync("quoteimages", "temp/temp123/one.jpg"))
|
||||
.ThrowsAsync(new InvalidOperationException("boom"));
|
||||
blobService
|
||||
.Setup(x => x.DeleteAsync("quoteimages", "temp/temp123/two.jpg"))
|
||||
.ReturnsAsync((true, string.Empty));
|
||||
|
||||
var service = CreateService(blobService: blobService);
|
||||
|
||||
await service.CleanupTempAsync("temp123");
|
||||
|
||||
blobService.Verify(x => x.DeleteAsync("quoteimages", "temp/temp123/one.jpg"), Times.Once);
|
||||
blobService.Verify(x => x.DeleteAsync("quoteimages", "temp/temp123/two.jpg"), Times.Once);
|
||||
}
|
||||
|
||||
private static QuotePhotoService CreateService(Mock<IAzureBlobStorageService>? blobService = null)
|
||||
{
|
||||
var settings = Options.Create(new StorageSettings
|
||||
{
|
||||
Containers = new StorageContainers
|
||||
{
|
||||
QuoteImages = "quoteimages"
|
||||
}
|
||||
});
|
||||
|
||||
return new QuotePhotoService(
|
||||
(blobService ?? new Mock<IAzureBlobStorageService>()).Object,
|
||||
settings,
|
||||
Mock.Of<ILogger<QuotePhotoService>>());
|
||||
}
|
||||
|
||||
private static IFormFile CreateFormFile(string fileName, long? lengthOverride = null)
|
||||
{
|
||||
var dataLength = lengthOverride.HasValue
|
||||
? (int)Math.Min(lengthOverride.Value, 1024)
|
||||
: 16;
|
||||
var bytes = Enumerable.Repeat((byte)65, dataLength).ToArray();
|
||||
var stream = new MemoryStream(bytes);
|
||||
|
||||
return new FormFile(stream, 0, lengthOverride ?? bytes.Length, "file", fileName);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user