mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 18:31:08 -04:00
The nest name box was always read-only, so a nest could only be renamed by Save As, which leaves the old copy behind. In Database mode Nest Info now edits the name (uploaded with the next Save), as does the saved-nest browser's Nest Information (saved immediately). File mode keeps it read-only because the file name is the nest name. Renaming to a name another active nest already has asks first, and declining reopens the dialog with the edits kept; a duplicate accepted there is not asked about again by the nest's first Save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
4.7 KiB
C#
106 lines
4.7 KiB
C#
using OpenNest.Data;
|
|
|
|
namespace OpenNest.Server.Tests;
|
|
|
|
public sealed class NestSequenceTests
|
|
{
|
|
[Fact]
|
|
public void Allocation_CountsPerYearAndSurvivesReopen()
|
|
{
|
|
using var factory = new ServerFactory();
|
|
using (var database = new NestDatabase(factory.DatabasePath, pooling: false))
|
|
{
|
|
Assert.Equal(1, database.AllocateNestNumber(2026));
|
|
Assert.Equal(2, database.AllocateNestNumber(2026));
|
|
Assert.Equal(1, database.AllocateNestNumber(2027));
|
|
}
|
|
|
|
using var reopened = new NestDatabase(factory.DatabasePath, pooling: false);
|
|
Assert.Equal(3, reopened.AllocateNestNumber(2026));
|
|
}
|
|
|
|
[Fact]
|
|
public void Allocation_SkipsPastStoredGeneratedNamesIncludingDeleted()
|
|
{
|
|
using var factory = new ServerFactory();
|
|
using var database = new NestDatabase(factory.DatabasePath, pooling: false);
|
|
database.Insert(Guid.NewGuid(), new NestRecord { Name = NestNames.Format(2026, 40) }, [1]);
|
|
var deleted = database.Insert(Guid.NewGuid(), new NestRecord { Name = NestNames.Format(2026, 75) }, [2]);
|
|
database.SetDeleted(deleted.Id, true, deleted.Revision);
|
|
// Another year's name, a renamed nest and non-canonical padding never move the counter.
|
|
database.Insert(Guid.NewGuid(), new NestRecord { Name = NestNames.Format(2025, 900) }, [3]);
|
|
database.Insert(Guid.NewGuid(), new NestRecord { Name = "N26-ZZZ customer copy" }, [4]);
|
|
database.Insert(Guid.NewGuid(), new NestRecord { Name = "N26-2ZZZ" }, [5]);
|
|
|
|
Assert.Equal(76, database.AllocateNestNumber(2026));
|
|
Assert.Equal(77, database.AllocateNestNumber(2026));
|
|
|
|
// A name uploaded later (an offline fallback) still pushes the counter past it.
|
|
database.Insert(Guid.NewGuid(), new NestRecord { Name = NestNames.Format(2026, 200) }, [6]);
|
|
Assert.Equal(201, database.AllocateNestNumber(2026));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Allocation_IsUniqueUnderConcurrentClients()
|
|
{
|
|
using var factory = new ServerFactory();
|
|
using var client = factory.CreateClient();
|
|
var repository = new RemoteNestRepository(client, client.BaseAddress!.ToString());
|
|
|
|
var numbers = await Task.WhenAll(
|
|
Enumerable.Range(0, 40).Select(_ => repository.AllocateNestNumberAsync(2026)));
|
|
|
|
Assert.Equal(Enumerable.Range(1, 40), numbers.Order());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DuplicateFirstSaveName_GetsAFreshNameOnlyForActiveExactMatches()
|
|
{
|
|
using var factory = new ServerFactory();
|
|
using (var database = new NestDatabase(factory.DatabasePath, pooling: false))
|
|
{
|
|
database.Insert(Guid.NewGuid(), new NestRecord { Name = NestNames.Format(2026, 40) }, [1]);
|
|
var deleted = database.Insert(Guid.NewGuid(), new NestRecord { Name = "Old job" }, [2]);
|
|
database.SetDeleted(deleted.Id, true, deleted.Revision);
|
|
}
|
|
using var client = factory.CreateClient();
|
|
var repository = new RemoteNestRepository(client, client.BaseAddress!.ToString());
|
|
|
|
var replacement = await NestNameConflicts.FindReplacementAsync(repository, NestNames.Format(2026, 40), 2026);
|
|
Assert.Equal(NestNames.Format(2026, 41), replacement?.Name);
|
|
|
|
Assert.Null(await NestNameConflicts.FindReplacementAsync(repository, "Old job", 2026));
|
|
Assert.Null(await NestNameConflicts.FindReplacementAsync(repository, NestNames.Format(2026, 40).ToLowerInvariant(), 2026));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Rename_WarnsOnlyAboutOtherActiveNests()
|
|
{
|
|
using var factory = new ServerFactory();
|
|
Guid own;
|
|
using (var database = new NestDatabase(factory.DatabasePath, pooling: false))
|
|
{
|
|
own = database.Insert(Guid.NewGuid(), new NestRecord { Name = "Bracket job" }, [1]).Id;
|
|
database.Insert(Guid.NewGuid(), new NestRecord { Name = "Gusset job" }, [2]);
|
|
}
|
|
using var client = factory.CreateClient();
|
|
var repository = new RemoteNestRepository(client, client.BaseAddress!.ToString());
|
|
|
|
Assert.False(await NestNameConflicts.TryIsNameTakenAsync(repository, "Bracket job", own));
|
|
Assert.True(await NestNameConflicts.TryIsNameTakenAsync(repository, "Gusset job", own));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Endpoint_RejectsYearsOutsideTheNameRange()
|
|
{
|
|
using var factory = new ServerFactory();
|
|
using var client = factory.CreateClient();
|
|
|
|
using var response = await client.PostAsync("/api/nest-names/next?year=1999", null);
|
|
|
|
Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
|
|
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
|
|
new RemoteNestRepository(client, client.BaseAddress!.ToString()).AllocateNestNumberAsync(2100));
|
|
}
|
|
}
|