Add wizard UI, general-assembly resolution, and test suite
Restructure Form1 into a sequential wizard (database -> import -> raw -> recursive component resolution -> completeness/STEP report), fix case-sensitivity bugs in codice/tipo_file lookups and a word-boundary gap in the assembly scanner that missed _D100-style sub-assembly references, and treat the distinta's general assembly as an ordinary CSV row (derived from the DBP_<codice>_<timestamp> filename) instead of special-casing it throughout. Also adds CadSearchGui.Tests (xUnit) covering the scanner, filename parsing, and the case-insensitive DB lookup behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
146ce78eae
commit
52483177f0
16 files changed
+1144
-249
No files matched your search
@@ -0,0 +1,60 @@
|
||||
using CadSearchGui;
|
||||
|
||||
namespace CadSearchGui.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 (riferimento vero), "99998888" una sola (rumore).
|
||||
string percorso = ScriviAsmFinto("blah 11112222 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 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 File_inesistente_lancia_eccezione_IO() =>
|
||||
Assert.Throws<DirectoryNotFoundException>(() =>
|
||||
AssemblyComponentScanner.EstraiCodiciReferenziati(@"C:\non\esiste\pippo.asm.1"));
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CadSearchGui\CadSearchGui.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,124 @@
|
||||
using CadSearchGui;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace CadSearchGui.Tests;
|
||||
|
||||
/// <summary>Ricrea lo schema reale del DB (stessa costante SQL usata da Indexer.ScanAsync) e
|
||||
/// verifica, con dati inseriti a mano, i due comportamenti che in passato hanno causato bug
|
||||
/// silenziosi: il confronto case-insensitive su codice/tipo_file e la vista
|
||||
/// files_ultima_versione che tiene solo l'ultima versione per cartella_radice+codice+tipo.</summary>
|
||||
public class CaseInsensitiveLookupIntegrationTests : IDisposable
|
||||
{
|
||||
private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"cadsearch_test_{Guid.NewGuid():N}.db");
|
||||
private readonly SqliteConnection _connection;
|
||||
|
||||
public CaseInsensitiveLookupIntegrationTests()
|
||||
{
|
||||
_connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||
_connection.Open();
|
||||
using var schemaCmd = _connection.CreateCommand();
|
||||
schemaCmd.CommandText = Indexer.Schema;
|
||||
schemaCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection.Dispose();
|
||||
SqliteConnection.ClearPool(_connection);
|
||||
File.Delete(_dbPath);
|
||||
}
|
||||
|
||||
private void InserisciFile(string percorso, string cartella, string codice, string tipo, int versione, string dataModifica)
|
||||
{
|
||||
using var cmd = _connection.CreateCommand();
|
||||
cmd.CommandText = """
|
||||
INSERT INTO files (percorso_completo, percorso_relativo, nome_file, cartella_radice,
|
||||
famiglia, codice, tipo_file, versione, dimensione_byte, data_modifica, scansionato_il)
|
||||
VALUES (@p, @p, @p, @cartella, NULL, @codice, @tipo, @versione, 1, @modifica, @modifica)
|
||||
""";
|
||||
cmd.Parameters.AddWithValue("@p", percorso);
|
||||
cmd.Parameters.AddWithValue("@cartella", cartella);
|
||||
cmd.Parameters.AddWithValue("@codice", codice);
|
||||
cmd.Parameters.AddWithValue("@tipo", tipo);
|
||||
cmd.Parameters.AddWithValue("@versione", versione);
|
||||
cmd.Parameters.AddWithValue("@modifica", dataModifica);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>Stessa query (LOWER su entrambi i lati) usata da Form1 per risolvere un codice
|
||||
/// referenziato in un .asm: la scansione euristica del binario può restituire maiuscole
|
||||
/// diverse da quelle del file reale su disco (es. "_D100" estratto vs "_d100" su X:\).</summary>
|
||||
private List<(string Tipo, string Percorso)> CercaCasoInsensitive(string codiceCercato)
|
||||
{
|
||||
using var cmd = _connection.CreateCommand();
|
||||
cmd.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione "
|
||||
+ "WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ('prt', 'asm') "
|
||||
+ "ORDER BY tipo_file, data_modifica DESC";
|
||||
cmd.Parameters.AddWithValue("@codice", codiceCercato);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
var risultati = new List<(string, string)>();
|
||||
while (reader.Read())
|
||||
risultati.Add((reader.GetString(0), reader.GetString(1)));
|
||||
return risultati;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trova_il_file_anche_se_la_maiuscola_del_codice_non_corrisponde()
|
||||
{
|
||||
InserisciFile(@"X:\__Corpi\442\M_B\9244228573_d100.asm.16", "__Corpi", "9244228573_d100", "asm", 16, "2018-08-22T14:51:19Z");
|
||||
|
||||
var risultati = CercaCasoInsensitive("9244228573_D100");
|
||||
|
||||
Assert.Single(risultati);
|
||||
Assert.Equal("asm", risultati[0].Tipo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trova_il_file_anche_se_il_tipo_e_maiuscolo()
|
||||
{
|
||||
InserisciFile(@"X:\Commerciali\Elettrici\1504240807.PRT.1", "Commerciali", "1504240807", "PRT", 1, "2020-01-01T00:00:00Z");
|
||||
|
||||
var risultati = CercaCasoInsensitive("1504240807");
|
||||
|
||||
Assert.Single(risultati);
|
||||
Assert.Equal("PRT", risultati[0].Tipo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tra_due_cartelle_radice_diverse_tiene_solo_la_versione_piu_recente()
|
||||
{
|
||||
// Stesso codice+tipo duplicato su due cartelle radice diverse (capitato con temp_paris
|
||||
// vs __Corpi): la vista files_ultima_versione deve restituire il più recente per
|
||||
// cartella, ma la query applicativa a valle deve poi tenerne solo uno in assoluto
|
||||
// (comportamento verificato qui a livello di dati grezzi restituiti dalla vista).
|
||||
InserisciFile(@"X:\temp_paris\gabbia45_modelli_copia\9244228573.asm.1", "temp_paris", "9244228573", "asm", 1, "2014-06-25T07:38:13Z");
|
||||
InserisciFile(@"X:\__Corpi\442\M_B\9244228573.asm.41", "__Corpi", "9244228573", "asm", 41, "2019-05-08T13:27:23Z");
|
||||
|
||||
var risultati = CercaCasoInsensitive("9244228573");
|
||||
|
||||
// La vista non deduplica tra cartelle radice diverse: la query applicativa ordina per
|
||||
// data_modifica DESC e tiene solo la prima per tipo, cioè quella più recente.
|
||||
Assert.Equal(2, risultati.Count);
|
||||
Assert.Equal(@"X:\__Corpi\442\M_B\9244228573.asm.41", risultati[0].Percorso);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void La_vista_ultima_versione_scarta_le_versioni_precedenti_nella_stessa_cartella()
|
||||
{
|
||||
InserisciFile(@"X:\__Corpi\442\M_B\9244228573.asm.40", "__Corpi", "9244228573", "asm", 40, "2018-08-22T14:51:27Z");
|
||||
InserisciFile(@"X:\__Corpi\442\M_B\9244228573.asm.41", "__Corpi", "9244228573", "asm", 41, "2019-05-08T13:27:23Z");
|
||||
|
||||
var risultati = CercaCasoInsensitive("9244228573");
|
||||
|
||||
Assert.Single(risultati);
|
||||
Assert.Contains(".asm.41", risultati[0].Percorso);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Nessun_risultato_per_un_codice_non_indicizzato()
|
||||
{
|
||||
InserisciFile(@"X:\__Corpi\442\M_B\9244228573.asm.41", "__Corpi", "9244228573", "asm", 41, "2019-05-08T13:27:23Z");
|
||||
|
||||
Assert.Empty(CercaCasoInsensitive("0000000000"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using CadSearchGui;
|
||||
|
||||
namespace CadSearchGui.Tests;
|
||||
|
||||
public class DistintaBaseImporterTests
|
||||
{
|
||||
private const string SpreadsheetMlMinimo = """
|
||||
<?xml version="1.0"?>
|
||||
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet">
|
||||
<Worksheet ss:Name="Distinta Base" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
||||
<Table>
|
||||
<Row><Cell ss:Index="2"><Data>Componente</Data></Cell><Cell><Data>Descrizione</Data></Cell></Row>
|
||||
<Row><Cell ss:Index="2"><Data>1234567890</Data></Cell><Cell><Data>Vite</Data></Cell></Row>
|
||||
</Table>
|
||||
</Worksheet>
|
||||
</Workbook>
|
||||
""";
|
||||
|
||||
private static string ScriviFileDistinta(string nomeFile, string contenuto)
|
||||
{
|
||||
string percorso = Path.Combine(Path.GetTempPath(), $"cadsearch_test_{Guid.NewGuid():N}", nomeFile);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(percorso)!);
|
||||
File.WriteAllText(percorso, contenuto);
|
||||
return percorso;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Aggiunge_assieme_generale_dedotto_dal_nome_file_DBP()
|
||||
{
|
||||
string percorso = ScriviFileDistinta("DBP_9244228573_20260916144238.xls", SpreadsheetMlMinimo);
|
||||
try
|
||||
{
|
||||
var (_, righe) = DistintaBaseImporter.ConvertiInCsv(percorso);
|
||||
|
||||
Assert.Contains(righe, r => r.Componente == "9244228573" && r.Descrizione == "Assieme generale");
|
||||
Assert.Contains(righe, r => r.Componente == "1234567890");
|
||||
}
|
||||
finally { Directory.Delete(Path.GetDirectoryName(percorso)!, recursive: true); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Non_duplica_lassieme_generale_se_gia_presente_tra_i_componenti()
|
||||
{
|
||||
// Caso limite: il codice del nome file coincide con un componente già elencato nel foglio.
|
||||
string contenutoConGeneraleGiaPresente = SpreadsheetMlMinimo.Replace("1234567890", "9244228573");
|
||||
string percorso = ScriviFileDistinta("DBP_9244228573_20260916144238.xls", contenutoConGeneraleGiaPresente);
|
||||
try
|
||||
{
|
||||
var (_, righe) = DistintaBaseImporter.ConvertiInCsv(percorso);
|
||||
|
||||
Assert.Single(righe, r => r.Componente == "9244228573");
|
||||
}
|
||||
finally { Directory.Delete(Path.GetDirectoryName(percorso)!, recursive: true); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Nessun_assieme_generale_aggiunto_se_il_nome_file_non_segue_il_pattern_DBP()
|
||||
{
|
||||
string percorso = ScriviFileDistinta("estrazione_qualsiasi.xls", SpreadsheetMlMinimo);
|
||||
try
|
||||
{
|
||||
var (_, righe) = DistintaBaseImporter.ConvertiInCsv(percorso);
|
||||
|
||||
Assert.DoesNotContain(righe, r => r.Descrizione == "Assieme generale");
|
||||
Assert.Single(righe);
|
||||
}
|
||||
finally { Directory.Delete(Path.GetDirectoryName(percorso)!, recursive: true); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using CadSearchGui;
|
||||
|
||||
namespace CadSearchGui.Tests;
|
||||
|
||||
public class IndexerParseFilenameTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("9244228573.asm.41", "9244228573", "asm", 41)]
|
||||
[InlineData("9244228573_D100.asm.16", "9244228573_D100", "asm", 16)]
|
||||
[InlineData("1504240807.PRT.1", "1504240807", "PRT", 1)]
|
||||
public void Estrae_codice_tipo_e_versione_dal_nome_file(
|
||||
string nomeFile, string codiceAtteso, string tipoAtteso, int versioneAttesa)
|
||||
{
|
||||
var (codice, tipo, versione) = Indexer.ParseFilename(nomeFile);
|
||||
Assert.Equal(codiceAtteso, codice);
|
||||
Assert.Equal(tipoAtteso, tipo);
|
||||
Assert.Equal(versioneAttesa, versione);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("9244228573.edz")]
|
||||
[InlineData("readme.txt")]
|
||||
[InlineData("senza_estensioni")]
|
||||
public void File_senza_pattern_codice_tipo_versione_finisce_in_altro(string nomeFile)
|
||||
{
|
||||
var (codice, tipo, versione) = Indexer.ParseFilename(nomeFile);
|
||||
Assert.Equal(nomeFile, codice);
|
||||
Assert.Equal("altro", tipo);
|
||||
Assert.Null(versione);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user