shoc-backend/SeaHavenIndustries.Tests/WorkOrderCompletedSelectiveLockTests.cs

363 lines
12 KiB
C#

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;
namespace SeaHavenIndustries.Tests;
public class WorkOrderCompletedSelectiveLockTests
{
private static string ToVersion(WorkOrder workOrder)
=> Convert.ToBase64String(workOrder.RowVersion ?? new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 });
private static (ApplicationDbContext Context, WorkOrderBoardUpdateService Service) CreateBoardSut()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var context = new ApplicationDbContext(options);
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
var service = new WorkOrderBoardUpdateService(
new WorkOrderBoardDataService(context),
new WorkOrderBoardMutationDataService(context),
audit);
return (context, service);
}
private static (ApplicationDbContext Context, WorkOrderMediaService Service) CreateMediaSut()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var context = new ApplicationDbContext(options);
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
var service = new WorkOrderMediaService(
new WorkOrderMediaDataService(context),
new WorkOrderDetailDataService(context),
audit);
return (context, service);
}
private static WorkOrder CompletedWorkOrder()
=> new()
{
Id = 1,
LifecycleStatus = LifecycleStatus.Completed,
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
};
[Fact]
public async Task PatchField_Completed_AllowsFlagColor()
{
var (context, service) = CreateBoardSut();
var wo = CompletedWorkOrder();
context.workOrders.Add(wo);
await context.SaveChangesAsync();
var result = await service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
{
Field = WorkOrderBoardFieldNames.FlagColor,
Value = "#ef4444",
WorkOrderVersion = ToVersion(wo)
}, "actor-1");
Assert.Equal("#EF4444", result.FlagColor);
}
[Theory]
[InlineData(WorkOrderBoardFieldNames.LifecycleStatus, "Scheduled")]
[InlineData(WorkOrderBoardFieldNames.SiteCode, "BK5")]
[InlineData(WorkOrderBoardFieldNames.AssignTo, "dispatcher-2")]
public async Task PatchField_Completed_BlocksCoreFields(string field, string value)
{
var (context, service) = CreateBoardSut();
var wo = CompletedWorkOrder();
context.workOrders.Add(wo);
await context.SaveChangesAsync();
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
{
Field = field,
Value = value,
WorkOrderVersion = ToVersion(wo)
}, "actor-1"));
Assert.Equal("ReadOnly", ex.Code);
}
[Fact]
public async Task PatchField_Canceled_BlocksFlagColor()
{
var (context, service) = CreateBoardSut();
var wo = new WorkOrder
{
Id = 1,
LifecycleStatus = LifecycleStatus.Canceled,
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
};
context.workOrders.Add(wo);
await context.SaveChangesAsync();
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
service.PatchFieldAsync(1, new WorkOrderBoardPatchRequestDto
{
Field = WorkOrderBoardFieldNames.FlagColor,
Value = "#ef4444",
WorkOrderVersion = ToVersion(wo)
}, "actor-1"));
Assert.Equal("CanceledReadOnly", ex.Code);
}
[Fact]
public async Task UpdateComment_Completed_UpdatesText()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var context = new ApplicationDbContext(options);
context.workOrders.Add(CompletedWorkOrder());
context.Users.Add(new ApplicationUser
{
Id = "user-author",
UserName = "author",
FirstName = "Alice",
LastName = "Dispatcher"
});
var comment = new Comments
{
WorkerOrderId = 1,
UserId = "user-author",
Commenttext = "Original",
CommentType = "General",
RecordType = "WorkOrder",
CreatedDate = DateTime.UtcNow
};
context.Comments.Add(comment);
await context.SaveChangesAsync();
var service = new WorkOrderCommentService(
new WorkOrderDetailDataService(context),
new CommentDataService(context),
new UserDataService(context),
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.UpdateCommentAsync(
1,
comment.Id,
new WorkOrderCommentCreateDto { Text = "Follow-up" },
WorkOrderAccountTestHelpers.OrgWideAdmin("user-author"),
"user-author");
Assert.Equal("Follow-up", result.Text);
}
[Fact]
public async Task AddComment_Completed_AddsText()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var context = new ApplicationDbContext(options);
context.workOrders.Add(CompletedWorkOrder());
context.Users.Add(new ApplicationUser
{
Id = "user-author",
UserName = "author",
FirstName = "Alice",
LastName = "Dispatcher"
});
await context.SaveChangesAsync();
var service = new WorkOrderCommentService(
new WorkOrderDetailDataService(context),
new CommentDataService(context),
new UserDataService(context),
WorkOrderAccountTestHelpers.Resolver(context));
var result = await service.AddCommentAsync(
1,
new WorkOrderCommentCreateDto { Text = "Follow-up" },
WorkOrderAccountTestHelpers.OrgWideAdmin("user-author"),
"user-author");
Assert.Equal("Follow-up", result.Text);
Assert.Equal("user-author", result.AuthorId);
}
[Fact]
public async Task AddComment_Canceled_ThrowsReadOnly()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var context = new ApplicationDbContext(options);
context.workOrders.Add(new WorkOrder
{
Id = 1,
LifecycleStatus = LifecycleStatus.Canceled,
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
});
await context.SaveChangesAsync();
var service = new WorkOrderCommentService(
new WorkOrderDetailDataService(context),
new CommentDataService(context),
new UserDataService(context),
WorkOrderAccountTestHelpers.Resolver(context));
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
service.AddCommentAsync(
1,
new WorkOrderCommentCreateDto { Text = "Should not persist" },
WorkOrderAccountTestHelpers.OrgWideAdmin("user-author"),
"user-author"));
Assert.Equal("ReadOnly", ex.Code);
Assert.Empty(context.Comments);
}
[Fact]
public async Task AddMedia_Completed_AllowsExtra()
{
var (context, service) = CreateMediaSut();
context.workOrders.Add(CompletedWorkOrder());
await context.SaveChangesAsync();
var media = await service.AddMediaAsync(
1,
WorkOrderMediaCategory.Extra,
"https://example.com/extra.pdf",
WorkOrderAccountTestHelpers.OrgWideAdmin(),
"actor-1");
Assert.True(media.Id > 0);
Assert.Equal(WorkOrderMediaCategory.Extra, media.Category);
}
[Fact]
public async Task AddMedia_Completed_BlocksBefore()
{
var (context, service) = CreateMediaSut();
context.workOrders.Add(CompletedWorkOrder());
await context.SaveChangesAsync();
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
service.AddMediaAsync(
1,
WorkOrderMediaCategory.Before,
"https://example.com/before.jpg",
WorkOrderAccountTestHelpers.OrgWideAdmin(),
"actor-1"));
Assert.Equal("ReadOnly", ex.Code);
}
[Fact]
public async Task UpdateMediaCategory_Completed_KeepsExtra()
{
var (context, service) = CreateMediaSut();
var wo = CompletedWorkOrder();
context.workOrders.Add(wo);
context.workOrderAttachments.Add(new WorkOrderAttachments
{
Id = 10,
WorkorderId = 1,
Attachments = "https://example.com/extra.pdf",
Category = WorkOrderMediaCategory.Extra
});
await context.SaveChangesAsync();
var media = await service.UpdateMediaCategoryAsync(
1,
10,
WorkOrderMediaCategory.Extra,
ToVersion(wo),
WorkOrderAccountTestHelpers.OrgWideAdmin(),
"actor-1");
Assert.Equal(WorkOrderMediaCategory.Extra, media.Category);
}
[Fact]
public async Task UpdateMediaCategory_Completed_BlocksMoveToBefore()
{
var (context, service) = CreateMediaSut();
var wo = CompletedWorkOrder();
context.workOrders.Add(wo);
context.workOrderAttachments.Add(new WorkOrderAttachments
{
Id = 10,
WorkorderId = 1,
Attachments = "https://example.com/extra.pdf",
Category = WorkOrderMediaCategory.Extra
});
await context.SaveChangesAsync();
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
service.UpdateMediaCategoryAsync(
1,
10,
WorkOrderMediaCategory.Before,
ToVersion(wo),
WorkOrderAccountTestHelpers.OrgWideAdmin(),
"actor-1"));
Assert.Equal("ReadOnly", ex.Code);
}
[Fact]
public async Task DeleteMedia_Completed_AllowsExtra()
{
var (context, service) = CreateMediaSut();
var wo = CompletedWorkOrder();
context.workOrders.Add(wo);
context.workOrderAttachments.Add(new WorkOrderAttachments
{
Id = 10,
WorkorderId = 1,
Attachments = "https://example.com/extra.pdf",
Category = WorkOrderMediaCategory.Extra
});
await context.SaveChangesAsync();
await service.DeleteMediaAsync(
1,
10,
ToVersion(wo),
WorkOrderAccountTestHelpers.OrgWideAdmin(),
"actor-1");
Assert.True(context.workOrderAttachments.Single().IsDeleted);
}
[Fact]
public async Task AddMedia_Canceled_BlocksExtra()
{
var (context, service) = CreateMediaSut();
context.workOrders.Add(new WorkOrder
{
Id = 1,
LifecycleStatus = LifecycleStatus.Canceled,
RowVersion = new byte[] { 1, 0, 0, 0, 0, 0, 0, 1 }
});
await context.SaveChangesAsync();
var ex = await Assert.ThrowsAsync<WorkOrderBoardValidationException>(() =>
service.AddMediaAsync(
1,
WorkOrderMediaCategory.Extra,
"https://example.com/extra.pdf",
WorkOrderAccountTestHelpers.OrgWideAdmin(),
"actor-1"));
Assert.Equal("ReadOnly", ex.Code);
}
}