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>
240 lines
9.2 KiB
C#
240 lines
9.2 KiB
C#
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);
|
|
}
|
|
}
|