mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 19:41:08 -04:00
Operators need to build kits from library drawings before they can be released. The Drawing Library dialog gains a Sets tab beside Drawings, so sets live where their drawings are browsed: list, search, archive, restore, history, and an editor whose Add Drawings reuses the library in a multi-select pick mode. The Drawings tab's menu shows which sets use a drawing and adds it to a chosen or new set. DrawingSetEditSession keeps a new set's id across a save whose reply was lost, so a retry adopts the stored set instead of creating it twice, and guards existing-set saves with the revision they were read at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
158 lines
6.2 KiB
C#
158 lines
6.2 KiB
C#
using OpenNest.Data;
|
|
|
|
namespace OpenNest.Tests.Data;
|
|
|
|
public class DrawingSetEditSessionTests
|
|
{
|
|
private static DrawingLibraryRecord Drawing(string name) => new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Revision = 1,
|
|
Name = name,
|
|
MaterialName = "Synthetic Steel",
|
|
Thickness = 0.25,
|
|
};
|
|
|
|
[Fact]
|
|
public void AddDrawings_SkipsDrawingsAlreadyListed()
|
|
{
|
|
var a = Drawing("SYN-A");
|
|
var b = Drawing("SYN-B");
|
|
var session = new DrawingSetEditSession(new InMemoryDrawingSets(), null, "Synthetic operator");
|
|
|
|
var first = session.AddDrawings(new[] { a, b, a }, 3);
|
|
var second = session.AddDrawings(new[] { b, Drawing("SYN-C") });
|
|
|
|
Assert.Equal(new[] { a, b }, first.Added);
|
|
Assert.Equal(new[] { a }, first.AlreadyMembers);
|
|
Assert.Equal(new[] { b }, second.AlreadyMembers);
|
|
Assert.Equal(new[] { ("SYN-A", 3), ("SYN-B", 3), ("SYN-C", 1) },
|
|
session.Members.Select(m => (m.Drawing!.Name, m.Quantity)));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => session.AddDrawings(new[] { Drawing("SYN-D") }, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveRemoveAndQuantity_ChangeTheMemberList()
|
|
{
|
|
var session = new DrawingSetEditSession(new InMemoryDrawingSets(), null, "");
|
|
session.AddDrawings(new[] { Drawing("SYN-A"), Drawing("SYN-B"), Drawing("SYN-C") });
|
|
|
|
Assert.True(session.Move(2, -2));
|
|
Assert.False(session.Move(0, -1));
|
|
session.SetQuantity(1, 7);
|
|
session.RemoveAt(2);
|
|
|
|
Assert.Equal(new[] { ("SYN-C", 1), ("SYN-A", 7) }, session.Members.Select(m => (m.Drawing!.Name, m.Quantity)));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => session.SetQuantity(0, DrawingSetRules.MaxQuantity + 1));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => session.RemoveAt(2));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NewSet_SavesRevisionOneUnderItsAllocatedId()
|
|
{
|
|
var store = new InMemoryDrawingSets();
|
|
var session = new DrawingSetEditSession(store, null, "Synthetic operator");
|
|
Assert.Equal("name is required.", session.GetError());
|
|
session.Name = "Synthetic kit";
|
|
session.Customer = "Synthetic customer";
|
|
session.AddDrawings(new[] { Drawing("SYN-A") }, 2);
|
|
|
|
var saved = await session.SaveAsync();
|
|
|
|
Assert.Equal((session.Id, 1, "Synthetic operator"), (saved.Id, saved.Revision, saved.SavedBy));
|
|
Assert.False(session.IsNew);
|
|
Assert.False(session.IsChanged);
|
|
Assert.Same(saved, session.Saved);
|
|
Assert.Single(store.Creates);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExistingSet_SavesGuardedByTheRevisionItWasReadAt_AndIdenticalSaveSendsNothing()
|
|
{
|
|
var store = new InMemoryDrawingSets();
|
|
var a = Drawing("SYN-A");
|
|
var created = await store.CreateAsync(new DrawingSetRecord
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Name = "Synthetic kit",
|
|
Members = new[] { new DrawingSetMember { DrawingId = a.Id, Quantity = 1, Drawing = a } },
|
|
});
|
|
var session = new DrawingSetEditSession(store, created, "");
|
|
|
|
Assert.False(session.IsChanged);
|
|
Assert.Same(created, await session.SaveAsync());
|
|
session.SetQuantity(0, 4);
|
|
Assert.True(session.IsChanged);
|
|
var saved = await session.SaveAsync();
|
|
|
|
Assert.Equal(new[] { (created.Id, 1) }, store.Updates.Select(u => (u.Id, u.ExpectedRevision)));
|
|
Assert.Equal(2, saved.Revision);
|
|
Assert.Equal(4, session.Members.Single().Quantity);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StaleSave_RaisesTheConflictAndKeepsTheEdits()
|
|
{
|
|
var store = new InMemoryDrawingSets();
|
|
var created = await store.CreateAsync(new DrawingSetRecord { Id = Guid.NewGuid(), Name = "Synthetic kit" });
|
|
var session = new DrawingSetEditSession(store, created, "");
|
|
await store.UpdateAsync(created.Id, new DrawingSetRecord { Name = "Renamed elsewhere" }, 1);
|
|
session.Notes = "Synthetic notes";
|
|
|
|
var conflict = await Assert.ThrowsAsync<DrawingSetSaveConflictException>(() => session.SaveAsync());
|
|
|
|
Assert.Equal(2, conflict.Current.Revision);
|
|
Assert.Equal("Synthetic notes", session.Notes);
|
|
Assert.Equal(1, session.Saved!.Revision);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NewSet_AmbiguousFailureThenRetry_AdoptsTheStoredSetInsteadOfCreatingTwice()
|
|
{
|
|
var store = new InMemoryDrawingSets { LoseNextCreateReply = true };
|
|
var session = new DrawingSetEditSession(store, null, "");
|
|
session.Name = "Synthetic kit";
|
|
|
|
await Assert.ThrowsAsync<IOException>(() => session.SaveAsync());
|
|
Assert.True(session.HasPendingSave);
|
|
session.Notes = "Changed after the lost reply";
|
|
var saved = await session.SaveAsync();
|
|
|
|
Assert.Single(store.Creates);
|
|
Assert.Equal(new[] { (session.Id, 1) }, store.Updates.Select(u => (u.Id, u.ExpectedRevision)));
|
|
Assert.Equal((2, "Changed after the lost reply"), (saved.Revision, saved.Notes));
|
|
Assert.False(session.HasPendingSave);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NewSet_FailureThatStoredNothing_RetriesTheSameId()
|
|
{
|
|
var store = new InMemoryDrawingSets { FailNextCreate = true };
|
|
var session = new DrawingSetEditSession(store, null, "");
|
|
session.Name = "Synthetic kit";
|
|
|
|
await Assert.ThrowsAsync<IOException>(() => session.SaveAsync());
|
|
var saved = await session.SaveAsync();
|
|
|
|
Assert.Equal(new[] { session.Id, session.Id }, store.Creates.Select(c => c.Id));
|
|
Assert.Equal(1, saved.Revision);
|
|
}
|
|
|
|
[Fact]
|
|
public void Constructor_RefusesAnArchivedOrUnjoinedSet()
|
|
{
|
|
var store = new InMemoryDrawingSets();
|
|
var archived = new DrawingSetRecord { Id = Guid.NewGuid(), Revision = 2, Name = "Synthetic kit", Archived = true };
|
|
var unjoined = new DrawingSetRecord
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Revision = 1,
|
|
Name = "Synthetic kit",
|
|
Members = new[] { new DrawingSetMember { DrawingId = Guid.NewGuid(), Quantity = 1 } },
|
|
};
|
|
|
|
Assert.Throws<ArgumentException>(() => new DrawingSetEditSession(store, archived, ""));
|
|
Assert.Throws<ArgumentException>(() => new DrawingSetEditSession(store, unjoined, ""));
|
|
}
|
|
}
|