mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-29 00:11:06 -04:00
Auto Nest's Stock Options gets a Load from ledger button that reads the nest
material's free sheets and drops from MaterialLedger's API-key-protected
GET /api/stock/opennest. Ledger rows are request-local: the engine sees them as
ordinary NestPlateStock rows with ledger:{n} ids and finite quantities, and they
are never written to Nest.PlateOptions.
- OpenNest.Data/MaterialLedger: settings file, read-only client (X-Api-Key,
5 s timeout, closed LedgerStockResult set) and LedgerStockMapper (bundles and
sheets one row each, same-size drops grouped with ordered item codes, unknown
shapes and too-small items skipped, inch/mm conversion).
- Options: MaterialLedger URL and API key (DPAPI, CurrentUser) with Test.
- Auto Nest grid: Use, Qty and Source columns for every row; PlateOption gains
request-local Quantity and Id, which NestStockBuilder passes to the engine.
Ledger rows score by area; hand-typed costs stay saved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
2.4 KiB
C#
66 lines
2.4 KiB
C#
using OpenNest.Data.MaterialLedger;
|
|
|
|
namespace OpenNest.Tests.Data;
|
|
|
|
public class MaterialLedgerSettingsTests : IDisposable
|
|
{
|
|
private readonly string directory = Path.Combine(Path.GetTempPath(), "opennest-ledger-" + Guid.NewGuid().ToString("N"));
|
|
|
|
private string SettingsPath => Path.Combine(directory, "material-ledger.json");
|
|
|
|
private static string Protect(string plain) => "P:" + new string(plain.Reverse().ToArray());
|
|
|
|
private static string Unprotect(string text) =>
|
|
text.StartsWith("P:") ? new string(text[2..].Reverse().ToArray()) : throw new FormatException("not ours");
|
|
|
|
[Fact]
|
|
public void RoundTripStoresOnlyTheProtectedKey()
|
|
{
|
|
new MaterialLedgerSettings { BaseUrl = " http://ledger:5000/ ", ApiKey = "secret-key" }.Save(SettingsPath, Protect);
|
|
|
|
var json = File.ReadAllText(SettingsPath);
|
|
Assert.DoesNotContain("secret-key", json);
|
|
Assert.Contains("yek-terces", json);
|
|
|
|
var loaded = MaterialLedgerSettings.Load(SettingsPath, Unprotect);
|
|
Assert.Equal("http://ledger:5000", loaded.BaseUrl);
|
|
Assert.Equal("secret-key", loaded.ApiKey);
|
|
Assert.True(loaded.IsConfigured);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyThatCannotBeUnprotectedLoadsEmpty()
|
|
{
|
|
new MaterialLedgerSettings { BaseUrl = "http://ledger", ApiKey = "secret-key" }.Save(SettingsPath, _ => "foreign blob");
|
|
var loaded = MaterialLedgerSettings.Load(SettingsPath, Unprotect);
|
|
Assert.Equal("http://ledger", loaded.BaseUrl);
|
|
Assert.Equal("", loaded.ApiKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void MissingOrCorruptFileIsNotConfigured()
|
|
{
|
|
Assert.False(MaterialLedgerSettings.Load(SettingsPath, Unprotect).IsConfigured);
|
|
Directory.CreateDirectory(directory);
|
|
File.WriteAllText(SettingsPath, "{ not json");
|
|
Assert.False(MaterialLedgerSettings.Load(SettingsPath, Unprotect).IsConfigured);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("", true, "")]
|
|
[InlineData(" https://ledger/ ", true, "https://ledger")]
|
|
[InlineData("ledger", false, "")]
|
|
[InlineData("ftp://ledger", false, "")]
|
|
public void UrlsAreValidatedAndNormalized(string input, bool valid, string expected)
|
|
{
|
|
Assert.Equal(valid, MaterialLedgerSettings.TryNormalizeUrl(input, out var normalized));
|
|
Assert.Equal(expected, normalized);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Directory.Exists(directory))
|
|
Directory.Delete(directory, recursive: true);
|
|
}
|
|
}
|