Due bug reali trovati confrontando l'estrazione BOM con la BOM ufficiale Creo: un componente scartato per placement fallito in un file veniva bloccato per sempre anche quando il file giusto aveva un vero placement (perdendo interi sotto-alberi come 9256510047), e i codici con suffisso (_C01 ecc.) richiedevano sempre placement esplicito invece dell'eccezione per occorrenze ripetute già presente nella GUI. Entrambi corretti. AssemblyComponentScanner reso pubblico e scripts/BomViaDb.cs, ExtractCreoStrings.cs riscritti per referenziarlo via "#:project" invece di duplicare l'euristica: un'unica fonte di verità condivisa da GUI e script standalone. Aggiunti test di integrazione per Indexer.ScanAsync (mai coperto oltre al parsing del nome file) e test end-to-end che ricostruiscono l'intera pipeline disco -> indice -> risoluzione albero componenti senza toccare la UI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
202 lines
8.7 KiB
C#
202 lines
8.7 KiB
C#
using PrepStep;
|
|
|
|
namespace PrepStep.Tests;
|
|
|
|
public class AssemblyComponentScannerTests
|
|
{
|
|
private static string ScriviAsmFinto(string testo)
|
|
{
|
|
string percorso = Path.GetTempFileName();
|
|
File.WriteAllBytes(percorso, System.Text.Encoding.Latin1.GetBytes(testo));
|
|
return percorso;
|
|
}
|
|
|
|
[Fact]
|
|
public void Scarta_codici_con_una_sola_occorrenza()
|
|
{
|
|
// "11112222" compare 2 volte con un vero placement (riferimento vero), "99998888"
|
|
// una sola volta senza placement (rumore).
|
|
string percorso = ScriviAsmFinto(
|
|
"blah Componente ID 1 (11112222.PRT) blah 99998888 blah 11112222 fine");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
|
|
Assert.Contains("11112222", codici);
|
|
Assert.DoesNotContain("99998888", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Scarta_codice_ripetuto_senza_un_vero_placement()
|
|
{
|
|
// Riferimento al nome di un altro assieme (es. il padre che lo contiene): ripetuto
|
|
// più volte ma mai dentro un placement "Componente ID N (...)" - va scartato.
|
|
string percorso = ScriviAsmFinto("padre 33334444 assieme 33334444_D200 33334444");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
|
|
Assert.DoesNotContain("33334444", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Riconosce_sotto_assiemi_con_suffisso_stile_D100()
|
|
{
|
|
// Visto su 9244228573.asm: i sotto-assiemi hanno nomi come <codice>_D100/_D200/_D300.
|
|
string percorso = ScriviAsmFinto("x 9244228573_D100 y 9244228573_D100 z 9244228573_D200 9244228573_D200");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
|
|
Assert.Contains("9244228573_D100", codici);
|
|
Assert.Contains("9244228573_D200", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Non_estrae_nulla_da_una_sequenza_di_cifre_troppo_lunga()
|
|
{
|
|
// Una sequenza di 15 cifre di fila non contiene nessun confine di parola valido
|
|
// per un blocco di 8-12 cifre: non deve produrre falsi positivi troncati.
|
|
string percorso = ScriviAsmFinto("123456789012345 123456789012345");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
|
|
Assert.Empty(codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Scarta_id_di_viste_salvate_ModelView()
|
|
{
|
|
// Visto su 9044207061.asm: "ModelView#1073741828" compare più volte ma è un ID
|
|
// interno di vista salvata, non un codice componente.
|
|
string percorso = ScriviAsmFinto("x ModelView#1073741828 y ModelView#1073741828 z");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
|
|
Assert.DoesNotContain("1073741828", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Estrae_il_codice_generico_da_istanze_di_family_table_UNI()
|
|
{
|
|
// Visto su 9244228576.asm: le viti normalizzate compaiono come istanza di family
|
|
// table "R12--_-_U6592<UNI6592>", ma il file reale indicizzato è "uni6592.prt".
|
|
string percorso = ScriviAsmFinto(
|
|
"Componente ID 1 (R12--_-_U6592<UNI6592>.PRT) x Componente ID 2 (R12--_-_U6592<UNI6592>.PRT)");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso, codiciNoti: new[] { "UNI6592" });
|
|
Assert.Contains("UNI6592", codici);
|
|
Assert.DoesNotContain("R12--_-_U6592<UNI6592>", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void File_inesistente_lancia_eccezione_IO() =>
|
|
Assert.Throws<DirectoryNotFoundException>(() =>
|
|
AssemblyComponentScanner.EstraiCodiciReferenziati(@"C:\non\esiste\pippo.asm.1"));
|
|
|
|
[Fact]
|
|
public void Riconosce_parte_commerciale_con_nome_alfabetico_se_nel_db()
|
|
{
|
|
// Visto su un caso reale: le parti commerciali scaricate come STEP (es. anelli,
|
|
// boccole) hanno nomi Creo alfabetici, non un codice numerico Pro-E - si
|
|
// riconoscono solo se il nome esiste davvero come codice indicizzato.
|
|
string percorso = ScriviAsmFinto(
|
|
"blah Componente ID 11 (ANELLO_ESTERNO-1-SOLID1.PRT) blah RUMORE_CASUALE blah");
|
|
try
|
|
{
|
|
var codiciNoti = new[] { "ANELLO_ESTERNO-1-SOLID1" };
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso, codiciNoti);
|
|
Assert.Contains("ANELLO_ESTERNO-1-SOLID1", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Scarta_token_alfabetico_nel_db_ma_senza_placement()
|
|
{
|
|
// Un token che coincide per puro caso con un codice DB corto/generico (es. una
|
|
// keyword interna di Creo) non basta: senza un vero placement resta rumore.
|
|
string percorso = ScriviAsmFinto("start_part blah blah start_part");
|
|
try
|
|
{
|
|
var codiciNoti = new[] { "start_part" };
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso, codiciNoti);
|
|
Assert.DoesNotContain("start_part", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Ignora_token_alfabetici_non_presenti_nel_db()
|
|
{
|
|
string percorso = ScriviAsmFinto(
|
|
"blah Componente ID 1 (QUALCOSA_DI_INESISTENTE-1.PRT) blah");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso, codiciNoti: new[] { "ALTRO_CODICE" });
|
|
Assert.DoesNotContain("QUALCOSA_DI_INESISTENTE-1", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Riconosce_codice_con_suffisso_anche_senza_dettaglio_nel_placement()
|
|
{
|
|
// Regressione: caso reale 9284218024b.asm -> 2546220101_C01.._C04. Il placement
|
|
// riporta solo il codice padre senza suffisso ("Componente ID N (2546220101.PRT)"),
|
|
// il dettaglio "_C01" compare solo altrove come stringa ripetuta, mai dentro un
|
|
// placement esplicito. Un codice nudo nella stessa situazione andrebbe scartato
|
|
// (vedi Scarta_codice_ripetuto_senza_un_vero_placement), ma un codice con suffisso
|
|
// di variante è considerato "forma nota": bastano le occorrenze ripetute. Prima
|
|
// che questa eccezione fosse portata anche in scripts/BomViaDb.cs, lo script
|
|
// perdeva l'intero sotto-assieme (vedi docs/formato-asm.md).
|
|
string percorso = ScriviAsmFinto(
|
|
"Componente ID 1 (2546220101.PRT) x 2546220101_C01 y 2546220101_C01 z");
|
|
try
|
|
{
|
|
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
|
|
Assert.Contains("2546220101_C01", codici);
|
|
}
|
|
finally { File.Delete(percorso); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Il_placement_e_indipendente_per_ogni_file_scansionato()
|
|
{
|
|
// Regressione: caso reale 9244228558.asm -> 9244236104 dentro 9244228558_C01.asm.
|
|
// Lo stesso codice compare come riferimento fantasma (nudo, senza placement) nel
|
|
// file dell'assieme padre, e con un vero placement nel file del sotto-assieme che
|
|
// lo contiene realmente. La proprietà che rende sicura la BFS del chiamante
|
|
// (Form1.RisolviAlberoComponenti, scripts/BomViaDb.cs, scripts/ExtractCreoStrings.cs)
|
|
// è che EstraiCodiciReferenziati valuta il placement da zero per ogni file, senza
|
|
// stato condiviso tra le chiamate: un fallimento nel file padre non deve
|
|
// condizionare il risultato quando lo stesso codice viene ripassato per il file
|
|
// figlio (vedi docs/formato-asm.md, "Trappola nella BFS").
|
|
string filePadre = ScriviAsmFinto("assieme 11112222 padre 11112222 fine");
|
|
string fileFiglio = ScriviAsmFinto("Componente ID 1 (11112222.PRT) x 11112222 y");
|
|
try
|
|
{
|
|
Assert.DoesNotContain("11112222", AssemblyComponentScanner.EstraiCodiciReferenziati(filePadre));
|
|
Assert.Contains("11112222", AssemblyComponentScanner.EstraiCodiciReferenziati(fileFiglio));
|
|
}
|
|
finally { File.Delete(filePadre); File.Delete(fileFiglio); }
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("9284208188", "9284208188")]
|
|
[InlineData("9284208188_D600", "9284208188")]
|
|
[InlineData("9244228558_C03-01", "9244228558")]
|
|
public void CodiceRadice_estrae_la_parte_prima_del_primo_underscore(string codice, string radiceAttesa) =>
|
|
Assert.Equal(radiceAttesa, AssemblyComponentScanner.CodiceRadice(codice));
|
|
}
|