using Microsoft.Data.Sqlite; using PrepStep; namespace PrepStep.Tests; /// End-to-end: disco CAD finto -> Indexer.ScanAsync -> cad_index.db reale -> BFS di /// risoluzione albero componenti (stesso algoritmo di Form1.RisolviAlberoComponenti e /// scripts/BomViaDb.cs, che ora delegano entrambi ad AssemblyComponentScanner - vedi /// docs/formato-asm.md). Nessuna UI coinvolta: verifica che le tre fasi si compongano /// correttamente, non solo che ciascuna funzioni isolata. I contenuti .asm/.prt sono testo /// sintetico con gli stessi marcatori documentati per un vero file Creo ("Componente ID N /// (...)"), non file binari reali - sufficiente perché AssemblyComponentScanner legge il file /// come testo Latin1 indipendentemente dal resto del formato. public class PipelineEndToEndTests : IDisposable { private readonly string _root = Path.Combine(Path.GetTempPath(), $"cadsearch_e2e_{Guid.NewGuid():N}"); private readonly string _dbPath; public PipelineEndToEndTests() { Directory.CreateDirectory(_root); _dbPath = Path.Combine(_root, "indice.db"); } public void Dispose() { SqliteConnection.ClearAllPools(); Directory.Delete(_root, recursive: true); } private static void ScriviFile(string percorso, string contenuto) { Directory.CreateDirectory(Path.GetDirectoryName(percorso)!); File.WriteAllText(percorso, contenuto); } /// Stesso algoritmo BFS di scripts/BomViaDb.cs: qui non è duplicazione a rischio /// come lo era prima della riscrittura (vedi docs/formato-asm.md) - è la prova, in un /// test, che comporre AssemblyComponentScanner con un semplice ciclo di accodamento dà il /// risultato atteso sopra un indice reale, non un mock. private static (SortedSet Trovati, List NonTrovati) RisolviAlbero( string asmIniziale, Dictionary> codiciNoti) { string codiceProprio = Path.GetFileName(asmIniziale).Split('.')[0]; string codiceRadice = AssemblyComponentScanner.CodiceRadice(codiceProprio); var trovati = new SortedSet(StringComparer.OrdinalIgnoreCase); var nonTrovati = new List(); var visti = new HashSet(StringComparer.OrdinalIgnoreCase) { codiceProprio, codiceRadice }; var coda = new Queue(new[] { asmIniziale }); while (coda.Count > 0) { var asm = coda.Dequeue(); foreach (var codice in AssemblyComponentScanner.EstraiCodiciReferenziati(asm, codiciNoti.Keys)) { if (!visti.Add(codice)) continue; if (!codiciNoti.TryGetValue(codice, out var componenti)) { nonTrovati.Add(codice); continue; } trovati.Add(codice); foreach (var (tipo, percorso) in componenti) if (tipo == "asm") coda.Enqueue(percorso); } } return (trovati, nonTrovati); } private Dictionary> CaricaCodiciNoti() { using var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly"); connection.Open(); using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT codice, tipo_file, percorso_completo FROM files_ultima_versione " + "WHERE LOWER(tipo_file) IN ('prt', 'asm')"; using var reader = cmd.ExecuteReader(); var codiciNoti = new Dictionary>(StringComparer.OrdinalIgnoreCase); while (reader.Read()) { string codice = reader.GetString(0); var lista = codiciNoti.TryGetValue(codice, out var l) ? l : codiciNoti[codice] = new(); lista.Add((reader.GetString(1), reader.GetString(2))); } return codiciNoti; } [Fact] public async Task Risolve_lalbero_completo_a_partire_dal_disco_indicizzato() { // TOP -> CHILD (vero placement, 2 occorrenze) -> LEAF (vero placement, 2 occorrenze). // "99999999" compare una sola volta in TOP: rumore binario, va scartato anche se // fosse indicizzato (vedi AssemblyComponentScannerTests.Scarta_codici_con_una_sola_occorrenza). ScriviFile(Path.Combine(_root, "MACCHINE", "GRUPPO1", "12345678.asm.1"), "inizio Componente ID 1 (23456789.ASM) testo Componente ID 2 (23456789.ASM) rumore 99999999 fine"); ScriviFile(Path.Combine(_root, "MACCHINE", "GRUPPO1", "23456789.asm.1"), "Componente ID 1 (34567890.PRT) blah Componente ID 2 (34567890.PRT) fine"); ScriviFile(Path.Combine(_root, "MACCHINE", "GRUPPO1", "34567890.prt.1"), "dati foglia"); ScriviFile(Path.Combine(_root, "MACCHINE", "GRUPPO1", "99999999.prt.1"), "mai referenziato davvero"); var progress = new Progress(); await Indexer.ScanAsync(_root, _dbPath, progress, CancellationToken.None); var codiciNoti = CaricaCodiciNoti(); var asmIniziale = Path.Combine(_root, "MACCHINE", "GRUPPO1", "12345678.asm.1"); var (trovati, nonTrovati) = RisolviAlbero(asmIniziale, codiciNoti); Assert.Equal(new[] { "23456789", "34567890" }, trovati); Assert.DoesNotContain("99999999", trovati); Assert.Empty(nonTrovati); } [Fact] public async Task Segnala_come_non_trovato_un_componente_con_placement_vero_ma_mai_indicizzato() { // "23456789" ha un vero placement in TOP ma il suo file non esiste sul disco finto // (mai scansionato -> non in cad_index.db): deve finire tra i "non trovati", non far // fallire la risoluzione dell'albero. ScriviFile(Path.Combine(_root, "MACCHINE", "GRUPPO1", "12345678.asm.1"), "Componente ID 1 (23456789.ASM) testo Componente ID 2 (23456789.ASM) fine"); var progress = new Progress(); await Indexer.ScanAsync(_root, _dbPath, progress, CancellationToken.None); var codiciNoti = CaricaCodiciNoti(); var asmIniziale = Path.Combine(_root, "MACCHINE", "GRUPPO1", "12345678.asm.1"); var (trovati, nonTrovati) = RisolviAlbero(asmIniziale, codiciNoti); Assert.Empty(trovati); Assert.Contains("23456789", nonTrovati); } [Fact] public async Task File_in_cartella_temp_non_entra_nellindice_e_quindi_non_viene_risolto() { // Stesso codice "23456789" esiste per davvero, ma solo dentro una cartella radice // "temp_..." - esclusa dalla scansione (vedi Indexer.cs e docs/formato-asm.md, falso // positivo su copie scollegate dentro temp_*). Il componente deve restare "non // trovato", non risolversi per sbaglio contro la copia in temp_. ScriviFile(Path.Combine(_root, "MACCHINE", "GRUPPO1", "12345678.asm.1"), "Componente ID 1 (23456789.ASM) testo Componente ID 2 (23456789.ASM) fine"); ScriviFile(Path.Combine(_root, "temp_davide", "23456789.asm.1"), "copia scollegata"); var progress = new Progress(); await Indexer.ScanAsync(_root, _dbPath, progress, CancellationToken.None); var codiciNoti = CaricaCodiciNoti(); var asmIniziale = Path.Combine(_root, "MACCHINE", "GRUPPO1", "12345678.asm.1"); var (trovati, nonTrovati) = RisolviAlbero(asmIniziale, codiciNoti); Assert.Empty(trovati); Assert.Contains("23456789", nonTrovati); } }