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 Shippings { get; set; } public List Orders { get; set; } public List 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(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(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(); 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(); 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(); 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(); 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(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 { "Customer", "OrderNotes", "ProductDto", "GenericAttributes", "ShippingDocumentFile", "Pallet" }; // GenericAttributes speciális eset - polimorf, nincs other-key sem var knownPolymorphicNavigations = new HashSet { "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(); var skippedUnidirectional = new List(); 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(); 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 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"); } } /// /// Test helper class to verify reference handling with shared object instances. /// public class TestContainerWithSharedRefs { public ProductDto? Product1 { get; set; } public ProductDto? Product2 { get; set; } }