mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 03:43:11 +00:00
235 lines
9.8 KiB
C#
235 lines
9.8 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Data.SeaHavenIndustries.Enums;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Implementation;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Implementation;
|
|
using SeaHaven.Services.Validation;
|
|
|
|
namespace SeaHavenIndustries.Tests;
|
|
|
|
public class WorkOrderBoardCreateRelationalTests
|
|
{
|
|
[Fact]
|
|
public async Task Create_AuditAndFieldLocks_UseGeneratedWorkOrderId()
|
|
{
|
|
await using var connection = new SqliteConnection("DataSource=:memory:");
|
|
await connection.OpenAsync();
|
|
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseSqlite(connection)
|
|
.Options;
|
|
|
|
await using var context = new SqliteBoardTestDbContext(options);
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
const string actorId = "actor-1";
|
|
context.Users.Add(new ApplicationUser
|
|
{
|
|
Id = actorId,
|
|
UserName = "actor-1",
|
|
NormalizedUserName = "ACTOR-1",
|
|
Email = "actor@test.local",
|
|
NormalizedEmail = "ACTOR@TEST.LOCAL"
|
|
});
|
|
await context.SaveChangesAsync();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context);
|
|
await WorkOrderAccountTestHelpers.EnsureLocationAsync(context);
|
|
|
|
var boardData = new WorkOrderBoardDataService(context);
|
|
var mutationData = new WorkOrderBoardMutationDataService(context);
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardService = new WorkOrderBoardService(boardData, resolver);
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var service = new WorkOrderBoardCreateService(
|
|
boardData,
|
|
mutationData,
|
|
boardService,
|
|
audit,
|
|
new WorkOrderBoardCreateValidation(),
|
|
resolver,
|
|
new WorkOrderPocDataService(context));
|
|
|
|
var result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto
|
|
{
|
|
WorkOrderType = WorkOrderType.PM,
|
|
SiteCode = "BK5",
|
|
LocationId = 1,
|
|
Description = "Relational create"
|
|
}, WorkOrderAccountTestHelpers.AccountUser(actorId), actorId);
|
|
|
|
Assert.True(result.Id > 0);
|
|
|
|
var logs = await context.WorkOrderAuditLogs.AsNoTracking().ToListAsync();
|
|
var locks = await context.WorkOrderFieldLocks.AsNoTracking().ToListAsync();
|
|
|
|
Assert.NotEmpty(logs);
|
|
Assert.NotEmpty(locks);
|
|
Assert.Contains(logs, l => l.Action == "Create");
|
|
Assert.All(logs, l => Assert.Equal(result.Id, l.WorkOrderId));
|
|
Assert.All(locks, l => Assert.Equal(result.Id, l.WorkOrderId));
|
|
Assert.DoesNotContain(logs, l => l.WorkOrderId == 0);
|
|
Assert.DoesNotContain(locks, l => l.WorkOrderId == 0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_WhenContactPersistenceFails_RollsBackWorkOrder()
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True");
|
|
await connection.OpenAsync();
|
|
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseSqlite(connection)
|
|
.Options;
|
|
|
|
await using var context = new SqliteBoardTestDbContext(options);
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
const string actorId = "actor-1";
|
|
context.Users.Add(new ApplicationUser
|
|
{
|
|
Id = actorId,
|
|
UserName = "actor-1",
|
|
NormalizedUserName = "ACTOR-1",
|
|
Email = "actor@test.local",
|
|
NormalizedEmail = "ACTOR@TEST.LOCAL"
|
|
});
|
|
await context.SaveChangesAsync();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context);
|
|
await WorkOrderAccountTestHelpers.EnsureLocationAsync(context);
|
|
|
|
var boardData = new WorkOrderBoardDataService(context);
|
|
var mutationData = new WorkOrderBoardMutationDataService(context);
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var boardService = new WorkOrderBoardService(boardData, resolver);
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var audit = new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks);
|
|
var service = new WorkOrderBoardCreateService(
|
|
boardData,
|
|
mutationData,
|
|
boardService,
|
|
audit,
|
|
new WorkOrderBoardCreateValidation(),
|
|
resolver,
|
|
new WorkOrderPocDataService(context));
|
|
|
|
await Assert.ThrowsAsync<DbUpdateException>(() =>
|
|
service.CreateAsync(new WorkOrderBoardCreateRequestDto
|
|
{
|
|
WorkOrderType = WorkOrderType.PM,
|
|
SiteCode = "BK5",
|
|
LocationId = 1,
|
|
Description = "Should roll back",
|
|
PocContactId = 999_999
|
|
}, WorkOrderAccountTestHelpers.AccountUser(actorId), actorId));
|
|
|
|
// Fresh context on the same connection so we observe committed state only.
|
|
await using var verify = new SqliteBoardTestDbContext(options);
|
|
Assert.Empty(await verify.workOrders.AsNoTracking().ToListAsync());
|
|
Assert.Empty(await verify.WorkOrderAuditLogs.AsNoTracking().ToListAsync());
|
|
Assert.Empty(await verify.WorkOrderFieldLocks.AsNoTracking().ToListAsync());
|
|
Assert.Empty(await verify.WorkOrderContacts.AsNoTracking().ToListAsync());
|
|
Assert.Empty(await verify.Dispatches.AsNoTracking().ToListAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_WithVendor_PersistsPrimaryDispatchOwnershipForUpliftResolution()
|
|
{
|
|
await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True");
|
|
await connection.OpenAsync();
|
|
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
.UseSqlite(connection)
|
|
.Options;
|
|
|
|
await using (var context = new SqliteBoardTestDbContext(options))
|
|
{
|
|
await context.Database.EnsureCreatedAsync();
|
|
context.Users.Add(new ApplicationUser
|
|
{
|
|
Id = "actor-1",
|
|
UserName = "actor-1",
|
|
NormalizedUserName = "ACTOR-1",
|
|
Email = "actor@test.local",
|
|
NormalizedEmail = "ACTOR@TEST.LOCAL"
|
|
});
|
|
await context.SaveChangesAsync();
|
|
await WorkOrderAccountTestHelpers.EnsureAccountAsync(context);
|
|
await WorkOrderAccountTestHelpers.EnsureLocationAsync(context);
|
|
context.Vendors.Add(new Vendor { Id = 5, CompanyName = "Acme HVAC" });
|
|
await context.SaveChangesAsync();
|
|
}
|
|
|
|
WorkOrderBoardRowDto result;
|
|
await using (var context = new SqliteBoardTestDbContext(options))
|
|
{
|
|
var boardData = new WorkOrderBoardDataService(context);
|
|
var resolver = WorkOrderAccountTestHelpers.Resolver(context);
|
|
var fieldLocks = new WorkOrderFieldLockService(new WorkOrderFieldLockDataService(context));
|
|
var service = new WorkOrderBoardCreateService(
|
|
boardData,
|
|
new WorkOrderBoardMutationDataService(context),
|
|
new WorkOrderBoardService(boardData, resolver),
|
|
new WorkOrderAuditService(new WorkOrderAuditDataService(context), fieldLocks),
|
|
new WorkOrderBoardCreateValidation(),
|
|
resolver,
|
|
new WorkOrderPocDataService(context));
|
|
|
|
result = await service.CreateAsync(new WorkOrderBoardCreateRequestDto
|
|
{
|
|
WorkOrderType = WorkOrderType.PM,
|
|
SiteCode = "BK5",
|
|
LocationId = 1,
|
|
VendorId = 5,
|
|
TechnicianAssigned = false
|
|
}, WorkOrderAccountTestHelpers.AccountUser(), "actor-1");
|
|
}
|
|
|
|
// SH-393: fresh context, committed state only. The primary dispatch must be owned
|
|
// by the work order so the uplift create/read scope resolves it.
|
|
await using var verify = new SqliteBoardTestDbContext(options);
|
|
var dispatch = await verify.Dispatches.AsNoTracking().SingleAsync();
|
|
Assert.Equal(result.PrimaryDispatchId, dispatch.Id);
|
|
Assert.Equal(result.Id, dispatch.WorkOrderId);
|
|
Assert.False(await verify.workOrders
|
|
.Where(workOrder => workOrder.Id == result.Id)
|
|
.Select(workOrder => workOrder.TechnicianAssigned)
|
|
.SingleAsync());
|
|
|
|
var upliftDispatch = await new UpliftDataService(verify).GetUpliftDispatchForWorkOrderAsync(
|
|
result.Id,
|
|
result.PrimaryDispatchId,
|
|
CancellationToken.None);
|
|
Assert.Equal(dispatch.Id, upliftDispatch?.Id);
|
|
}
|
|
|
|
private sealed class SqliteBoardTestDbContext : ApplicationDbContext
|
|
{
|
|
public SqliteBoardTestDbContext(DbContextOptions<ApplicationDbContext> options)
|
|
: base(options)
|
|
{
|
|
}
|
|
|
|
protected override void OnModelCreating(ModelBuilder builder)
|
|
{
|
|
base.OnModelCreating(builder);
|
|
|
|
// SQL Server filtered index syntax is invalid on SQLite.
|
|
foreach (var index in builder.Model.GetEntityTypes().SelectMany(e => e.GetIndexes()))
|
|
{
|
|
if (index.GetFilter() != null)
|
|
index.SetFilter(null);
|
|
}
|
|
|
|
// SQLite has no rowversion type; treat as plain nullable blobs.
|
|
foreach (var entityType in new[] { typeof(WorkOrder), typeof(Dispatch) })
|
|
{
|
|
var property = builder.Entity(entityType).Property("RowVersion").Metadata;
|
|
property.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.Never;
|
|
property.IsConcurrencyToken = false;
|
|
}
|
|
}
|
|
}
|
|
}
|