mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 17:21:34 -04:00
The server becomes the system of record for shared drawings (cut type, thickness, material and grade, customer, notes). Schema v4 adds `drawings` (current row) and `drawing_revisions` (immutable) to the same SQLite file under the existing monitor; the nest tables are untouched and a v3 upgrade keeps every nest row byte-identical. `/api/drawings` follows the nest revision contract: client-allocated ids, If-Match on every existing-drawing write (428/400/409 with no state change), archive instead of delete, and one transaction for promotion plus snapshot. Geometry is an opaque package (a zero-plate .nest) the server never parses. Enum fields accept single names only, because the JSON enum converter would otherwise accept numbers and comma combinations. Material, grade and customer filters match SharedListNames keys so spacing and case cannot split a drawing family; thickness is stored and compared at four decimals. Shared bounds live in OpenNest.Data so the client can refuse the same requests before sending. If-Match parsing moves to one helper used by nests and drawings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
2.9 KiB
C#
74 lines
2.9 KiB
C#
using System.IO.Compression;
|
|
using System.Net.Http.Json;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using OpenNest.Data;
|
|
|
|
namespace OpenNest.Server.Tests;
|
|
|
|
/// <summary>Neutral, code-built drawing library fixtures for the server tests.</summary>
|
|
internal static class DrawingLibraryFixtures
|
|
{
|
|
public static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web)
|
|
{ Converters = { new JsonStringEnumConverter() } };
|
|
|
|
public static DrawingLibraryRecord Record(string name = "Synthetic bracket", Guid? id = null) => new()
|
|
{
|
|
Id = id ?? Guid.NewGuid(),
|
|
Name = name,
|
|
Customer = "Synthetic customer",
|
|
MaterialName = "Synthetic Steel",
|
|
MaterialGrade = "Grade A",
|
|
Thickness = 0.25,
|
|
Units = Units.Inches,
|
|
CutType = CutType.Laser,
|
|
Notes = "Synthetic notes",
|
|
SavedBy = "Synthetic operator",
|
|
};
|
|
|
|
/// <summary>A real ZIP archive whose single entry holds <paramref name="content"/>.</summary>
|
|
public static byte[] Package(string content = "synthetic")
|
|
{
|
|
using var buffer = new MemoryStream();
|
|
using (var archive = new ZipArchive(buffer, ZipArchiveMode.Create, leaveOpen: true))
|
|
{
|
|
var entry = archive.CreateEntry("nest.json");
|
|
entry.LastWriteTime = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
|
using var stream = entry.Open();
|
|
stream.Write(Encoding.UTF8.GetBytes(content));
|
|
}
|
|
return buffer.ToArray();
|
|
}
|
|
|
|
public static string Sha256(byte[] bytes) => Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
|
|
|
public static MultipartFormDataContent Body(string metadataJson, byte[]? package)
|
|
{
|
|
var body = new MultipartFormDataContent();
|
|
body.Add(new StringContent(metadataJson), "metadata");
|
|
if (package is not null)
|
|
body.Add(new ByteArrayContent(package), "package", "drawing.nest");
|
|
return body;
|
|
}
|
|
|
|
public static MultipartFormDataContent Body(DrawingLibraryRecord record, byte[]? package) =>
|
|
Body(JsonSerializer.Serialize(record, Options), package);
|
|
|
|
public static async Task<DrawingLibraryRecord> Upload(HttpClient client, DrawingLibraryRecord record, byte[] package)
|
|
{
|
|
using var response = await client.PostAsync("/api/drawings", Body(record, package));
|
|
response.EnsureSuccessStatusCode();
|
|
return (await response.Content.ReadFromJsonAsync<DrawingLibraryRecord>(Options))!;
|
|
}
|
|
|
|
public static HttpRequestMessage Guarded(HttpMethod method, string url, string? ifMatch, HttpContent? content = null)
|
|
{
|
|
var request = new HttpRequestMessage(method, url) { Content = content };
|
|
if (ifMatch is not null)
|
|
request.Headers.TryAddWithoutValidation("If-Match", ifMatch);
|
|
return request;
|
|
}
|
|
}
|