Files
Mimante/Mimante/Tests/ExportAndLogTests.cs
T
Alby96andClaude Fable 5.1 9177b31bd5 Due database, interruttore dell'apprendimento, barre a icone; via kill-switch, esportazioni e voci deprecate
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>
2026-09-08 13:58:08 +02:00

173 lines
5.6 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AutoBidder.Models;
using AutoBidder.Utilities;
using Xunit;
namespace AutoBidder.Tests;
/// <summary>
/// I filtri decidono cosa finisce nel file da analizzare. Un filtro che lascia passare
/// un'asta sbagliata non si nota — si notano invece le conclusioni sbagliate che se ne
/// traggono un mese dopo, quando non c'è più modo di capire da dove venivano.
/// </summary>
public class AuctionDetailRecordTests
{
private static AuctionDetailRecord Record(
string id = "1",
string name = "Cuffie Sony",
double finalPrice = 5,
int myBids = 3,
bool won = false,
bool fromStart = true,
bool toEnd = true,
DateTime? endedAt = null) => new()
{
AuctionId = id,
Name = name,
ProductKey = "cuffie-sony",
FinalPrice = finalPrice,
MyBids = myBids,
WonByMe = won,
ObservedFromStart = fromStart,
ObservedToEnd = toEnd,
EndedAt = endedAt ?? DateTime.Now.AddDays(-1)
};
[Fact]
public void Unasta_completa_e_quella_vista_dallinizio_alla_fine()
{
Assert.True(Record(fromStart: true, toEnd: true).IsComplete);
Assert.False(Record(fromStart: false, toEnd: true).IsComplete);
Assert.False(Record(fromStart: true, toEnd: false).IsComplete);
}
[Fact]
public void Il_costo_reale_comprende_le_puntate_spese_e_la_spedizione()
{
// È il numero che dice se conveniva davvero: il solo prezzo finale non lo dice.
var record = Record(finalPrice: 5, myBids: 10);
record.ShippingCost = 6.90;
Assert.Equal(13.90, record.TotalCostIfWon(0.20), 2);
}
[Fact]
public void Il_rapporto_col_valore_del_prodotto_manca_se_il_valore_non_si_conosce()
{
// Meglio nessun numero che uno inventato: un rapporto calcolato su un valore
// assente sarebbe indistinguibile da un affare vero.
Assert.Null(Record().FinalPriceRatio);
var withValue = Record(finalPrice: 30);
withValue.BuyNowPrice = 300;
Assert.Equal(0.1, withValue.FinalPriceRatio!.Value, 3);
}
}
/// <summary>
/// La scheda prodotto tiene i numeri su cui si decidono i limiti. Devono restare coerenti
/// mentre si accumulano, perché nessuno li ricalcola: si sommano una volta sola, quando
/// l'asta finisce.
/// </summary>
public class ProductStatsStoreTests
{
private static CompletedAuctionRecord Completed(
string key,
double price,
int myBids,
bool won,
DateTime? endedAt = null) => new()
{
AuctionId = Guid.NewGuid().ToString("N"),
Name = "Prodotto di prova",
ProductKey = key,
FinalPrice = price,
MyBidsUsed = myBids,
WonByMe = won,
EndedAt = endedAt ?? DateTime.Now
};
private static string NewKey() => "p-" + Guid.NewGuid().ToString("N")[..8];
[Fact]
public void La_prima_asta_crea_la_scheda()
{
var key = NewKey();
ProductStatsStore.RecordCompleted(Completed(key, 5, 2, won: false), null);
var card = ProductStatsStore.Find(key);
Assert.NotNull(card);
Assert.Equal(1, card!.AuctionsCompleted);
Assert.Equal(5, card.LastFinalPrice);
}
[Fact]
public void Le_medie_si_accumulano_senza_rileggere_lo_storico()
{
var key = NewKey();
ProductStatsStore.RecordCompleted(Completed(key, 4, 1, won: false), null);
ProductStatsStore.RecordCompleted(Completed(key, 8, 3, won: true), null);
var card = ProductStatsStore.Find(key)!;
Assert.Equal(2, card.AuctionsCompleted);
Assert.Equal(6, card.AverageFinalPrice);
Assert.Equal(4, card.MinFinalPrice);
Assert.Equal(8, card.MaxFinalPrice);
Assert.Equal(4, card.MyBidsTotal);
}
[Fact]
public void Le_aste_solo_osservate_non_abbassano_la_percentuale_di_vittorie()
{
// Contarle come sconfitte racconterebbe un fallimento mai avvenuto: su quelle
// non si è mai puntato.
var key = NewKey();
ProductStatsStore.RecordCompleted(Completed(key, 5, myBids: 0, won: false), null);
ProductStatsStore.RecordCompleted(Completed(key, 6, myBids: 4, won: true), null);
var card = ProductStatsStore.Find(key)!;
Assert.Equal(2, card.AuctionsCompleted);
Assert.Equal(1, card.AuctionsWithMyBids);
Assert.Equal(100, card.WinRatePercent);
}
// Il prezzo massimo consigliato non e' piu' una proprieta' della scheda prodotto:
// dipende dalla distribuzione delle chiusure, che la scheda non conserva. Vive in
// PriceAdvisor, ed e' provato in PriceAdvisorTests.
[Fact]
public void Le_osservazioni_complete_si_contano_a_parte()
{
var key = NewKey();
var complete = new AuctionDetailRecord { ObservedFromStart = true, ObservedToEnd = true, ObservedMinutes = 30 };
var partial = new AuctionDetailRecord { ObservedFromStart = false, ObservedToEnd = true, ObservedMinutes = 10 };
ProductStatsStore.RecordCompleted(Completed(key, 5, 1, false), complete);
ProductStatsStore.RecordCompleted(Completed(key, 5, 1, false), partial);
var card = ProductStatsStore.Find(key)!;
Assert.Equal(2, card.AuctionsCompleted);
Assert.Equal(1, card.CompleteObservations);
Assert.Equal(20, card.AverageObservedMinutes);
}
[Fact]
public void Un_prodotto_mai_visto_non_ha_scheda()
{
Assert.Null(ProductStatsStore.Find(NewKey()));
Assert.Null(ProductStatsStore.Find(""));
}
}