mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
test(work-orders): add Phase 7 coexistence coverage
This commit is contained in:
parent
64ca9e0bdf
commit
03dc04ad0b
1 changed files with 241 additions and 0 deletions
241
SeaHavenIndustries.Tests/WorkOrderPhase7CoexistenceTests.cs
Normal file
241
SeaHavenIndustries.Tests/WorkOrderPhase7CoexistenceTests.cs
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SeaHaven.DataServices.Implementation;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Helpers;
|
||||
using SeaHaven.Services.Implementation;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHavenIndustries.Tests;
|
||||
|
||||
public class WorkOrderPhase7CoexistenceTests
|
||||
{
|
||||
[Fact]
|
||||
public void IngestFieldMapper_MapsDynamoStatus()
|
||||
{
|
||||
Assert.Equal("Open", WorkOrderIngestFieldMapper.MapStatus("new"));
|
||||
Assert.Equal("In Progress", WorkOrderIngestFieldMapper.MapStatus("in_progress"));
|
||||
Assert.Equal("Done", WorkOrderIngestFieldMapper.MapStatus("completed"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestService_CreatesThenUpdates_IdempotentByExternalId()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
await using var context = new ApplicationDbContext(options);
|
||||
|
||||
var fieldLocks = new WorkOrderFieldLockService(context);
|
||||
var audit = new WorkOrderAuditService(context, fieldLocks);
|
||||
var ingest = new WorkOrderIngestService(context, new SyncFieldMergePolicy(fieldLocks), fieldLocks, audit);
|
||||
|
||||
var payload = new WorkOrderIngestPayloadDto
|
||||
{
|
||||
ExternalWorkOrderId = "ext-100",
|
||||
Description = "Initial",
|
||||
WoStatus = "new",
|
||||
Severity = "2",
|
||||
SiteCode = "BK1"
|
||||
};
|
||||
|
||||
var create = await ingest.UpsertBatchAsync(new[] { payload });
|
||||
Assert.Equal(1, create.Created);
|
||||
Assert.Equal(0, create.Updated);
|
||||
|
||||
payload.Description = "Updated via ingest";
|
||||
var update = await ingest.UpsertBatchAsync(new[] { payload });
|
||||
Assert.Equal(0, update.Created);
|
||||
Assert.Equal(1, update.Updated);
|
||||
|
||||
var wo = await context.workOrders.SingleAsync();
|
||||
Assert.Equal("Updated via ingest", wo.Description);
|
||||
Assert.Equal("ext-100", wo.ExternalWorkOrderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestRespectsFieldLock_AfterShocBoardCreate()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
await using var context = new ApplicationDbContext(options);
|
||||
|
||||
var boardData = new WorkOrderBoardDataService(context);
|
||||
var boardService = new WorkOrderBoardService(boardData);
|
||||
var fieldLocks = new WorkOrderFieldLockService(context);
|
||||
var audit = new WorkOrderAuditService(context, fieldLocks);
|
||||
var createService = new WorkOrderBoardCreateService(
|
||||
context, boardData, boardService, audit, new WorkOrderBoardCreateValidation());
|
||||
var policy = new SyncFieldMergePolicy(fieldLocks);
|
||||
var ingest = new WorkOrderIngestService(context, policy, fieldLocks, audit);
|
||||
|
||||
await createService.CreateAsync(new WorkOrderBoardCreateRequestDto
|
||||
{
|
||||
WorkOrderType = WorkOrderType.PM,
|
||||
SiteCode = "BK5"
|
||||
}, "dispatcher-1");
|
||||
|
||||
var wo = await context.workOrders.SingleAsync();
|
||||
wo.ExternalWorkOrderId = "ext-shoc-1";
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
await ingest.UpsertBatchAsync(new[]
|
||||
{
|
||||
new WorkOrderIngestPayloadDto
|
||||
{
|
||||
ExternalWorkOrderId = "ext-shoc-1",
|
||||
SiteCode = "SYNC-SITE"
|
||||
}
|
||||
});
|
||||
|
||||
Assert.Equal("BK5", wo.SiteCode);
|
||||
Assert.True(await context.WorkOrderAuditLogs.AnyAsync(l => l.Action == "SyncRejected"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestRespectsLocationIdFieldLock()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
await using var context = new ApplicationDbContext(options);
|
||||
|
||||
context.Locations.Add(new Locations { Id = 1, Name = "BK1", Status = "Active" });
|
||||
context.Locations.Add(new Locations { Id = 2, Name = "BK2", Status = "Active" });
|
||||
context.workOrders.Add(new WorkOrder
|
||||
{
|
||||
Id = 10,
|
||||
ExternalWorkOrderId = "ext-loc-1",
|
||||
LocationId = 1,
|
||||
SiteCode = "BK1",
|
||||
istemplate = false
|
||||
});
|
||||
context.WorkOrderFieldLocks.Add(new WorkOrderFieldLock
|
||||
{
|
||||
WorkOrderId = 10,
|
||||
FieldName = "LocationId",
|
||||
LockedAt = DateTime.UtcNow,
|
||||
LockedByUserId = "dispatcher-1"
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var fieldLocks = new WorkOrderFieldLockService(context);
|
||||
var audit = new WorkOrderAuditService(context, fieldLocks);
|
||||
var ingest = new WorkOrderIngestService(context, new SyncFieldMergePolicy(fieldLocks), fieldLocks, audit);
|
||||
|
||||
await ingest.UpsertBatchAsync(new[]
|
||||
{
|
||||
new WorkOrderIngestPayloadDto
|
||||
{
|
||||
ExternalWorkOrderId = "ext-loc-1",
|
||||
SiteCode = "BK2",
|
||||
Building = "BK2"
|
||||
}
|
||||
});
|
||||
|
||||
var wo = await context.workOrders.SingleAsync(w => w.Id == 10);
|
||||
Assert.Equal(1, wo.LocationId);
|
||||
Assert.True(await context.WorkOrderAuditLogs.AnyAsync(l =>
|
||||
l.Action == "SyncRejected" && l.FieldName == "LocationId"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestBatch_SkipsItemsWithoutExternalId()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
await using var context = new ApplicationDbContext(options);
|
||||
|
||||
var fieldLocks = new WorkOrderFieldLockService(context);
|
||||
var audit = new WorkOrderAuditService(context, fieldLocks);
|
||||
var ingest = new WorkOrderIngestService(context, new SyncFieldMergePolicy(fieldLocks), fieldLocks, audit);
|
||||
|
||||
var result = await ingest.UpsertBatchAsync(new[]
|
||||
{
|
||||
new WorkOrderIngestPayloadDto { ExternalWorkOrderId = " ", Description = "bad" },
|
||||
new WorkOrderIngestPayloadDto { ExternalWorkOrderId = "ext-ok", Description = "good" }
|
||||
});
|
||||
|
||||
Assert.Equal(1, result.Created);
|
||||
Assert.Equal(1, result.Skipped);
|
||||
Assert.Equal(1, result.Results.Count);
|
||||
Assert.Equal("ext-ok", result.Results[0].ExternalWorkOrderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpsHealth_ReturnsCoexistenceMetrics()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
await using var context = new ApplicationDbContext(options);
|
||||
|
||||
context.WorkOrderFieldLocks.Add(new WorkOrderFieldLock
|
||||
{
|
||||
WorkOrderId = 1,
|
||||
FieldName = "SiteCode",
|
||||
LockedAt = DateTime.UtcNow,
|
||||
LockedByUserId = "u1"
|
||||
});
|
||||
context.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
||||
{
|
||||
WorkOrderId = 1,
|
||||
Action = "SyncRejected",
|
||||
FieldName = "Description",
|
||||
CreatedAt = DateTime.UtcNow.AddHours(-1)
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var runState = new WorkOrderJobRunStateAccessor
|
||||
{
|
||||
State = { LastWeekRolledRunUtc = DateTime.UtcNow.AddDays(-1) }
|
||||
};
|
||||
|
||||
var healthOptions = Options.Create(new WorkOrderOpsHealthOptions
|
||||
{
|
||||
SyncEnabled = true,
|
||||
IngestEnabled = true,
|
||||
LegacyDeprecationEnabled = true,
|
||||
LegacySunsetDate = "2026-12-31"
|
||||
});
|
||||
|
||||
var service = new WorkOrderOpsHealthService(context, runState, healthOptions);
|
||||
var health = await service.GetHealthAsync();
|
||||
|
||||
Assert.Equal(1, health.FieldLockCount);
|
||||
Assert.Equal(1, health.SyncRejectedLast24h);
|
||||
Assert.True(health.SyncEnabled);
|
||||
Assert.True(health.IngestEnabled);
|
||||
Assert.Equal("2026-12-31", health.LegacySunsetDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BoardAndLegacyEndpoints_CanCoexist_InSameDatabase()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
await using var context = new ApplicationDbContext(options);
|
||||
|
||||
context.workOrders.Add(new WorkOrder
|
||||
{
|
||||
WorkerOrderTitle = "Legacy row",
|
||||
Status = "Open",
|
||||
InternalWONumber = "10000001",
|
||||
istemplate = false
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var boardData = new WorkOrderBoardDataService(context);
|
||||
var boardService = new WorkOrderBoardService(boardData);
|
||||
var count = await context.workOrders.CountAsync();
|
||||
|
||||
Assert.Equal(1, count);
|
||||
Assert.NotNull(boardService);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue