mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-01 23:13:22 +00:00
Dispatchers can attach one evidence file, and create links it only after that document has passed scanning.
219 lines
8 KiB
C#
219 lines
8 KiB
C#
using System.Security.Claims;
|
|
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
using SeaHaven.DataServices.Implementation;
|
|
using SeaHaven.Services.Configuration;
|
|
using SeaHaven.Services.Implementation;
|
|
using SeaHaven.Services.Interfaces;
|
|
using Xunit;
|
|
|
|
namespace SeaHavenIndustries.Tests;
|
|
|
|
public sealed class WorkOrderUpliftEvidenceServiceTests
|
|
{
|
|
private static ApplicationDbContext CreateContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new ApplicationDbContext(options);
|
|
}
|
|
|
|
private static async Task<WorkOrder> SeedWorkOrderAsync(ApplicationDbContext context)
|
|
{
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context);
|
|
context.Users.Add(new ApplicationUser
|
|
{
|
|
Id = "dispatcher-1",
|
|
UserName = "dispatcher-1",
|
|
FirstName = "Alex",
|
|
LastName = "Dispatcher",
|
|
});
|
|
context.Vendors.Add(new Vendor { Id = 1, CompanyName = "Acme HVAC" });
|
|
context.Dispatches.Add(new Dispatch
|
|
{
|
|
Id = 10,
|
|
VendorId = 1,
|
|
WorkOrderId = 1,
|
|
NTEAmount = 1000m,
|
|
DispatchNumber = "DIS-10",
|
|
Status = "Scheduled",
|
|
});
|
|
var workOrder = new WorkOrder
|
|
{
|
|
Id = 1,
|
|
InternalWONumber = "10000000001",
|
|
PrimaryDispatchId = 10,
|
|
AccountId = 1,
|
|
WorkOrderType = WorkOrderType.PM,
|
|
};
|
|
context.workOrders.Add(workOrder);
|
|
await context.SaveChangesAsync();
|
|
return workOrder;
|
|
}
|
|
|
|
private static (WorkOrderUpliftEvidenceService Service, MemoryStorage Storage) NewService(
|
|
ApplicationDbContext context,
|
|
bool passWhenScannerUnavailable = false)
|
|
{
|
|
var storage = new MemoryStorage();
|
|
var service = new WorkOrderUpliftEvidenceService(
|
|
new WorkOrderDetailDataService(context),
|
|
new DispatchDataService(context),
|
|
new VendorDocumentDataService(context),
|
|
storage,
|
|
WorkOrderAccountTestHelpers.Resolver(context),
|
|
Options.Create(new VendorDocumentsOptions
|
|
{
|
|
PassWhenScannerUnavailable = passWhenScannerUnavailable,
|
|
}));
|
|
return (service, storage);
|
|
}
|
|
|
|
private static ClaimsPrincipal Dispatcher()
|
|
=> WorkOrderAccountTestHelpers.OrgWideAdmin("dispatcher-1");
|
|
|
|
private static FormFile FormFile(byte[] bytes, string fileName, string contentType)
|
|
{
|
|
return new FormFile(new MemoryStream(bytes), 0, bytes.Length, "file", fileName)
|
|
{
|
|
Headers = new HeaderDictionary(),
|
|
ContentType = contentType,
|
|
};
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UploadAsync_StoresPendingUpliftEvidenceForThePrimaryDispatch()
|
|
{
|
|
await using var context = CreateContext();
|
|
var workOrder = await SeedWorkOrderAsync(context);
|
|
var (service, storage) = NewService(context);
|
|
var pdf = "%PDF-1.4"u8.ToArray();
|
|
|
|
var uploaded = await service.UploadAsync(
|
|
workOrder.Id,
|
|
FormFile(pdf, "quote.pdf", "application/pdf"),
|
|
Dispatcher(),
|
|
CancellationToken.None);
|
|
|
|
var document = Assert.Single(context.VendorCompletionDocuments);
|
|
Assert.Equal(document.Id, uploaded!.Id);
|
|
Assert.Equal("Pending", uploaded.ScanStatus);
|
|
Assert.Null(document.ScannedAt);
|
|
Assert.Equal(VendorDocumentPurpose.UpliftEvidence, document.Purpose);
|
|
Assert.Equal(1, document.VendorId);
|
|
Assert.Equal(10, document.DispatchId);
|
|
Assert.Equal("quote.pdf", document.OriginalFileName);
|
|
Assert.Null(document.ReplacesDocumentId);
|
|
var saved = Assert.Single(storage.Saved);
|
|
Assert.Equal((1, 10), (saved.VendorId, saved.DispatchId));
|
|
var audit = Assert.Single(context.WorkOrderAuditLogs);
|
|
Assert.Equal("uplift_evidence_uploaded", audit.Action);
|
|
Assert.Equal("internal", audit.ActorType);
|
|
Assert.Equal("dispatcher-1", audit.UserId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UploadAsync_PassWhenScannerUnavailable_StoresPassedEvidence()
|
|
{
|
|
await using var context = CreateContext();
|
|
var workOrder = await SeedWorkOrderAsync(context);
|
|
var (service, _) = NewService(context, passWhenScannerUnavailable: true);
|
|
|
|
var uploaded = await service.UploadAsync(
|
|
workOrder.Id,
|
|
FormFile("%PDF-1.4"u8.ToArray(), "quote.pdf", "application/pdf"),
|
|
Dispatcher(),
|
|
CancellationToken.None);
|
|
|
|
var document = Assert.Single(context.VendorCompletionDocuments);
|
|
Assert.Equal("Passed", uploaded!.ScanStatus);
|
|
Assert.Equal("Passed", document.ScanStatus);
|
|
Assert.NotNull(document.ScannedAt);
|
|
Assert.Equal("Processing", document.ReviewStatus);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UploadAsync_MismatchedSignature_SavesNothing()
|
|
{
|
|
await using var context = CreateContext();
|
|
var workOrder = await SeedWorkOrderAsync(context);
|
|
var (service, storage) = NewService(context);
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
service.UploadAsync(
|
|
workOrder.Id,
|
|
FormFile("not a pdf"u8.ToArray(), "quote.pdf", "application/pdf"),
|
|
Dispatcher(),
|
|
CancellationToken.None));
|
|
|
|
Assert.Contains("signature", ex.Message, StringComparison.OrdinalIgnoreCase);
|
|
Assert.Empty(context.VendorCompletionDocuments);
|
|
Assert.Empty(storage.Saved);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetStatusAsync_ReturnsScanStatusOnlyForUpliftEvidenceOnTheDispatch()
|
|
{
|
|
await using var context = CreateContext();
|
|
var workOrder = await SeedWorkOrderAsync(context);
|
|
var (service, _) = NewService(context);
|
|
var uploaded = await service.UploadAsync(
|
|
workOrder.Id,
|
|
FormFile("%PDF-1.4"u8.ToArray(), "quote.pdf", "application/pdf"),
|
|
Dispatcher(),
|
|
CancellationToken.None);
|
|
|
|
var pending = await service.GetStatusAsync(workOrder.Id, uploaded!.Id, Dispatcher(), CancellationToken.None);
|
|
Assert.Equal("Pending", pending!.ScanStatus);
|
|
Assert.Equal("quote.pdf", pending.OriginalFileName);
|
|
|
|
var stored = Assert.Single(context.VendorCompletionDocuments);
|
|
stored.ScanStatus = "Passed";
|
|
context.VendorCompletionDocuments.Add(new VendorCompletionDocument
|
|
{
|
|
Id = 90,
|
|
VendorId = 1,
|
|
DispatchId = 10,
|
|
WorkOrderId = workOrder.Id,
|
|
OriginalFileName = "done.pdf",
|
|
StoredFileName = "done.pdf",
|
|
ContentType = "application/pdf",
|
|
Purpose = "Completion",
|
|
ScanStatus = "Passed",
|
|
Version = 1,
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var passed = await service.GetStatusAsync(workOrder.Id, uploaded.Id, Dispatcher(), CancellationToken.None);
|
|
Assert.Equal("Passed", passed!.ScanStatus);
|
|
Assert.Null(await service.GetStatusAsync(workOrder.Id, 90, Dispatcher(), CancellationToken.None));
|
|
}
|
|
|
|
private sealed class MemoryStorage : IVendorDocumentStoragePort
|
|
{
|
|
public List<(int VendorId, int DispatchId, string Name)> Saved { get; } = new();
|
|
|
|
public async Task SaveAsync(
|
|
int vendorId,
|
|
int dispatchId,
|
|
string storedFileName,
|
|
Stream content,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
using var buffer = new MemoryStream();
|
|
await content.CopyToAsync(buffer, cancellationToken);
|
|
Saved.Add((vendorId, dispatchId, storedFileName));
|
|
}
|
|
|
|
public Stream OpenRead(int vendorId, int dispatchId, string storedFileName)
|
|
=> throw new NotSupportedException();
|
|
|
|
public void Delete(int vendorId, int dispatchId, string storedFileName)
|
|
{
|
|
}
|
|
}
|
|
}
|