feat(1099): wire W-9 document upload/view for recipients
Adds POST/GET payee-1099/{id}/w9, mirroring the expense-receipt upload:
IFileStorage saves to finance/w9/{id}{ext}, content-type derived from the
blob extension. Frontend dialog (edit mode) gains a W-9 file input and an
auth-correct blob "View W-9" link. Payee1099Service ctor now takes
IFileStorage; tests updated with an in-memory FakeStorage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,12 +10,24 @@ using ROLAC.API.Entities;
|
||||
using ROLAC.API.Services;
|
||||
using ROLAC.API.Services.Logging;
|
||||
using ROLAC.API.Services.Security;
|
||||
using ROLAC.API.Services.Storage;
|
||||
using Xunit;
|
||||
|
||||
namespace ROLAC.API.Tests.Services;
|
||||
|
||||
public class Payee1099ServiceTests
|
||||
{
|
||||
// Minimal in-memory IFileStorage (mirrors the ExpenseServiceTests fake).
|
||||
private sealed class FakeStorage : IFileStorage
|
||||
{
|
||||
public Dictionary<string, byte[]> Files = new();
|
||||
public Task<string> SaveAsync(Stream c, string p, CancellationToken ct = default)
|
||||
{ using var ms = new MemoryStream(); c.CopyTo(ms); Files[p] = ms.ToArray(); return Task.FromResult(p); }
|
||||
public Task<Stream?> OpenReadAsync(string p, CancellationToken ct = default)
|
||||
=> Task.FromResult<Stream?>(Files.TryGetValue(p, out var b) ? new MemoryStream(b) : null);
|
||||
public Task DeleteAsync(string p, CancellationToken ct = default) { Files.Remove(p); return Task.CompletedTask; }
|
||||
}
|
||||
|
||||
private static (Payee1099Service svc, AppDbContext db) Build()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext { User = new(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "test-user") })) };
|
||||
@@ -25,7 +37,7 @@ public class Payee1099ServiceTests
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.AddInterceptors(new AuditSaveChangesInterceptor(new CurrentUserAccessor(accessorMock.Object))).Options);
|
||||
var tin = new TinProtector(DataProtectionProvider.Create("ROLAC.Tests"));
|
||||
return (new Payee1099Service(db, tin), db);
|
||||
return (new Payee1099Service(db, tin, new FakeStorage()), db);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -76,4 +88,25 @@ public class Payee1099ServiceTests
|
||||
await svc.DeleteAsync(id);
|
||||
Assert.Empty(await svc.GetAllAsync(includeInactive: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveW9_records_document_and_round_trips_bytes()
|
||||
{
|
||||
var (svc, _) = Build();
|
||||
var id = await svc.CreateAsync(new SavePayee1099Request { LegalName = "W9 Payee" });
|
||||
|
||||
var bytes = new byte[] { 1, 2, 3, 4, 5 };
|
||||
await svc.SaveW9Async(id, new MemoryStream(bytes), "w9.pdf");
|
||||
|
||||
var dto = await svc.GetByIdAsync(id);
|
||||
Assert.NotNull(dto);
|
||||
Assert.True(dto!.HasW9Document);
|
||||
|
||||
var opened = await svc.OpenW9Async(id);
|
||||
Assert.NotNull(opened);
|
||||
Assert.Equal("application/pdf", opened!.Value.contentType);
|
||||
using var ms = new MemoryStream();
|
||||
await opened.Value.stream.CopyToAsync(ms);
|
||||
Assert.Equal(bytes, ms.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user