// Banco di prova del livello dati (fuori dal pacchetto RPM). // Compila con Qt 5.15 locale e gira con XDG_CONFIG_HOME su una cartella // temporanea, cosi' NON tocca la configurazione vera: // sh tests/cpp_test.sh #include #include #include #include #include #include #include #include #include #include #include "../../src/datastore.h" static int failures = 0; static void check(const QString &name, bool ok, const QString &detail = QString()) { if (!ok) failures++; qInfo().noquote() << (ok ? "OK " : "FAIL") << name << (ok || detail.isEmpty() ? QString() : "-> " + detail); } static QVariantList makeBreeds() { QVariantList hens; QVariantMap m1; m1.insert("id", "m1"); m1.insert("name", "Marta"); m1.insert("birthDate", "2025-01-01T00:00:00.000"); m1.insert("isLaying", true); hens.append(m1); QVariantMap marans; marans.insert("breedId", "marans"); marans.insert("hens", hens); return QVariantList() << marans; } static QVariantList makeReservations() { QVariantMap r; r.insert("id", "r1"); r.insert("customerName", "Cli1"); r.insert("breedId", "araucana"); r.insert("eggsRequested", 2); r.insert("createdAt", "2026-04-25T09:00:00.000"); r.insert("deliveryDate", "2026-04-28"); r.insert("isDelivered", false); return QVariantList() << r; } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); app.setOrganizationName("harbour"); app.setApplicationName("henflow"); qInfo().noquote() << "config dir:" << QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); // --- primo avvio: nessun file, stato vuoto ----------------------------- { DataStore store; check("primo avvio: nessuna razza", store.breedSettings().isEmpty()); check("primo avvio: nessuna prenotazione", store.reservations().isEmpty()); check("primo avvio: magazzino vuoto", store.warehouseStock().isEmpty()); check("primo avvio: nessuna data di aggiornamento", store.lastProductionUpdate().isEmpty()); check("il file dati non esiste ancora", !QFile::exists(store.dataFilePath())); } // --- scrittura e rilettura -------------------------------------------- DataStore store; check("salvataggio razze", store.saveBreedSettings(makeBreeds())); check("salvataggio prenotazioni", store.saveReservations(makeReservations())); QVariantMap customers; customers.insert("id", "c1"); customers.insert("firstName", "Anna"); customers.insert("lastName", "Rossi"); customers.insert("phoneNumber", "+39 333 0000000"); check("salvataggio clienti", store.saveCustomers(QVariantList() << customers)); QVariantMap stock; stock.insert("marans", 6.0); stock.insert("araucana", 1.5); check("salvataggio magazzino", store.saveWarehouseStock(stock)); check("salvataggio data aggiornamento", store.saveLastProductionUpdate("2026-10-09")); check("il file dati esiste", QFile::exists(store.dataFilePath())); { DataStore reopened; check("rilettura razze", reopened.breedSettings().size() == 1); check("rilettura nidificata (galline)", reopened.breedSettings().at(0).toMap() .value("hens").toList().size() == 1); check("rilettura prenotazioni", reopened.reservations().size() == 1); check("rilettura quantita' uova", reopened.reservations().at(0).toMap() .value("eggsRequested").toInt() == 2); check("rilettura flag consegna (bool)", reopened.reservations().at(0).toMap() .value("isDelivered").toBool() == false); check("rilettura clienti", reopened.customers().size() == 1 && reopened.customers().at(0).toMap().value("lastName").toString() == "Rossi"); check("rilettura magazzino (numeri)", qFuzzyCompare(reopened.warehouseStock() .value("araucana").toDouble(), 1.5)); check("rilettura data aggiornamento", reopened.lastProductionUpdate() == "2026-10-09"); } // --- contratto con le chiavi dell'app Flutter -------------------------- { QFile f(store.dataFilePath()); f.open(QIODevice::ReadOnly); const QJsonObject root = QJsonDocument::fromJson(f.readAll()).object(); f.close(); check("chiave breed_settings_v2 (compatibile con l'app Android)", root.contains("breed_settings_v2")); check("chiave reservations_v2", root.contains("reservations_v2")); check("chiave customers_v1", root.contains("customers_v1")); check("chiave warehouse_stock_v1", root.contains("warehouse_stock_v1")); check("chiave last_production_update_v1", root.contains("last_production_update_v1")); } // --- backup nel formato dell'app Android ------------------------------- QString exported; { exported = store.exportBackup(); check("export: percorso restituito", !exported.isEmpty(), exported); check("export: file scritto in Documenti", exported.contains(QStringLiteral("/Documents/")), exported); check("export: file esistente", QFile::exists(exported)); QFile f(exported); f.open(QIODevice::ReadOnly); const QJsonObject root = QJsonDocument::fromJson(f.readAll()).object(); f.close(); check("backup: chiave breedSettings (come l'app Android)", root.contains("breedSettings")); check("backup: chiave reservations", root.contains("reservations")); check("backup: chiave customers", root.contains("customers")); check("backup: nessuna chiave interna (breed_settings_v2)", !root.contains("breed_settings_v2")); check("backup: esattamente 3 chiavi", root.size() == 3); } // backup prodotto dall'app Android (scritto a mano come lo esporta lei): // deve essere accettato e sostituire i dati. { const QString android = QStringLiteral( "{\n" " \"breedSettings\": [\n" " {\"breedId\": \"livorno\", \"hens\": [\n" " {\"id\": \"h9\", \"name\": \"Gina\", \"birthDate\": \"2024-03-01T00:00:00.000\", \"isLaying\": true}\n" " ]}\n" " ],\n" " \"reservations\": [\n" " {\"id\": \"r9\", \"customerName\": \"Luca\", \"breedId\": \"livorno\", \"eggsRequested\": 6,\n" " \"createdAt\": \"2026-10-01T08:00:00.000\", \"deliveryDate\": \"2026-10-03\", \"isDelivered\": false}\n" " ],\n" " \"customers\": [\n" " {\"id\": \"c9\", \"firstName\": \"Luca\", \"lastName\": \"Bianchi\", \"phoneNumber\": \"333\"}\n" " ]\n" "}\n"); check("backup Android riconosciuto valido", store.isValidBackup(android)); check("import del backup Android", store.importBackup(android)); check("import: razze sostituite", store.breedSettings().size() == 1 && store.breedSettings().at(0).toMap().value("breedId").toString() == "livorno"); check("import: gallina annidata letta", store.breedSettings().at(0).toMap() .value("hens").toList().at(0).toMap().value("name").toString() == "Gina"); check("import: prenotazioni sostituite", store.reservations().size() == 1 && store.reservations().at(0).toMap().value("eggsRequested").toInt() == 6); check("import: clienti sostituiti", store.customers().size() == 1 && store.customers().at(0).toMap().value("lastName").toString() == "Bianchi"); // il backup Android non porta scorte ne' ultima produzione: si tengono check("import: magazzino preservato", !store.warehouseStock().isEmpty()); check("import: data aggiornamento preservata", store.lastProductionUpdate() == "2026-10-09"); // rilettura da disco: l'import e' stato salvato DataStore reopened; check("import: salvato su disco", reopened.reservations().size() == 1 && reopened.breedSettings().at(0).toMap().value("breedId").toString() == "livorno"); } // backup senza 'customers' (le versioni vecchie non lo avevano) { const QString old = QStringLiteral("{\"breedSettings\": [], \"reservations\": []}"); check("backup senza clienti: valido", store.isValidBackup(old)); check("backup senza clienti: importato", store.importBackup(old)); check("backup senza clienti: liste vuote", store.breedSettings().isEmpty() && store.customers().isEmpty()); } // --- file corrotto: non deve crashare e non deve perdere i dati --------- { QFile f(store.dataFilePath()); f.open(QIODevice::WriteOnly | QIODevice::Truncate); f.write("{ questo non e' json"); f.close(); DataStore broken; check("file corrotto: non crasha", true); check("file corrotto: stato vuoto", broken.reservations().isEmpty()); check("import di un JSON non valido rifiutato", !broken.importBackup("{ rotto")); check("import di un oggetto senza le chiavi richieste rifiutato", !broken.importBackup("{\"altro\": 1}")); check("import di un array (non oggetto) rifiutato", !broken.importBackup("[]")); check("import di testo vuoto rifiutato", !broken.importBackup(" ")); check("isValidBackup su testo vuoto", !broken.isValidBackup("")); } QFile::remove(exported); qInfo().noquote() << (failures == 0 ? "\ntutti i controlli superati" : QString("\n%1 controlli falliti").arg(failures)); return failures == 0 ? 0 : 1; }