fix(vendors): address roadmap review findings

This commit is contained in:
Alexandre Brandizzi 2026-07-24 11:49:57 -03:00
parent 39829f7274
commit b1421de0b5
5 changed files with 49 additions and 6 deletions

View file

@ -124,6 +124,36 @@ public sealed class VendorPortalDocumentTests : IDisposable
locked.StatusCode.Should().Be(StatusCodes.Status423Locked); locked.StatusCode.Should().Be(StatusCodes.Status423Locked);
} }
[Fact]
public async Task UploadCompletionDocument_AcceptsMixedCasePdfContentType()
{
using var context = NewContext();
var (_, dispatch) = await SeedDispatch(context);
var pdf = "%PDF-1.4\nvendor completion"u8.ToArray();
var result = await NewController(context).UploadCompletionDocument(
dispatch.Id,
FormFile(pdf, "completion.pdf", "Application/PDF"));
result.Should().BeOfType<OkObjectResult>();
context.VendorCompletionDocuments.Should().HaveCount(1);
}
[Fact]
public async Task UploadCompletionDocument_AcceptsMixedCaseImageContentType()
{
using var context = NewContext();
var (_, dispatch) = await SeedDispatch(context);
var png = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00 };
var result = await NewController(context).UploadCompletionDocument(
dispatch.Id,
FormFile(png, "photo.png", "Image/PNG"));
result.Should().BeOfType<OkObjectResult>();
context.VendorCompletionDocuments.Should().HaveCount(1);
}
public void Dispose() public void Dispose()
{ {
if (Directory.Exists(_contentRoot)) if (Directory.Exists(_contentRoot))

View file

@ -803,11 +803,11 @@ namespace Api.SeaHavenIndustries.Controllers
{ {
var bytes = new byte[8]; var bytes = new byte[8];
var count = await stream.ReadAsync(bytes); var count = await stream.ReadAsync(bytes);
if (contentType == "application/pdf") if (string.Equals(contentType, "application/pdf", StringComparison.OrdinalIgnoreCase))
return count >= 5 && bytes[0] == 0x25 && bytes[1] == 0x50 && bytes[2] == 0x44 && bytes[3] == 0x46 && bytes[4] == 0x2D; return count >= 5 && bytes[0] == 0x25 && bytes[1] == 0x50 && bytes[2] == 0x44 && bytes[3] == 0x46 && bytes[4] == 0x2D;
if (contentType == "image/jpeg") if (string.Equals(contentType, "image/jpeg", StringComparison.OrdinalIgnoreCase))
return count >= 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF; return count >= 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF;
if (contentType == "image/png") if (string.Equals(contentType, "image/png", StringComparison.OrdinalIgnoreCase))
return count >= 8 && bytes.SequenceEqual(new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }); return count >= 8 && bytes.SequenceEqual(new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A });
return false; return false;
} }

View file

@ -14,6 +14,12 @@
// Provide the real value via environment variable SendGrid__ApiKey or the instance secret store. // Provide the real value via environment variable SendGrid__ApiKey or the instance secret store.
"ApiKey": "${SENDGRID_API_KEY}" "ApiKey": "${SENDGRID_API_KEY}"
}, },
// Production must supply a reachable ClamAV Host (environment variable ClamAV__Host).
// While Host is empty the scanner is unavailable and uploads stay quarantined (423 Locked).
"ClamAV": {
"Host": "",
"Port": 3310
},
"AllowedHosts": "*", "AllowedHosts": "*",
"JWT": { "JWT": {
"ValidAudience": "http://console.seahavenind.com", "ValidAudience": "http://console.seahavenind.com",

View file

@ -17,6 +17,13 @@
// Provide the real value via environment variable SendGrid__ApiKey or user-secrets. // Provide the real value via environment variable SendGrid__ApiKey or user-secrets.
"ApiKey": "${SENDGRID_API_KEY}" "ApiKey": "${SENDGRID_API_KEY}"
}, },
// Malware scanner for vendor completion document uploads. While Host is empty the
// scanner is considered unavailable and uploaded files stay quarantined (download
// returns 423 Locked until a scan passes). Production must supply a real Host.
"ClamAV": {
"Host": "",
"Port": 3310
},
"AllowedHosts": "*", "AllowedHosts": "*",
"JWT": { "JWT": {
"ValidAudience": "http://localhost:4200", "ValidAudience": "http://localhost:4200",

View file

@ -16,7 +16,7 @@ namespace Data.SeaHavenIndustries.Migrations
table: "Vendors", table: "Vendors",
type: "nvarchar(max)", type: "nvarchar(max)",
nullable: false, nullable: false,
defaultValue: ""); defaultValue: "Unknown");
migrationBuilder.AddColumn<DateTime>( migrationBuilder.AddColumn<DateTime>(
name: "AvailabilityUpdatedAt", name: "AvailabilityUpdatedAt",
@ -54,14 +54,14 @@ namespace Data.SeaHavenIndustries.Migrations
table: "Dispatches", table: "Dispatches",
type: "nvarchar(max)", type: "nvarchar(max)",
nullable: false, nullable: false,
defaultValue: ""); defaultValue: "Not Submitted");
migrationBuilder.AddColumn<string>( migrationBuilder.AddColumn<string>(
name: "PaymentStatus", name: "PaymentStatus",
table: "Dispatches", table: "Dispatches",
type: "nvarchar(max)", type: "nvarchar(max)",
nullable: false, nullable: false,
defaultValue: ""); defaultValue: "Unavailable");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "SitePreferredVendors", name: "SitePreferredVendors",