// Banchi di prova del motore Henflow (node, fuori dal pacchetto RPM). // node tests/engine.test.js // I casi replicano il comportamento dell'app Flutter (lib/main.dart) e i dati // visibili nello screenshot dello store: Marans 2 galline, Araucana 2, // Leghorn 3 (di cui 2 in deposizione) -> 6.0 uova/giorno. var HF = require("../qml/js/henflow.js"); var failures = 0; function check(name, got, expected) { var ok = JSON.stringify(got) === JSON.stringify(expected); if (!ok) failures++; console.log((ok ? "OK " : "FAIL ") + name + " got=" + JSON.stringify(got) + (ok ? "" : " expected=" + JSON.stringify(expected))); } // catalogo (dal breed_catalog.dart: tutte le razze 1.0 uovo/gallina/giorno) var CATALOG = [ { id: "marans", eggsPerHenPerDay: 1.0 }, { id: "araucana", eggsPerHenPerDay: 1.0 }, { id: "olive", eggsPerHenPerDay: 1.0 }, { id: "livorno", eggsPerHenPerDay: 1.0 } ]; function hen(id, laying) { return { id: id, name: id, birthDate: "2025-01-01T00:00:00.000", isLaying: laying }; } var BREEDS = [ { breedId: "marans", hens: [hen("m1", true), hen("m2", true)] }, { breedId: "araucana", hens: [hen("a1", true), hen("a2", true)] }, { breedId: "livorno", hens: [hen("l1", true), hen("l2", true), hen("l3", false)] } ]; var TODAY = new Date(2026, 3, 27); // 27 aprile 2026, come nello screenshot // --- produzione --------------------------------------------------------- check("produzione marans (2 galline)", HF.breedDailyProduction(BREEDS, CATALOG, "marans"), 2.0); check("produzione livorno (2 su 3 in deposizione)", HF.breedDailyProduction(BREEDS, CATALOG, "livorno"), 2.0); check("produzione totale = 6.0 uova/giorno", HF.totalDailyProduction(BREEDS, CATALOG), 6.0); check("razza non configurata -> 0", HF.breedDailyProduction(BREEDS, CATALOG, "olive"), 0); check("dettaglio razze attive", HF.productionDetail(BREEDS, CATALOG).map(function (e) { return e.daily; }), [2, 2, 2]); // --- eta' e produttivita' (soglia 6 mesi) -------------------------------- check("eta' 6 mesi compiuti", HF.ageInMonths(new Date(2025, 9, 27), TODAY), 6); check("un giorno prima dei 6 mesi -> 5", HF.ageInMonths(new Date(2025, 9, 28), TODAY), 5); check("6 mesi -> produttiva", HF.isProductive(new Date(2025, 9, 27), TODAY), true); check("5 mesi -> non ancora produttiva", HF.isProductive(new Date(2025, 10, 28), TODAY), false); // --- magazzino: accrual all'apertura ------------------------------------- check("3 giorni di produzione", HF.accrueStock({}, BREEDS, CATALOG, 3), { marans: 6, araucana: 6, livorno: 6 }); check("scorte esistenti incrementate", HF.accrueStock({ marans: 2 }, BREEDS, CATALOG, 1), { marans: 4, araucana: 2, livorno: 2 }); check("nessun giorno passato -> invariate", HF.accrueStock({ marans: 1 }, BREEDS, CATALOG, 0), { marans: 1 }); // --- data di consegna per una nuova prenotazione ------------------------- check("scorte sufficienti -> oggi", HF.estimateDeliveryDate([], { araucana: 3 }, BREEDS, CATALOG, "araucana", 2, TODAY), "2026-04-27"); check("2 uova senza scorte, 2/giorno -> domani", HF.estimateDeliveryDate([], {}, BREEDS, CATALOG, "araucana", 2, TODAY), "2026-04-28"); check("5 uova senza scorte, 2/giorno -> 3 giorni (ceil)", HF.estimateDeliveryDate([], {}, BREEDS, CATALOG, "araucana", 5, TODAY), "2026-04-30"); check("ordini pendenti contano nelle scorte richieste", HF.estimateDeliveryDate([res("p", "araucana", 4)], {}, BREEDS, CATALOG, "araucana", 2, TODAY), "2026-04-30"); check("razza senza galline in deposizione -> oggi (produzione 0)", HF.estimateDeliveryDate([], {}, BREEDS, CATALOG, "olive", 3, TODAY), "2026-04-27"); // --- coda FIFO ------------------------------------------------------------ function res(id, breedId, eggs, createdDay, delivered) { return { id: id, customerName: "Cli", breedId: breedId, eggsRequested: eggs, createdAt: "2026-04-2" + createdDay + "T09:00:00.000", deliveryDate: "2026-04-20T00:00:00.000", isDelivered: !!delivered }; } var queue = HF.refreshDeliveryDates( [res("b", "araucana", 2, 5), res("a", "araucana", 1, 1)], { araucana: 0 }, BREEDS, CATALOG, TODAY); // ordine FIFO: "a" (creato prima) consuma la prima produzione check("FIFO: ordine piu' vecchio prima", [queue[0].id, queue[1].id], ["a", "b"]); check("FIFO: date calcolate in ordine di arrivo", [queue[0].deliveryDate, queue[1].deliveryDate], ["2026-04-28", "2026-04-29"]); check("ordine evaso subito con scorte", HF.refreshDeliveryDates([res("c", "araucana", 2, 1)], { araucana: 2 }, BREEDS, CATALOG, TODAY)[0].deliveryDate, "2026-04-27"); check("ordine consegnato: data invariata e nessun consumo di scorte", HF.refreshDeliveryDates([res("d", "araucana", 99, 1, true), res("e", "araucana", 1, 2)], { araucana: 1 }, BREEDS, CATALOG, TODAY)[0].deliveryDate, "2026-04-20"); check("razza a produzione 0: data non toccata", HF.refreshDeliveryDates([res("f", "olive", 3, 1)], {}, BREEDS, CATALOG, TODAY)[0].deliveryDate, "2026-04-20"); check("la coda modificata non altera l'input (immutabilita')", (function () { var input = [res("g", "araucana", 1, 1)]; HF.refreshDeliveryDates(input, {}, BREEDS, CATALOG, TODAY); return input[0].deliveryDate; })(), "2026-04-20T00:00:00.000"); // --- stato della card ----------------------------------------------------- check("stato: scaduto", HF.statusKey(-1), "late"); check("stato: oggi", HF.statusKey(0), "today"); check("stato: domani", HF.statusKey(1), "tomorrow"); check("stato: fra N giorni", HF.statusKey(4), "in_days"); // --- date ----------------------------------------------------------------- check("dayDiff ignora l'ora", HF.dayDiff(new Date(2026, 3, 27, 23, 59), new Date(2026, 3, 28, 0, 1)), 1); check("parseIso -> solo il giorno", HF.isoDay(HF.parseIso("2026-04-28T09:30:00.000")), "2026-04-28"); check("isoDay con padding", HF.isoDay(new Date(2026, 0, 5)), "2026-01-05"); check("formatDay gg/mm/aaaa (come la card Android)", HF.formatDay("2026-04-28"), "28/04/2026"); check("formatDay su stringa vuota", HF.formatDay(""), ""); check("nowIso in formato ISO", /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.000$/.test(HF.nowIso()), true); check("newId unico", HF.newId() !== HF.newId(), true); // --- fedelta' dei campi degli ordini (app Android) ------------------------ var withNote = { id: "n1", customerName: "Anna", breedId: "marans", eggsRequested: 2, createdAt: "2026-04-25T09:00:00.000", deliveryDate: "2026-04-28T00:00:00.000", isDelivered: false, source: "client", clientNote: "guscio chiaro" }; var kept = HF.refreshDeliveryDates([withNote], {}, BREEDS, CATALOG, TODAY); check("nota del cliente conservata nel ricalcolo delle date", kept[0].clientNote, "guscio chiaro"); check("origine dell'ordine conservata", kept[0].source, "client"); // --- consegna: le uova escono dal magazzino ------------------------------- var stock0 = { "marans": 10, "araucana": 3 }; var afterDelivery = HF.applyDelivery(stock0, { breedId: "marans", eggsRequested: 4 }, true); check("consegna: uova scalate dal magazzino della razza", afterDelivery.marans, 6); check("consegna: le altre razze non si toccano", afterDelivery.araucana, 3); check("consegna: magazzino di partenza non modificato", stock0.marans, 10); check("consegna annullata: uova rimesse nel magazzino", HF.applyDelivery(afterDelivery, { breedId: "marans", eggsRequested: 4 }, false).marans, 10); check("consegna su razza senza scorte: il magazzino va sotto zero", HF.applyDelivery({}, { breedId: "olive", eggsRequested: 3 }, true).olive, -3); console.log(failures === 0 ? "\ntutti i controlli superati" : "\n" + failures + " controlli falliti"); process.exit(failures === 0 ? 0 : 1);