using Omika.HfSqlProbe; namespace Omika.Tests.Tools; /// /// Tests unitaires du probe HFSQL ODBC — appelle les VRAIES classes /// du projet Omika.HfSqlProbe. Aucune connexion réelle. /// public class HfSqlProbeTests { // ========================================================================= // SqlIdentifierValidator // ========================================================================= [Theory] [InlineData("Client")] [InlineData("ProbeTest")] [InlineData("CL_PKID")] [InlineData("Marker")] [InlineData("a")] [InlineData("A")] [InlineData("_")] [InlineData("_test")] [InlineData("Client123")] [InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_____")] public void ValidateSqlIdentifier_ValidNames_Accepted(string name) { Assert.True(SqlIdentifierValidator.IsValid(name), $"Valid identifier rejected: '{name}'"); _ = SqlIdentifierValidator.Validate(name); } [Theory] [InlineData("")] [InlineData(" ")] [InlineData("123Client")] // starts with digit [InlineData("Client;DROP TABLE")] [InlineData("Client--")] [InlineData("Client/*x*/")] [InlineData("Client x")] [InlineData("Client\t")] [InlineData("Client\n")] [InlineData("Client\r")] [InlineData("Client\r\n")] [InlineData("Client\nDROP")] [InlineData("Client'")] [InlineData("Client\"")] [InlineData("[Client]")] [InlineData("`Client`")] [InlineData("Client.Name")] [InlineData("Client\\Name")] [InlineData("C lien")] [InlineData("C;DROP")] public void ValidateSqlIdentifier_InvalidNames_Rejected(string name) { Assert.False(SqlIdentifierValidator.IsValid(name), $"Invalid identifier accepted: '{name}'"); Assert.Throws(() => SqlIdentifierValidator.Validate(name)); } // ========================================================================= // ConnectionStringRedactor // ========================================================================= [Fact] public void Redact_RemovesPWD() { string input = "DSN=Test;PWD=MySecret123;UID=admin;Database=TestDB"; string result = ConnectionStringRedactor.Redact(input); Assert.DoesNotContain("MySecret123", result); Assert.Contains("PWD=***", result); } [Fact] public void Redact_RemovesUID() { string input = "DSN=Test;UID=admin123;PWD=pass"; string result = ConnectionStringRedactor.Redact(input); Assert.DoesNotContain("admin123", result); Assert.Contains("UID=***", result); } [Fact] public void Redact_RemovesPasswordKey() { string input = "DSN=Test;Password=SuperSecret;Server=localhost"; string result = ConnectionStringRedactor.Redact(input); Assert.DoesNotContain("SuperSecret", result); } [Fact] public void Redact_PreservesNonCredentials() { string input = "DSN=HFSQL_Test;Server=localhost;Database=TestDB"; string result = ConnectionStringRedactor.Redact(input); Assert.Contains("DSN=HFSQL_Test", result); Assert.Contains("Server=localhost", result); Assert.Contains("Database=TestDB", result); } [Fact] public void Redact_CaseInsensitive() { string input = "DSN=X;pwd=secret123"; string result = ConnectionStringRedactor.Redact(input); Assert.DoesNotContain("secret123", result); Assert.Contains("PWD=***", result); } [Fact] public void Redact_SpecialCharsInPassword() { string input = "DSN=X;Password=aB12!@#$"; string result = ConnectionStringRedactor.Redact(input); Assert.DoesNotContain("aB12!@#$", result); } [Fact] public void Redact_EmptyPassword_Works() { string input = "DSN=X;PWD=;Server=Y"; string result = ConnectionStringRedactor.Redact(input); Assert.Contains("PWD=***", result); } // ========================================================================= // ProbeArgumentParser // ========================================================================= [Theory] [InlineData("capabilities")] [InlineData("CAPABILITIES")] [InlineData("Capabilities")] public void NormalizeCommand_RecognizesCapabilities(string cmd) { Assert.Equal("capabilities", ProbeArgumentParser.NormalizeCommand([cmd])); } [Fact] public void NormalizeCommand_EmptyArgs_ReturnsEmpty() { Assert.Equal("", ProbeArgumentParser.NormalizeCommand([])); } [Theory] [InlineData("")] [InlineData("unknown")] public void IsValidCommand_RejectsUnknown(string cmd) { Assert.False(ProbeArgumentParser.IsValidCommand(cmd)); } [Theory] [InlineData("capabilities")] [InlineData("transaction")] [InlineData("returning")] [InlineData("omika-guid")] [InlineData("--help")] public void IsValidCommand_AcceptsKnown(string cmd) { Assert.True(ProbeArgumentParser.IsValidCommand(cmd)); } [Fact] public void Parse_ParsesConnectionStringOption() { var options = ProbeArgumentParser.Parse( ["--connection-string", "DSN=Test;UID=u;PWD=p"]); Assert.Equal("DSN=Test;UID=u;PWD=p", options.ConnectionString); } [Fact] public void Parse_ParsesAllowWriteProbeFlag() { var options = ProbeArgumentParser.Parse(["--allow-write-probe"]); Assert.True(options.AllowWriteProbe); } [Fact] public void Parse_ParsesTableAndIdColumn() { var options = ProbeArgumentParser.Parse( ["--table", "MyTable", "--id-column", "PKID"]); Assert.Equal("MyTable", options.Table); Assert.Equal("PKID", options.IdColumn); } [Fact] public void Parse_ParsesSchema() { var options = ProbeArgumentParser.Parse(["--schema", "batik"]); Assert.Equal("batik", options.Schema); } [Fact] public void Parse_DoesNotSetAllowWriteProbeByDefault() { var options = ProbeArgumentParser.Parse([]); Assert.False(options.AllowWriteProbe); } // ========================================================================= // Write gate // ========================================================================= [Theory] [InlineData("transaction")] [InlineData("returning")] public void IsWriteCommand_WriteCommandsRequireGate(string cmd) { Assert.True(ProbeArgumentParser.IsWriteCommand(cmd), $"'{cmd}' must require --allow-write-probe."); } [Theory] [InlineData("capabilities")] [InlineData("")] [InlineData("unknown")] public void IsWriteCommand_ReadCommandsDoNotRequireGate(string cmd) { Assert.False(ProbeArgumentParser.IsWriteCommand(cmd)); } // ========================================================================= // ProbeSqlBuilder // ========================================================================= [Fact] public void SqlBuilder_InsertReturning_ValidatesAllIdentifiers() { string sql = ProbeSqlBuilder.BuildInsertReturning("TestTable", "TestPKID", "Marker"); Assert.Contains("INSERT INTO TestTable", sql); Assert.Contains("VALUES (?)", sql); Assert.Contains("RETURNING TestPKID", sql); Assert.DoesNotContain("'", sql); } [Fact] public void SqlBuilder_RejectsInvalidTable() { var ex = Assert.Throws( () => ProbeSqlBuilder.BuildInsertReturning("Bad;DROP", "PKID", "Marker")); Assert.Contains("Bad;DROP", ex.Message); } [Fact] public void SqlBuilder_RejectsInvalidMarkerColumn() { Assert.Throws( () => ProbeSqlBuilder.BuildInsertMarker("Table", "x y")); } [Fact] public void SqlBuilder_RejectsInvalidIdColumn() { Assert.Throws( () => ProbeSqlBuilder.BuildInsertReturning("Table", "PKID--", "M")); } [Fact] public void SqlBuilder_CountByMarker_Parameterized() { string sql = ProbeSqlBuilder.BuildCountByMarker("TestTable", "Marker"); Assert.Contains("SELECT COUNT(*)", sql); Assert.Contains("WHERE Marker = ?", sql); Assert.DoesNotContain("'", sql); } [Fact] public void SqlBuilder_InsertMarker_Parameterized() { string sql = ProbeSqlBuilder.BuildInsertMarker("T", "Col"); Assert.Contains("INSERT INTO T (Col)", sql); Assert.Contains("VALUES (?)", sql); } }