FruitBankHybridApp/FruitBankHybrid.Shared.Tests/ToonTests.cs

461 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AyCode.Core.Enums;
using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Serializers.Toons;
using FruitBank.Common;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities;
using FruitBank.Common.Loggers;
using FruitBankHybrid.Shared.Services.SignalRs;
using Mango.Nop.Core.Entities;
using Newtonsoft.Json;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using System.Linq.Expressions;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Serialization;
using System.Text;
namespace FruitBankHybrid.Shared.Tests;
//1. "Headered List" (A biztonságos táblázatosítás)
//Az LLM-eknek nem kell minden sorban megismételni a mezőneveket, ha a lista elején egyszer definiálod a sorrendet.Ez nem találgatás, hanem egy lokális "szerződés".
//Hagyományos (pazarló):
//Kódrészlet
//OrderItemDtos = [
// OrderItemDto { Id = 120, Quantity = 10, ProductName = "Áfonya" }
//OrderItemDto { Id = 121, Quantity = 5, ProductName = "Narancs" }
//]
//Optimalizált(pontos és tömör) :
//Kódrészlet
//OrderItemDtos: OrderItemDto[] = [
// [Id, Quantity, ProductName]
// [120, 10, "Áfonya"]
// [121, 5, "Narancs"]
//]
// Miért jó ez? Az LLM a fejléc alapján(mint egy CSV-nél) rendeli hozzá az értékeket a típushoz.Mivel a típus (OrderItemDto) ott van a definícióban, a szemantikai kapcsolat nem vész el.
//2. Típus-öröklődés a listákban
//Ha a @types részben már leírtad, hogy az OrderItemDto.ProductDto mezője egy ProductDto típust vár, akkor a @data részben felesleges kiírni a típusnevet minden egyes elemnél.
//Példa:
//Kódrészlet
//// A 'ProductDto' elhagyható az objektum elől, mert a sémából tudja
//ProductDto = {
// Id = 1
// Name = "Áfonya..."
// GenericAttributes = [
// { Id = 99, Key = "NetWeight", Value = "178.3" }
// { Id = 100, Key = "GrossWeight", Value = "19" }
// ]
//}
//3. Alapértelmezett értékek elhagyása(Implicit Defaults)
//Ha egy mező értéke megegyezik a @types - ban definiált default-value-val, vagy null/0/false, akkor azt teljesen hagyd ki a @data részből.
// Szabály: Ami nincs ott, az az alapértelmezett.
// Token megtakarítás: A FruitBank példádban a GenericAttributes = < GenericAttributeDto[] > (count: 0)[] sorok rengeteg helyet foglalnak. Ha üres, egyszerűen ne küldd el a mezőt.
//4. String Table helyett: "Object Anchoring"
//használd az objektum-referenciákat (amit a @ProductDto:1 jelöléssel már el is kezdett a rendszered).
//Ha ugyanaz a Product szerepel 5 különböző rendelési tételnél, ne írd le ötször.
// Első alkalommal: ProductDto { ... }
//Minden további alkalommal: ProductDto = @ProductDto:1
//[ToonIgnore][ToonDataIgnore]
[ToonDescription(Purpose = "Container model for Shipping, Order")]
public class FullProcessModel
{
public List<Shipping> Shippings { get; set; }
public List<OrderDto> Orders { get; set; }
public List<StockTaking> StockTakings { get; set; }
}
[TestClass]
public sealed class ToonTests
{
private const int CustomerIdAasdDsserverCom = 6; //aasd@dsserver.com
private FruitBankSignalRClient _signalRClient = null!;
[TestInitialize]
public void TestInit()
{
if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");
_signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
}
#if DEBUG
[TestMethod]
public async Task GetAnalyzeStringInternCandidatesLog()
{
var orders = (await _signalRClient.GetAllOrderDtos())!.Where(x=>x.CreatedOnUtc > DateTime.UtcNow.AddDays(-70)).ToList();
var options = AcBinarySerializerOptions.WithoutReferenceHandling;
//options.SetReferenceHandlingUnsafe(ReferenceHandlingMode.OnlyId);
var analysisLog = AcBinarySerializer.GetAnalyzeStringInternCandidatesLog(orders, options);
Assert.IsNotNull(analysisLog);
Assert.IsGreaterThan(0, analysisLog.Length);
// Print results sorted by occurrence count
Console.WriteLine(analysisLog.ToString());
Console.WriteLine();
}
#endif
[TestMethod]
public async Task OrderDtoToToon()
{
var a = new FullProcessModel();
a.Orders = (await _signalRClient.GetAllOrderDtos())!.Where(x=>x.CreatedOnUtc > DateTime.UtcNow.AddDays(-70)).ToList();
a.Shippings = (await _signalRClient.GetShippings())!.Where(x=>x.Created > DateTime.UtcNow.AddDays(-70)).ToList();
var toon = AcToonSerializer.Serialize(a, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
//var toon = AcToonSerializer.SerializeTypeMetadata<FullProcessModel>(FruitBankConstClient.DomainDescription);
Console.WriteLine(toon);
Assert.IsNotEmpty(toon);
// Note: @ref: only appears when the same object instance is referenced multiple times.
// Data from separate API calls typically don't share object instances.
}
[TestMethod]
public void GetMetaInfos()
{
var a = new FullProcessModel();
var toon = AcToonSerializer.SerializeTypeMetadata<FullProcessModel>(FruitBankConstClient.DomainDescription);
Console.WriteLine(toon);
}
[TestMethod]
public void ReferenceHandling_WithSharedReferences_ShouldOutputRefMarkers()
{
// Create a simple test container with shared references
var sharedProduct = new ProductDto { Id = 1, Name = "Shared Product" };
// Create a container that references the same ProductDto twice
var container = new TestContainerWithSharedRefs
{
Product1 = sharedProduct,
Product2 = sharedProduct // Same instance, should create @ref
};
var toon = AcToonSerializer.Serialize(container, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
Console.WriteLine(toon);
Assert.IsNotEmpty(toon);
Assert.IsTrue(toon.Contains("@ref:"), "ReferenceHandling should detect shared Product instance");
}
[TestMethod]
public void ReferenceHandling_WithSharedIIdReferences_ShouldOutputRefMarkers()
{
// Create a simple test container with shared references
var sharedProduct = new ProductDto { Id = 1, Name = "Shared Product" };
var sharedProduct2 = new ProductDto { Id = 1, Name = "Shared Product" };
// Create a container that references the same ProductDto twice
var container = new TestContainerWithSharedRefs
{
Product1 = sharedProduct,
Product2 = sharedProduct2 // Same instance, should create @ref
};
var toon = AcToonSerializer.Serialize(container, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
Console.WriteLine(toon);
Assert.IsNotEmpty(toon);
Assert.IsTrue(toon.Contains("@ref:"), "ReferenceHandling should detect shared Product instance");
}
[TestMethod]
public void ToonTypes_ShouldNotContainList1OrGenericTypeNames()
{
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
StringAssert.DoesNotMatch(toon, new System.Text.RegularExpressions.Regex(@"List`?1"), "A @meta.types vagy @types szekcióban nem szerepelhet List`1 vagy generikus típusnév.");
}
[TestMethod]
public void ToonTypes_ShouldNotContainDuplicateTypeNames()
{
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
var typesLine = toon.Split('\n').FirstOrDefault(x => x.TrimStart().StartsWith("types = ["));
Assert.IsNotNull(typesLine, "Nem található types lista a @meta szekcióban.");
var typeNames = typesLine.Substring(typesLine.IndexOf('[') + 1, typesLine.LastIndexOf(']') - typesLine.IndexOf('[') - 1)
.Split(',').Select(x => x.Trim(' ', '"')).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
var duplicates = typeNames.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key).ToList();
Assert.IsTrue(duplicates.Count == 0, $"A types listában duplikált típusnév található: {string.Join(", ", duplicates)}");
}
[TestMethod]
public void ToonTypes_EachTypeShouldBeDefinedOnceInTypesSection()
{
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
var typeDefLines = toon.Split('\n').Where(x => x.TrimEnd().EndsWith(": \"Object of type") || x.TrimEnd().EndsWith(": enum") || x.TrimEnd().EndsWith(": \"Object of type "));
var typeNames = typeDefLines.Select(x => x.Trim().Split(':')[0]).ToList();
var duplicates = typeNames.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key).ToList();
Assert.IsTrue(duplicates.Count == 0, $"A @types szekcióban duplikált típusdefiníció található: {string.Join(", ", duplicates)}");
}
[TestMethod]
public void ToonTypes_PropertyTypesShouldNotReferenceList1()
{
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
var lines = toon.Split('\n');
foreach (var line in lines)
{
if (line.Trim().EndsWith(": List`1"))
{
Assert.Fail($"Property List`1 típusra hivatkozik: {line.Trim()}");
}
}
}
[TestMethod]
public void ToonTypes_PropertyDescriptions_ShouldNotBeRedundantOrMisleading()
{
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>(FruitBankConstClient.DomainDescription);
var lines = toon.Split('\n');
foreach (var line in lines)
{
if (line.Trim().StartsWith("description:") && line.Contains("Collection of Object for"))
{
Assert.Fail($"Redundáns vagy félrevezető description: {line.Trim()}");
}
}
}
[TestMethod]
public void ToonTypes_NavigationMetadata_ShouldBeComplete()
{
var toon = AcToonSerializer.SerializeMetadata(FruitBankConstClient.DomainDescription, [typeof(Shipping), typeof(OrderDto), typeof(StockTaking), typeof(StockQuantityHistory), typeof(StockQuantityHistoryExt)]);
Console.WriteLine(toon);
Console.WriteLine("\n=== NAVIGATION METADATA ELLENŐRZÉS ===\n");
var lines = toon.Split('\n').Select(x => x.TrimEnd()).ToList();
// Ismerten egyirányú kapcsolatok - ezeknél nincs inverse property a másik oldalon
// Customer: NopCommerce domain entity, nincs benne Orders kollekció
// OrderNotes: OrderNote osztályban nincs Order navigation property
// ProductDto: nincs benne OrderItems kollekció
// GenericAttributes: polimorf kapcsolat ExpressionPredicate-tel, nincs inverse ÉS nincs egyértelmű FK
// FONTOS: Csak az inverse-property hiányát engedjük! Az other-key-nek léteznie kell!
var knownUnidirectionalNavigations = new HashSet<string>
{
"Customer",
"OrderNotes",
"ProductDto",
"GenericAttributes",
"ShippingDocumentFile",
"Pallet"
};
// GenericAttributes speciális eset - polimorf, nincs other-key sem
var knownPolymorphicNavigations = new HashSet<string>
{
"GenericAttributes"
};
// FK property-k NEM tartalmazhatnak foreign-key attribútumot
for (int i = 0; i < lines.Count; i++)
{
var line = lines[i].Trim();
if (line.EndsWith("Id: int") || line.EndsWith("Id: int?"))
{
int j = i + 1;
while (j < lines.Count)
{
if (lines[j].StartsWith(" ") && !lines[j].StartsWith(" "))
break;
if (lines[j].StartsWith(" "))
{
var metaLine = lines[j].Trim();
if (metaLine.StartsWith("foreign-key:"))
{
Assert.Fail($"FK property nem tartalmazhat foreign-key attribútumot: {line} -> {metaLine}");
}
}
j++;
}
}
}
Console.WriteLine("✓ FK property-k nem tartalmaznak foreign-key attribútumot\n");
// Számoljuk meg a hiányzó navigation metadatokat
var missingMetadata = new List<string>();
var skippedUnidirectional = new List<string>();
for (int i = 0; i < lines.Count; i++)
{
var line = lines[i].Trim();
// Navigation property-k keresése
if (line.Contains(": ") &&
!line.Contains(": int") && !line.Contains(": string") &&
!line.Contains(": DateTime") && !line.Contains(": decimal") &&
!line.Contains(": bool") && !line.Contains(": Guid") &&
!line.Contains(": double") && !line.Contains(": float") &&
!line.Contains("description:") && !line.Contains("purpose:") &&
!line.Contains("navigation:") && !line.Contains("foreign-key:") &&
!line.Contains("table-name:") && !line.Contains("constraints:") &&
!line.Contains("inverse-property:") && !line.Contains("other-key:") &&
!line.Contains("primary-key:") && !line.Contains("examples:") &&
!line.Contains("@meta") && !line.Contains("@types") &&
!line.Contains("version") && !line.Contains("format") && !line.Contains("source-code-language") &&
!line.Contains("underlying-type:") && !line.Contains("default-value:") && !line.Contains("values:"))
{
var propName = line.Split(':')[0].Trim();
if (string.IsNullOrEmpty(propName) || propName == "types") continue;
// Következő sorok metadatainak összegyűjtése
var metadata = new HashSet<string>();
int j = i + 1;
while (j < lines.Count && lines[j].StartsWith(" ") && lines[j].Trim().Contains(':'))
{
var metaLine = lines[j].Trim();
if (metaLine.StartsWith("navigation:")) metadata.Add("navigation");
if (metaLine.StartsWith("foreign-key:")) metadata.Add("foreign-key");
if (metaLine.StartsWith("inverse-property:")) metadata.Add("inverse-property");
if (metaLine.StartsWith("other-key:")) metadata.Add("other-key");
j++;
}
// Ha van navigation attribútum, ellenőrizzük a szükséges metadatokat
if (metadata.Contains("navigation"))
{
var navLine = lines.Skip(i + 1).FirstOrDefault(x => x.Trim().StartsWith("navigation:"));
if (navLine != null)
{
var isUnidirectional = knownUnidirectionalNavigations.Contains(propName);
var isPolymorphic = knownPolymorphicNavigations.Contains(propName);
if (navLine.Contains("many-to-one"))
{
if (!metadata.Contains("foreign-key"))
missingMetadata.Add($"{propName} (ManyToOne): hiányzik foreign-key");
if (!metadata.Contains("inverse-property"))
{
if (isUnidirectional)
skippedUnidirectional.Add($"{propName} (ManyToOne): egyirányú kapcsolat, nincs inverse");
else
missingMetadata.Add($"{propName} (ManyToOne): hiányzik inverse-property");
}
}
else if (navLine.Contains("one-to-many"))
{
// other-key: polimorf kapcsolatoknál nem kötelező
if (!metadata.Contains("other-key"))
{
if (isPolymorphic)
{
skippedUnidirectional.Add($"{propName} (OneToMany): polimorf kapcsolat, nincs other-key");
}
else
{
// DEBUG: részletes info
Console.WriteLine($"\n[DEBUG] {propName} (OneToMany) - other-key hiányzik!");
// Keressük meg a property típusát a Toon outputban
var propTypePart = line.Split(':').LastOrDefault()?.Trim() ?? "";
Console.WriteLine($" Property type: {propTypePart}");
// Ha List<X> formátum, keressük meg X-et
if (propTypePart.StartsWith("List<") && propTypePart.EndsWith(">"))
{
var elementTypeName = propTypePart.Substring(5, propTypePart.Length - 6);
Console.WriteLine($" Element type: {elementTypeName}");
// Keressük meg az element type definícióját
var elementTypeDefIndex = lines.FindIndex(l => l.Trim().StartsWith($"{elementTypeName}:"));
if (elementTypeDefIndex >= 0)
{
Console.WriteLine($" Element type definition found at line {elementTypeDefIndex}");
// Listázzuk ki az element type property-jeit amik "Id"-re végződnek
for (int k = elementTypeDefIndex + 1; k < lines.Count; k++)
{
var propLine = lines[k];
if (!propLine.StartsWith(" ")) break; // Új típus definíció
if (propLine.StartsWith(" ")) continue; // Metaadat, skip
var trimmed = propLine.Trim();
if (trimmed.EndsWith(": int") && trimmed.Contains("Id"))
{
Console.WriteLine($" FK candidate: {trimmed}");
}
}
}
}
missingMetadata.Add($"{propName} (OneToMany): hiányzik other-key");
}
}
if (!metadata.Contains("inverse-property"))
{
if (isUnidirectional)
skippedUnidirectional.Add($"{propName} (OneToMany): egyirányú kapcsolat, nincs inverse");
else
missingMetadata.Add($"{propName} (OneToMany): hiányzik inverse-property");
}
}
}
}
}
}
if (skippedUnidirectional.Count > 0)
{
Console.WriteLine("EGYIRÁNYÚ/POLIMORF KAPCSOLATOK (nem hiba):");
foreach (var skipped in skippedUnidirectional)
{
Console.WriteLine($" {skipped}");
}
Console.WriteLine();
}
if (missingMetadata.Count > 0)
{
Console.WriteLine("HIÁNYZÓ METAADATOK:");
foreach (var missing in missingMetadata)
{
Console.WriteLine($" - {missing}");
}
Assert.Fail($"Hiányzó navigation metaadatok: {missingMetadata.Count} db");
}
Console.WriteLine("✓ Minden navigation property tartalmazza a szükséges metadatokat");
}
}
/// <summary>
/// Test helper class to verify reference handling with shared object instances.
/// </summary>
public class TestContainerWithSharedRefs
{
public ProductDto? Product1 { get; set; }
public ProductDto? Product2 { get; set; }
}