Tutto ciò che l'applicazione registra sta ora in due file SQLite in %LocalAppData%\AutoBidder\Database: autobidder.sqlite per le osservazioni e esercizio.sqlite per i documenti d'esercizio (aste nel monitor, prodotti, promozioni, modelli appresi con prefisso ml/) e per i registri applicativo e del riscatto puntate. Il motore comune è SqliteDatabase; i file JSON e la cartella Apprendimento delle versioni precedenti vengono importati la prima volta e lasciati dove sono. La cartella si cambia dalle Impostazioni: al salvataggio si chiede se spostare i file, e si riavvia. Le cartelle Dati/Statistiche/Registri, i registri su file, le misure dell'anticipo e le impostazioni morte (LogBids, AutoApplyProductDefaults, NewAuctionLimitsPriority…) non esistono più. Il kill-switch se ne va: al suo posto un solo interruttore in barra, accanto ad Avvia/Osserva/Ferma, che spegne l'apprendimento. Spento, il motore punta solo entro i limiti dell'utente (prezzo, puntate, budget, fascia oraria, rischio) con l'anticipo fisso: niente valore atteso, regime, duello, bandit, anticipo adattivo. Le aste vengono comunque registrate e studiate. Tre difetti visti osservando il motore dal vivo per un'ora, in Osserva: - la copertura «Compralo Ora» con V lasciato al listino azzerava la perdita coperta e il motore diceva BID a ogni scadenza con P dell'1%: ora vale solo per i prodotti con «Valore reale €» scritto, altrimenti la puntata si conta persa e P decide; - la chiusura di un'asta arrivava due volte (fine, poi rimozione dal monitor) e di nuovo al riavvio: modello, profilo, bandit e statistiche per prodotto contavano ogni asta due volte. Il monitor la comunica una volta sola, l'apprendimento salta le aste già apprese, e le schede prodotto vengono ricostruite una volta dallo storico; - la pagina delle ricompense veniva scambiata per la pagina di accesso perché porta un collegamento a /login.php: prima si guarda se è la pagina delle ricompense. Interfaccia: la scheda Esporta e tutto il suo codice sono tolti; le esportazioni chiedono sempre dove salvare. Prodotti, Storico e Apprendimento hanno barre a sole icone con tooltip che dicono esattamente cosa succede, colorate come il monitor, con i nuovi pulsanti di pulizia (spegni tutte le stelline, togli i non seguiti, pulizia completa; seleziona tutte / elimina le selezionate / svuota lo storico). Impostazioni: sezione Database, Manutenzione con «Elimina tutti i dati (tranne la login)» e «Impostazioni di fabbrica», esportazione dei registri in testo. Modifiche.txt, il prompt ormai realizzato, esce dal repository. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
111 lines
3.8 KiB
C#
111 lines
3.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using AutoBidder.Data;
|
|
using AutoBidder.Utilities;
|
|
using Xunit;
|
|
|
|
namespace AutoBidder.Tests;
|
|
|
|
/// <summary>
|
|
/// Il database d'esercizio: documenti per chiave, importazione una tantum dei vecchi
|
|
/// file JSON, registri che si scrivono, si rileggono, si esportano e scadono.
|
|
/// </summary>
|
|
[Collection("Database")]
|
|
public class OperationalDatabaseTests
|
|
{
|
|
private static OperationalDatabase Db => OperationalDatabase.Instance;
|
|
|
|
[Fact]
|
|
public void Un_documento_si_scrive_e_si_rilegge()
|
|
{
|
|
var key = "test/" + Guid.NewGuid().ToString("N");
|
|
Db.SetDocument(key, "{\"a\":1}");
|
|
Assert.Equal("{\"a\":1}", Db.GetDocument(key));
|
|
|
|
Db.SetDocument(key, "{\"a\":2}");
|
|
Assert.Equal("{\"a\":2}", Db.GetDocument(key));
|
|
|
|
Db.DeleteDocument(key);
|
|
Db.Flush();
|
|
Assert.Null(Db.GetDocument(key));
|
|
}
|
|
|
|
[Fact]
|
|
public void Il_vecchio_file_si_importa_una_volta_sola()
|
|
{
|
|
var key = "test/import-" + Guid.NewGuid().ToString("N");
|
|
var legacy = Path.Combine(Path.GetTempPath(), $"legacy-{Guid.NewGuid():N}.json");
|
|
File.WriteAllText(legacy, "{\"vecchio\":true}");
|
|
|
|
try
|
|
{
|
|
Assert.Equal("{\"vecchio\":true}", Db.GetDocumentOrImport(key, legacy));
|
|
|
|
// Il file cambia, ma il documento è già nel database: comanda il database.
|
|
File.WriteAllText(legacy, "{\"vecchio\":false}");
|
|
Assert.Equal("{\"vecchio\":true}", Db.GetDocumentOrImport(key, legacy));
|
|
Assert.True(File.Exists(legacy)); // resta dov'è
|
|
}
|
|
finally { File.Delete(legacy); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Senza_documento_ne_file_si_ha_null()
|
|
{
|
|
Assert.Null(Db.GetDocumentOrImport("test/niente-" + Guid.NewGuid().ToString("N"), Path.Combine(Path.GetTempPath(), "non-esiste.json")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Il_registro_si_scrive_si_rilegge_e_si_esporta()
|
|
{
|
|
var marker = "riga-" + Guid.NewGuid().ToString("N");
|
|
Db.WriteLog(OperationalDatabase.AppLog, "INFO", marker);
|
|
Db.WriteLog(OperationalDatabase.AppLog, "WARN", marker + "-due");
|
|
|
|
var rows = Db.ReadLog(OperationalDatabase.AppLog, 10);
|
|
Assert.Contains(rows, r => r.Message == marker && r.Level == "INFO");
|
|
Assert.Equal(marker + "-due", rows[0].Message); // dalla più recente
|
|
|
|
var file = Path.Combine(Path.GetTempPath(), $"log-{Guid.NewGuid():N}.txt");
|
|
try
|
|
{
|
|
var n = Db.ExportLog(OperationalDatabase.AppLog, file);
|
|
Assert.True(n >= 2);
|
|
var text = File.ReadAllText(file);
|
|
Assert.Contains(marker, text);
|
|
Assert.Contains("[WARN]", text);
|
|
}
|
|
finally { File.Delete(file); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Le_righe_vecchie_scadono()
|
|
{
|
|
var old = SqliteDatabase.Iso(DateTime.Now.AddDays(-200));
|
|
Db.ExecuteNow($"INSERT INTO {OperationalDatabase.FreeBidsLog}(ts, level, message) VALUES(?1, 'INFO', 'antica')", old);
|
|
Db.WriteLog(OperationalDatabase.FreeBidsLog, "INFO", "recente");
|
|
|
|
var removed = Db.PurgeLogs(90);
|
|
|
|
Assert.True(removed >= 1);
|
|
var rows = Db.ReadLog(OperationalDatabase.FreeBidsLog, 100);
|
|
Assert.DoesNotContain(rows, r => r.Message == "antica");
|
|
Assert.Contains(rows, r => r.Message == "recente");
|
|
}
|
|
|
|
[Fact]
|
|
public void Le_aste_nel_monitor_vivono_nel_documento()
|
|
{
|
|
PersistenceManager.SaveAuctions(new[]
|
|
{
|
|
new Models.AuctionInfo { AuctionId = "doc-1", Name = "Uno" },
|
|
new Models.AuctionInfo { AuctionId = "doc-2", Name = "Due" }
|
|
});
|
|
|
|
var back = PersistenceManager.LoadAuctions();
|
|
Assert.Equal(2, back.Count);
|
|
Assert.Contains(back, a => a.AuctionId == "doc-2" && a.Name == "Due");
|
|
}
|
|
}
|