Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
using System.Data.Common;
|
||||
using System.Text.Json;
|
||||
using Omika.Core.Domain.Commands;
|
||||
using Omika.Core.Domain.Entities.Clients;
|
||||
using Omika.Data.HfSql.Connection;
|
||||
using Omika.Data.Repositories.Batik;
|
||||
using Omika.Data.Repositories.Omika;
|
||||
|
||||
namespace Omika.Tests.Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Tests d'intégration OLE DB HFSQL réels du chemin de création (CreateAsync).
|
||||
///
|
||||
/// Ces tests nécessitent un serveur HFSQL accessible et le provider OLE DB
|
||||
/// <c>PCSOFT.HFSQL</c> installé. Ils sont ignorés automatiquement si la
|
||||
/// configuration locale n'est pas disponible ou si le serveur est injoignable.
|
||||
///
|
||||
/// Principe : on ouvre une connexion physique avec les mots de passe de fichiers
|
||||
/// (Extended Properties) résolus par <see cref="HfSqlFileAccessResolver"/>, on
|
||||
/// crée une transaction externe, on exécute <c>CreateAsync</c> dans cette
|
||||
/// transaction, on vérifie le client créé, puis on annule (rollback) pour ne
|
||||
/// laisser AUCUNE trace en base.
|
||||
///
|
||||
/// Aucun secret n'est affiché : les assertions portent uniquement sur le client
|
||||
/// canonique retourné.
|
||||
/// </summary>
|
||||
public class HfSqlOleDbCreateIntegrationTests
|
||||
{
|
||||
private const string ConfigPath = @"D:\Avalonia\Omika\config\hfsql-test.local.json";
|
||||
|
||||
private sealed class TestConfig
|
||||
{
|
||||
public int Port { get; set; }
|
||||
public string Server { get; set; } = "";
|
||||
public string ProtectionPlusSecret { get; set; } = "";
|
||||
public FilePasswordsConfig FilePasswords { get; set; } = new();
|
||||
public DatabaseConfig Batik { get; set; } = new();
|
||||
public DatabaseConfig Omika { get; set; } = new();
|
||||
}
|
||||
|
||||
private sealed class FilePasswordsConfig
|
||||
{
|
||||
public string? ProtectionStd { get; set; }
|
||||
public string? ProtectionPlus { get; set; }
|
||||
}
|
||||
|
||||
private sealed class DatabaseConfig
|
||||
{
|
||||
public string Credential { get; set; } = "";
|
||||
public string Database { get; set; } = "";
|
||||
public string User { get; set; } = "";
|
||||
public string? OriginalDatabaseName { get; set; }
|
||||
}
|
||||
|
||||
private static TestConfig? LoadConfig()
|
||||
{
|
||||
if (!File.Exists(ConfigPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(ConfigPath);
|
||||
TestConfig? config = JsonSerializer.Deserialize<TestConfig>(
|
||||
json,
|
||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
|
||||
// Serveur HFSQL injoignable : le test est ignoré (pas d'échec).
|
||||
if (config is null || !IsHfsqlServerReachable(config.Server, config.Port))
|
||||
return null;
|
||||
|
||||
return config;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsHfsqlServerReachable(string server, int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new System.Net.Sockets.TcpClient();
|
||||
return client.ConnectAsync(server, port).Wait(TimeSpan.FromSeconds(3));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static HfSqlConnectionOptions BuildOptions(TestConfig config, DatabaseConfig db)
|
||||
{
|
||||
return new HfSqlConnectionOptions
|
||||
{
|
||||
Server = config.Server,
|
||||
Port = config.Port,
|
||||
Database = db.Database,
|
||||
User = db.User,
|
||||
Credential = db.Credential,
|
||||
FilePasswords = new HfSqlFilePasswordOptions
|
||||
{
|
||||
ProtectionStd = config.FilePasswords.ProtectionStd,
|
||||
ProtectionPlus = config.FilePasswords.ProtectionPlus
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construit les Extended Properties (mots de passe de fichiers) pour les
|
||||
/// fichiers nécessaires au chemin Create, en fonction du mode d'accès observé
|
||||
/// par le résolveur. Les fichiers accessibles directement n'ont pas besoin
|
||||
/// d'entrée.
|
||||
/// </summary>
|
||||
private static string? BuildExtendedProperties(
|
||||
TestConfig config,
|
||||
HfSqlConnectionOptions options,
|
||||
IReadOnlyList<string> logicalFiles)
|
||||
{
|
||||
var factory = new HfSqlOleDbConnectionFactory();
|
||||
var resolver = new HfSqlFileAccessResolver(factory, options);
|
||||
|
||||
var profiles = logicalFiles
|
||||
.Select(f => new HfSqlFileAccessProfile { LogicalFile = f })
|
||||
.ToList();
|
||||
|
||||
var resolved = resolver.ResolveAsync(profiles).GetAwaiter().GetResult();
|
||||
|
||||
var entries = new List<string>();
|
||||
foreach (var logicalFile in logicalFiles)
|
||||
{
|
||||
if (!resolved.TryGetValue(logicalFile, out var profile))
|
||||
continue;
|
||||
|
||||
string? password = profile.ObservedAccess switch
|
||||
{
|
||||
HfSqlFileAccessMode.ProtectionStd => config.FilePasswords.ProtectionStd,
|
||||
HfSqlFileAccessMode.ProtectionPlus => config.FilePasswords.ProtectionPlus,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (password is null)
|
||||
continue;
|
||||
|
||||
entries.Add(HfSqlOleDbConnectionStringBuilder.BuildFilePasswordEntry(logicalFile, password));
|
||||
}
|
||||
|
||||
return entries.Count == 0 ? null : string.Join(";", entries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Batik_PersistsAndRollsBack()
|
||||
{
|
||||
var config = LoadConfig();
|
||||
if (config is null)
|
||||
return; // config absente : test ignoré
|
||||
|
||||
var options = BuildOptions(config, config.Batik);
|
||||
var factory = new HfSqlOleDbConnectionFactory();
|
||||
|
||||
// Fichiers nécessaires au chemin Create Batik.
|
||||
var logicalFiles = new[] { "Client", "ParametreMetier", "Adresse", "Client_Adresse" };
|
||||
string? extendedProperties = BuildExtendedProperties(config, options, logicalFiles);
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Connexion physique avec les mots de passe de fichiers.
|
||||
var physical = factory.Create(options, extendedProperties);
|
||||
await physical.OpenAsync();
|
||||
|
||||
// 2. Transaction externe (rollback à la fin).
|
||||
await using var transaction = await physical.BeginTransactionAsync();
|
||||
|
||||
// 3. Connexion HFSQL réutilisant la connexion physique + transaction.
|
||||
await using var connection = new HfSqlOleDbConnection(
|
||||
factory, options, physical, extendedProperties);
|
||||
await connection.OpenAsync();
|
||||
|
||||
// 4. Repository Batik.
|
||||
var repository = new BatikClientRepository(connection);
|
||||
|
||||
// 5. Création dans la transaction externe.
|
||||
var command = new ClientCreateCommand
|
||||
{
|
||||
Nom = "INTEGRATION_CREATE_BATIK",
|
||||
Code = "INT-CREATE-BATIK",
|
||||
AdresseLigne1 = "1 rue du Test",
|
||||
AdresseCodePostal = "75000",
|
||||
AdresseVille = "Paris"
|
||||
};
|
||||
|
||||
Client client = await repository.CreateAsync(command, transaction, CancellationToken.None);
|
||||
|
||||
// 6. Vérification du client canonique.
|
||||
Assert.NotNull(client);
|
||||
Assert.Equal("INTEGRATION_CREATE_BATIK", client.Nom);
|
||||
Assert.NotEqual(Guid.Empty, client.Id);
|
||||
|
||||
|
||||
// 7. Rollback : aucune trace en base.
|
||||
await transaction.RollbackAsync();
|
||||
}
|
||||
catch (Exception ex) when (IsServerUnavailable(ex))
|
||||
{
|
||||
return; // serveur injoignable : test ignoré
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_Omika_PersistsAndRollsBack()
|
||||
{
|
||||
var config = LoadConfig();
|
||||
if (config is null)
|
||||
return; // config absente : test ignoré
|
||||
|
||||
var options = BuildOptions(config, config.Omika);
|
||||
var factory = new HfSqlOleDbConnectionFactory();
|
||||
|
||||
// Fichiers nécessaires au chemin Create Omika.
|
||||
var logicalFiles = new[] { "Client", "ParametreMetier", "Adresse", "Client_Adresse" };
|
||||
string? extendedProperties = BuildExtendedProperties(config, options, logicalFiles);
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Connexion physique avec les mots de passe de fichiers.
|
||||
var physical = factory.Create(options, extendedProperties);
|
||||
await physical.OpenAsync();
|
||||
|
||||
// 2. Transaction externe (rollback à la fin).
|
||||
await using var transaction = await physical.BeginTransactionAsync();
|
||||
|
||||
// 3. Connexion HFSQL réutilisant la connexion physique + transaction.
|
||||
await using var connection = new HfSqlOleDbConnection(
|
||||
factory, options, physical, extendedProperties);
|
||||
await connection.OpenAsync();
|
||||
|
||||
// 4. Repository Omika.
|
||||
var repository = new OmikaClientRepository(connection);
|
||||
|
||||
// 5. Création dans la transaction externe.
|
||||
var command = new ClientCreateCommand
|
||||
{
|
||||
Nom = "INTEGRATION_CREATE_OMIKA",
|
||||
Code = "INT-CREATE-OMIKA",
|
||||
AdresseLigne1 = "1 rue du Test",
|
||||
AdresseCodePostal = "75000",
|
||||
AdresseVille = "Paris"
|
||||
};
|
||||
|
||||
Client client = await repository.CreateAsync(command, transaction, CancellationToken.None);
|
||||
|
||||
// 6. Vérification du client canonique.
|
||||
Assert.NotNull(client);
|
||||
Assert.Equal("INTEGRATION_CREATE_OMIKA", client.Nom);
|
||||
Assert.NotEqual(Guid.Empty, client.Id);
|
||||
|
||||
|
||||
// 7. Rollback : aucune trace en base.
|
||||
await transaction.RollbackAsync();
|
||||
}
|
||||
catch (Exception ex) when (IsServerUnavailable(ex))
|
||||
{
|
||||
return; // serveur injoignable : test ignoré
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsServerUnavailable(Exception ex)
|
||||
{
|
||||
var message = ex.Message;
|
||||
return message.Contains("provider", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("serveur", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("server", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("connexion", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("connection", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("E_FAIL", StringComparison.OrdinalIgnoreCase)
|
||||
|| message.Contains("0x80004005", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user