mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
Close the remaining SH-221 bypass: board/legacy comments and completion-doc now enforce server-derived account scope, authorize before blob storage, and cover cross-account regressions.
639 lines
24 KiB
C#
639 lines
24 KiB
C#
using System.Security.Claims;
|
|
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Implementation;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Exceptions;
|
|
using SeaHaven.Services.Helpers;
|
|
using SeaHaven.Services.Implementation;
|
|
using SeaHaven.Services.Validation;
|
|
using Xunit;
|
|
|
|
namespace SeaHavenIndustries.Tests;
|
|
|
|
public class WorkOrderAccountScopeTests
|
|
{
|
|
private static ApplicationDbContext CreateContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new ApplicationDbContext(options);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BoardCreate_WithAccountClaim_StampsAccountId()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 7, "Scoped Co");
|
|
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardData = new WorkOrderBoardDataService(context);
|
|
var mutationData = new WorkOrderBoardMutationDataService(context);
|
|
var boardService = new WorkOrderBoardService(boardData, resolver);
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var create = new WorkOrderBoardCreateService(
|
|
boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver);
|
|
|
|
var user = WorkOrderAccountTestHelpers.AccountUser("actor-1", 7, "Dispatcher");
|
|
var row = await create.CreateAsync(
|
|
new WorkOrderBoardCreateRequestDto
|
|
{
|
|
WorkOrderType = WorkOrderType.PM,
|
|
SiteCode = "BK5"
|
|
},
|
|
user,
|
|
"actor-1");
|
|
|
|
var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == row.Id);
|
|
Assert.Equal(7, wo.AccountId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BoardCreate_MissingScope_ThrowsForbidden()
|
|
{
|
|
await using var context = CreateContext();
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardData = new WorkOrderBoardDataService(context);
|
|
var mutationData = new WorkOrderBoardMutationDataService(context);
|
|
var boardService = new WorkOrderBoardService(boardData, resolver);
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var create = new WorkOrderBoardCreateService(
|
|
boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver);
|
|
|
|
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
create.CreateAsync(
|
|
new WorkOrderBoardCreateRequestDto
|
|
{
|
|
WorkOrderType = WorkOrderType.PM,
|
|
SiteCode = "BK5"
|
|
},
|
|
WorkOrderAccountTestHelpers.MissingScope(),
|
|
"actor-1"));
|
|
|
|
Assert.Equal("Forbidden", ex.Code);
|
|
Assert.Empty(context.workOrders);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BoardCreate_OrgWide_WithUniqueCustomer_StampsAccountId()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 3, "Unique Customer");
|
|
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardData = new WorkOrderBoardDataService(context);
|
|
var mutationData = new WorkOrderBoardMutationDataService(context);
|
|
var boardService = new WorkOrderBoardService(boardData, resolver);
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var create = new WorkOrderBoardCreateService(
|
|
boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver);
|
|
|
|
var row = await create.CreateAsync(
|
|
new WorkOrderBoardCreateRequestDto
|
|
{
|
|
WorkOrderType = WorkOrderType.PO,
|
|
SiteCode = "DAL",
|
|
Customer = "Unique Customer"
|
|
},
|
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
|
"admin-1");
|
|
|
|
var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == row.Id);
|
|
Assert.Equal(3, wo.AccountId);
|
|
Assert.Equal("Unique Customer", wo.Customer);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BoardCreate_OrgWide_UnresolvedCustomer_ThrowsAccountUnresolved()
|
|
{
|
|
await using var context = CreateContext();
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardData = new WorkOrderBoardDataService(context);
|
|
var mutationData = new WorkOrderBoardMutationDataService(context);
|
|
var boardService = new WorkOrderBoardService(boardData, resolver);
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var create = new WorkOrderBoardCreateService(
|
|
boardData, mutationData, boardService, audit, new WorkOrderBoardCreateValidation(), resolver);
|
|
|
|
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
create.CreateAsync(
|
|
new WorkOrderBoardCreateRequestDto
|
|
{
|
|
WorkOrderType = WorkOrderType.PM,
|
|
SiteCode = "BK5"
|
|
},
|
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
|
"admin-1"));
|
|
|
|
Assert.Equal("AccountUnresolved", ex.Code);
|
|
Assert.Empty(context.workOrders);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Board_ScopedUser_HidesOtherAccountAndNullAccountRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
InternalWONumber = "00000000001",
|
|
AccountId = 1,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
ScheduledDate = new DateTime(2026, 8, 10),
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
InternalWONumber = "00000000002",
|
|
AccountId = 2,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
ScheduledDate = new DateTime(2026, 8, 11),
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
InternalWONumber = "00000000003",
|
|
AccountId = null,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
ScheduledDate = new DateTime(2026, 8, 12),
|
|
istemplate = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardService = new WorkOrderBoardService(new WorkOrderBoardDataService(context), resolver);
|
|
|
|
var scoped = await boardService.GetBoardAsync(
|
|
new WorkOrderBoardQueryDto
|
|
{
|
|
WeekStart = new DateOnly(2026, 8, 10),
|
|
WeekEnd = new DateOnly(2026, 8, 14)
|
|
},
|
|
WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"),
|
|
"disp-1");
|
|
|
|
Assert.Single(scoped.Scheduled);
|
|
Assert.Equal(1, scoped.Scheduled[0].Id);
|
|
|
|
var orgWide = await boardService.GetBoardAsync(
|
|
new WorkOrderBoardQueryDto
|
|
{
|
|
WeekStart = new DateOnly(2026, 8, 10),
|
|
WeekEnd = new DateOnly(2026, 8, 14)
|
|
},
|
|
WorkOrderAccountTestHelpers.OrgWideAdmin(),
|
|
"admin-1");
|
|
|
|
Assert.Equal(3, orgWide.Scheduled.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Detail_CrossAccount_ReturnsNull()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
|
|
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 10,
|
|
InternalWONumber = "00000000010",
|
|
AccountId = 2,
|
|
LifecycleStatus = LifecycleStatus.Incomplete,
|
|
istemplate = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardService = new WorkOrderBoardService(new WorkOrderBoardDataService(context), resolver);
|
|
var detail = new WorkOrderDetailService(
|
|
boardService,
|
|
new WorkOrderDetailDataService(context),
|
|
new CompletionDocTemplateDataService(context),
|
|
new UserDataService(context),
|
|
resolver);
|
|
|
|
var result = await detail.GetDetailAsync(
|
|
10,
|
|
WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"));
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Ingest_UnresolvedCustomer_SkipsCreate()
|
|
{
|
|
await using var context = CreateContext();
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var ingest = new WorkOrderIngestService(
|
|
new WorkOrderIngestDataService(context),
|
|
new SyncFieldMergePolicy(fieldLocks),
|
|
fieldLocks,
|
|
audit,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await ingest.UpsertBatchAsync(new[]
|
|
{
|
|
new WorkOrderIngestPayloadDto
|
|
{
|
|
ExternalWorkOrderId = "EXT-1",
|
|
Description = "No account",
|
|
Customer = "Unknown Customer"
|
|
}
|
|
});
|
|
|
|
Assert.Equal(1, result.Skipped);
|
|
Assert.Equal(0, result.Created);
|
|
Assert.Empty(context.workOrders);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Ingest_UniqueCustomer_StampsAccountId()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 9, "Ingest Customer");
|
|
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var ingest = new WorkOrderIngestService(
|
|
new WorkOrderIngestDataService(context),
|
|
new SyncFieldMergePolicy(fieldLocks),
|
|
fieldLocks,
|
|
audit,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var result = await ingest.UpsertBatchAsync(new[]
|
|
{
|
|
new WorkOrderIngestPayloadDto
|
|
{
|
|
ExternalWorkOrderId = "EXT-9",
|
|
Description = "Has account",
|
|
Customer = "Ingest Customer"
|
|
}
|
|
});
|
|
|
|
Assert.Equal(1, result.Created);
|
|
var wo = Assert.Single(context.workOrders);
|
|
Assert.Equal(9, wo.AccountId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveAccountFilter_MissingScope_ThrowsForbidden()
|
|
{
|
|
await using var context = CreateContext();
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
|
|
var ex = Assert.Throws<WorkOrderBoardValidationException>(() =>
|
|
resolver.ResolveAccountFilter(WorkOrderAccountTestHelpers.MissingScope()));
|
|
|
|
Assert.Equal("Forbidden", ex.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyList_ScopedUser_HidesOtherAccountAndNullAccountRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedThreeAccountRowsAsync(context);
|
|
|
|
var data = new WorkOrderDataService(context);
|
|
var scoped = (await data.GetAllWithDetailsAsync(accountId: 1)).ToList();
|
|
var dd = await data.GetNonTemplateWorkOrdersWithLocationsAsync(accountId: 1);
|
|
|
|
Assert.Single(scoped);
|
|
Assert.Equal(1, scoped[0].Id);
|
|
Assert.Single(dd);
|
|
Assert.Equal(1, dd[0].Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyList_OrgWide_IncludesNullAccountRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedThreeAccountRowsAsync(context);
|
|
|
|
var data = new WorkOrderDataService(context);
|
|
var orgWide = (await data.GetAllWithDetailsAsync(accountId: null)).ToList();
|
|
|
|
Assert.Equal(3, orgWide.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyDetail_CrossAccount_ReturnsNull()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedThreeAccountRowsAsync(context);
|
|
|
|
var data = new WorkOrderDataService(context);
|
|
var detail = await data.GetWorkOrderDetailAsync(2, accountId: 1);
|
|
|
|
Assert.Null(detail);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyDetail_SameAccount_ReturnsRow()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedThreeAccountRowsAsync(context);
|
|
|
|
var data = new WorkOrderDataService(context);
|
|
var detail = await data.GetWorkOrderDetailAsync(1, accountId: 1);
|
|
|
|
Assert.NotNull(detail);
|
|
Assert.Equal(1, detail!.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyDetail_OrgWide_CanReadNullAccountRow()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedThreeAccountRowsAsync(context);
|
|
|
|
var data = new WorkOrderDataService(context);
|
|
var detail = await data.GetWorkOrderDetailAsync(3, accountId: null);
|
|
|
|
Assert.NotNull(detail);
|
|
Assert.Equal(3, detail!.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyFiltered_ScopedUser_HidesOtherAccountAndNullAccountRows()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedThreeAccountRowsAsync(context);
|
|
|
|
var service = CreateLegacyReadService(context);
|
|
var page = await service.GetFilteredWorkOrdersAsync(
|
|
Array.Empty<string>(), null, null, null, null,
|
|
search: "", sort: "", sortby: "", page: 1, pageSize: 50, accountId: 1);
|
|
|
|
Assert.Equal(1, page.TotalCount);
|
|
Assert.Single(page.Data);
|
|
Assert.Equal(1, page.Data.First().Id);
|
|
}
|
|
|
|
private static WorkOrderService CreateLegacyReadService(ApplicationDbContext context)
|
|
{
|
|
return new WorkOrderService(
|
|
new WorkOrderDataService(context),
|
|
new CommentDataService(context),
|
|
new UserDataService(context),
|
|
new QuotesDataService(context),
|
|
fileStorage: null!,
|
|
new CreateWorkOrderValidation(),
|
|
new UpdateWorkOrderValidation(),
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
}
|
|
|
|
private static async Task SeedThreeAccountRowsAsync(ApplicationDbContext context)
|
|
{
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
|
|
|
|
context.workOrders.AddRange(
|
|
new WorkOrder
|
|
{
|
|
Id = 1,
|
|
InternalWONumber = "00000000001",
|
|
AccountId = 1,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 2,
|
|
InternalWONumber = "00000000002",
|
|
AccountId = 2,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
istemplate = false
|
|
},
|
|
new WorkOrder
|
|
{
|
|
Id = 3,
|
|
InternalWONumber = "00000000003",
|
|
AccountId = null,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
istemplate = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Comments_CrossAccount_GetAddUpdate_ReturnNotFound()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
|
|
|
|
context.Users.Add(new ApplicationUser
|
|
{
|
|
Id = "author-1",
|
|
UserName = "author",
|
|
FirstName = "Ann",
|
|
LastName = "Author"
|
|
});
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 20,
|
|
InternalWONumber = "00000000020",
|
|
AccountId = 2,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
istemplate = false
|
|
});
|
|
context.Comments.Add(new Comments
|
|
{
|
|
Id = 5,
|
|
WorkerOrderId = 20,
|
|
UserId = "author-1",
|
|
Commenttext = "Secret",
|
|
CommentType = "General",
|
|
RecordType = "WorkOrder",
|
|
CreatedDate = DateTime.UtcNow
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new WorkOrderCommentService(
|
|
new WorkOrderDetailDataService(context),
|
|
new CommentDataService(context),
|
|
new UserDataService(context),
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher");
|
|
|
|
Assert.Null(await service.GetCommentsAsync(20, scoped));
|
|
|
|
var addEx = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
service.AddCommentAsync(20, new WorkOrderCommentCreateDto { Text = "Nope" }, scoped, "disp-1"));
|
|
Assert.Equal("NotFound", addEx.Code);
|
|
|
|
var updateEx = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
service.UpdateCommentAsync(
|
|
20, 5, new WorkOrderCommentCreateDto { Text = "Nope" }, scoped, "author-1"));
|
|
Assert.Equal("NotFound", updateEx.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Comments_SameAccount_AddSucceeds()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
|
|
context.Users.Add(new ApplicationUser
|
|
{
|
|
Id = "disp-1",
|
|
UserName = "disp",
|
|
FirstName = "Dee",
|
|
LastName = "Spatch"
|
|
});
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 21,
|
|
InternalWONumber = "00000000021",
|
|
AccountId = 1,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
istemplate = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = new WorkOrderCommentService(
|
|
new WorkOrderDetailDataService(context),
|
|
new CommentDataService(context),
|
|
new UserDataService(context),
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher");
|
|
var result = await service.AddCommentAsync(
|
|
21, new WorkOrderCommentCreateDto { Text = "In scope" }, scoped, "disp-1");
|
|
|
|
Assert.Equal("In scope", result.Text);
|
|
Assert.Equal("disp-1", result.AuthorId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LegacyCommentsByWorkOrder_CrossAccount_ReturnsNull()
|
|
{
|
|
await using var context = CreateContext();
|
|
await SeedThreeAccountRowsAsync(context);
|
|
context.Comments.Add(new Comments
|
|
{
|
|
WorkerOrderId = 2,
|
|
Commenttext = "Other account",
|
|
CreatedDate = DateTime.UtcNow
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var service = CreateLegacyReadService(context);
|
|
var result = await service.GetCommentsByWorkorderIdAsync(
|
|
2, WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher"));
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CompletionDoc_CrossAccount_EnsureAndUpload_ThrowNotFound()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 2, "B");
|
|
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 30,
|
|
InternalWONumber = "00000000030",
|
|
AccountId = 2,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
DocStatus = DocStatus.No,
|
|
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 },
|
|
istemplate = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var service = new WorkOrderCompletionService(
|
|
new WorkOrderCompletionDataService(context),
|
|
new CompletionDocTemplateDataService(context),
|
|
new WorkOrderDetailDataService(context),
|
|
audit,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher");
|
|
|
|
var ensureEx = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
service.EnsureCanUploadCompletionDocAsync(30, scoped, "disp-1"));
|
|
Assert.Equal("NotFound", ensureEx.Code);
|
|
|
|
var uploadEx = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
|
|
service.UploadCompletionDocAsync(
|
|
30,
|
|
new WorkOrderCompletionDocUploadDto
|
|
{
|
|
WorkOrderVersion = Convert.ToBase64String(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 })
|
|
},
|
|
"https://example.com/should-not-stick.pdf",
|
|
scoped,
|
|
"disp-1"));
|
|
Assert.Equal("NotFound", uploadEx.Code);
|
|
|
|
var wo = await context.workOrders.AsNoTracking().SingleAsync(w => w.Id == 30);
|
|
Assert.Null(wo.SignOffAttachment);
|
|
Assert.Equal(DocStatus.No, wo.DocStatus);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CompletionDoc_SameAccount_EnsureAllowsUpload()
|
|
{
|
|
await using var context = CreateContext();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context, 1, "A");
|
|
|
|
var rowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 };
|
|
context.workOrders.Add(new WorkOrder
|
|
{
|
|
Id = 31,
|
|
InternalWONumber = "00000000031",
|
|
AccountId = 1,
|
|
LifecycleStatus = LifecycleStatus.Scheduled,
|
|
DocStatus = DocStatus.No,
|
|
RowVersion = rowVersion,
|
|
istemplate = false
|
|
});
|
|
await context.SaveChangesAsync();
|
|
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var service = new WorkOrderCompletionService(
|
|
new WorkOrderCompletionDataService(context),
|
|
new CompletionDocTemplateDataService(context),
|
|
new WorkOrderDetailDataService(context),
|
|
audit,
|
|
WorkOrderAccountTestHelpers.Resolver(context));
|
|
|
|
var scoped = WorkOrderAccountTestHelpers.AccountUser("disp-1", 1, "Dispatcher");
|
|
await service.EnsureCanUploadCompletionDocAsync(31, scoped, "disp-1");
|
|
|
|
var result = await service.UploadCompletionDocAsync(
|
|
31,
|
|
new WorkOrderCompletionDocUploadDto
|
|
{
|
|
WorkOrderVersion = Convert.ToBase64String(rowVersion)
|
|
},
|
|
"https://example.com/ok.pdf",
|
|
scoped,
|
|
"disp-1");
|
|
|
|
Assert.Equal(DocStatus.Yes, result.DocStatus);
|
|
Assert.Equal("https://example.com/ok.pdf", result.SignOffAttachment);
|
|
}
|
|
}
|