Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,452 @@
|
||||
using System.Collections;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using Omika.Data.HfSql.Connection;
|
||||
|
||||
|
||||
|
||||
namespace Omika.Tests.Connection;
|
||||
|
||||
public class HfSqlFileAccessResolverTests
|
||||
{
|
||||
private static HfSqlConnectionOptions CreateOptions() => new()
|
||||
{
|
||||
Server = "hfsql-serveur",
|
||||
Port = 4900,
|
||||
Database = "Omika",
|
||||
User = "tech",
|
||||
Credential = "secret",
|
||||
FilePasswords = new HfSqlFilePasswordOptions
|
||||
{
|
||||
ProtectionStd = "STD_PASSWORD",
|
||||
ProtectionPlus = "PLUS_PASSWORD"
|
||||
}
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_DirectPass_ReturnsDirect()
|
||||
{
|
||||
var factory = new FakeFactory(directPass: ["Client"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
var result = await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.Equal(HfSqlFileAccessMode.Direct, result["Client"].ObservedAccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_DirectFail_ProtectedPass_ReturnsProtectedMode()
|
||||
{
|
||||
var factory = new FakeFactory(
|
||||
directPass: [],
|
||||
protectedPass: ["Client"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
var result = await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.Equal(HfSqlFileAccessMode.ProtectionStd, result["Client"].ObservedAccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_DirectFail_ProtectedPlusPass_ReturnsProtectionPlus()
|
||||
{
|
||||
var factory = new FakeFactory(
|
||||
directPass: [],
|
||||
protectedPass: ["ParametreSysteme"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "ParametreSysteme", ExpectedGroup = "ProtectionPlus" }
|
||||
};
|
||||
|
||||
var result = await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.Equal(HfSqlFileAccessMode.ProtectionPlus, result["ParametreSysteme"].ObservedAccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_DirectFail_ProtectedFail_ReturnsFailed()
|
||||
{
|
||||
var factory = new FakeFactory(
|
||||
directPass: [],
|
||||
protectedPass: []);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
var result = await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.Equal(HfSqlFileAccessMode.Failed, result["Client"].ObservedAccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_NoExpectedGroup_DirectFail_ReturnsFailed()
|
||||
{
|
||||
var factory = new FakeFactory(
|
||||
directPass: [],
|
||||
protectedPass: []);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = null }
|
||||
};
|
||||
|
||||
var result = await resolver.ResolveAsync(files);
|
||||
|
||||
// Sans groupe attendu, pas de tentative protégée : le fichier reste Failed.
|
||||
Assert.Equal(HfSqlFileAccessMode.Failed, result["Client"].ObservedAccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_GetProfile_ReturnsCachedProfile()
|
||||
{
|
||||
var factory = new FakeFactory(directPass: ["Client"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
await resolver.ResolveAsync(files);
|
||||
|
||||
var profile = resolver.GetProfile("Client");
|
||||
Assert.NotNull(profile);
|
||||
Assert.Equal(HfSqlFileAccessMode.Direct, profile!.ObservedAccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_GetProfile_UnknownFile_ReturnsNull()
|
||||
{
|
||||
var factory = new FakeFactory(directPass: ["Client"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.Null(resolver.GetProfile("Inexistant"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_InvalidIdentifier_ReturnsFailed()
|
||||
{
|
||||
var factory = new FakeFactory(directPass: []);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Bad;Name", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
var result = await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.Equal(HfSqlFileAccessMode.Failed, result["Bad;Name"].ObservedAccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_ExplicitStdPassword_UsedInExtendedProperties()
|
||||
{
|
||||
// Vérifie que le resolver utilise la valeur EXACTE de FilePasswords.ProtectionStd
|
||||
// dans les Extended Properties (aucune dérivation).
|
||||
var factory = new CapturingFactory(directPass: [], protectedPass: ["Client"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.NotNull(factory.LastExtendedProperties);
|
||||
Assert.Contains("Password=Client:STD_PASSWORD", factory.LastExtendedProperties);
|
||||
Assert.DoesNotContain("DeriveProtectionStd", factory.LastExtendedProperties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_ExplicitPlusPassword_UsedInExtendedProperties()
|
||||
{
|
||||
// Vérifie que le resolver utilise la valeur EXACTE de FilePasswords.ProtectionPlus
|
||||
// dans les Extended Properties (aucune dérivation).
|
||||
var factory = new CapturingFactory(directPass: [], protectedPass: ["ParametreSysteme"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, CreateOptions());
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "ParametreSysteme", ExpectedGroup = "ProtectionPlus" }
|
||||
};
|
||||
|
||||
await resolver.ResolveAsync(files);
|
||||
|
||||
Assert.NotNull(factory.LastExtendedProperties);
|
||||
Assert.Contains("Password=ParametreSysteme:PLUS_PASSWORD", factory.LastExtendedProperties);
|
||||
Assert.DoesNotContain("DeriveProtectionPlus", factory.LastExtendedProperties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAsync_NoPasswordsConfigured_DirectOnly()
|
||||
{
|
||||
// Sans passwords configurés, le resolver ne tente aucune connexion protégée.
|
||||
var options = new HfSqlConnectionOptions
|
||||
{
|
||||
Server = "hfsql-serveur",
|
||||
Port = 4900,
|
||||
Database = "Omika",
|
||||
User = "tech",
|
||||
Credential = "secret",
|
||||
FilePasswords = new HfSqlFilePasswordOptions() // aucun password
|
||||
};
|
||||
|
||||
var factory = new CapturingFactory(directPass: ["Client"], protectedPass: ["Client"]);
|
||||
var resolver = new HfSqlFileAccessResolver(factory, options);
|
||||
|
||||
var files = new List<HfSqlFileAccessProfile>
|
||||
{
|
||||
new() { LogicalFile = "Client", ExpectedGroup = "ProtectionStd" }
|
||||
};
|
||||
|
||||
var result = await resolver.ResolveAsync(files);
|
||||
|
||||
// Direct PASS : pas de connexion protégée nécessaire.
|
||||
Assert.Equal(HfSqlFileAccessMode.Direct, result["Client"].ObservedAccess);
|
||||
Assert.Null(factory.LastExtendedProperties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToString_DoesNotExposeSecrets()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
var str = options.ToString();
|
||||
|
||||
Assert.DoesNotContain("STD_PASSWORD", str);
|
||||
Assert.DoesNotContain("PLUS_PASSWORD", str);
|
||||
Assert.DoesNotContain("secret", str);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fabrique simulée qui capture les Extended Properties pour vérifier que
|
||||
/// les mots de passe exacts des options sont utilisés (aucune dérivation).
|
||||
/// </summary>
|
||||
private sealed class CapturingFactory : IHfSqlOleDbConnectionFactory
|
||||
{
|
||||
private readonly HashSet<string> _directPass;
|
||||
private readonly HashSet<string> _protectedPass;
|
||||
|
||||
public string? LastExtendedProperties { get; private set; }
|
||||
|
||||
public CapturingFactory(string[] directPass, string[]? protectedPass = null)
|
||||
{
|
||||
_directPass = new HashSet<string>(directPass, StringComparer.OrdinalIgnoreCase);
|
||||
_protectedPass = new HashSet<string>(protectedPass ?? [], StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public DbConnection Create(HfSqlConnectionOptions options, string? extendedProperties = null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(extendedProperties))
|
||||
{
|
||||
LastExtendedProperties = extendedProperties;
|
||||
}
|
||||
bool isProtected = !string.IsNullOrWhiteSpace(extendedProperties);
|
||||
return new FakeConnection(
|
||||
isProtected ? _protectedPass : _directPass,
|
||||
isProtected ? extendedProperties : null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fabrique simulée : contrôle quels fichiers passent en Direct et lesquels
|
||||
/// passent en protégé. Vérifie aussi que les mots de passe sont bien fournis
|
||||
/// dans les Extended Properties de la connexion protégée.
|
||||
/// </summary>
|
||||
private sealed class FakeFactory : IHfSqlOleDbConnectionFactory
|
||||
|
||||
{
|
||||
private readonly HashSet<string> _directPass;
|
||||
private readonly HashSet<string> _protectedPass;
|
||||
|
||||
public FakeFactory(string[] directPass, string[]? protectedPass = null)
|
||||
{
|
||||
_directPass = new HashSet<string>(directPass, StringComparer.OrdinalIgnoreCase);
|
||||
_protectedPass = new HashSet<string>(protectedPass ?? [], StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public DbConnection Create(HfSqlConnectionOptions options, string? extendedProperties = null)
|
||||
{
|
||||
bool isProtected = !string.IsNullOrWhiteSpace(extendedProperties);
|
||||
return new FakeConnection(
|
||||
isProtected ? _protectedPass : _directPass,
|
||||
isProtected ? extendedProperties : null);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeConnection : DbConnection
|
||||
{
|
||||
private readonly HashSet<string> _passingTables;
|
||||
private readonly string? _extendedProperties;
|
||||
private ConnectionState _state = ConnectionState.Closed;
|
||||
|
||||
public FakeConnection(HashSet<string> passingTables, string? extendedProperties)
|
||||
{
|
||||
_passingTables = passingTables;
|
||||
_extendedProperties = extendedProperties;
|
||||
}
|
||||
|
||||
public override string ConnectionString { get; set; } = "";
|
||||
public override string Database => "Omika";
|
||||
public override string DataSource => "hfsql-serveur:4900";
|
||||
public override string ServerVersion => "1.0";
|
||||
public override ConnectionState State => _state;
|
||||
|
||||
public override void ChangeDatabase(string databaseName) { }
|
||||
|
||||
public override void Close() => _state = ConnectionState.Closed;
|
||||
|
||||
public override void Open() => _state = ConnectionState.Open;
|
||||
|
||||
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
protected override DbCommand CreateDbCommand() => new FakeCommand(_passingTables);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_state = ConnectionState.Closed;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeCommand : DbCommand
|
||||
{
|
||||
private readonly HashSet<string> _passingTables;
|
||||
|
||||
public FakeCommand(HashSet<string> passingTables)
|
||||
{
|
||||
_passingTables = passingTables;
|
||||
}
|
||||
|
||||
public override string CommandText { get; set; } = "";
|
||||
public override int CommandTimeout { get; set; }
|
||||
public override CommandType CommandType { get; set; }
|
||||
public override bool DesignTimeVisible { get; set; }
|
||||
public override UpdateRowSource UpdatedRowSource { get; set; }
|
||||
protected override DbConnection? DbConnection { get; set; }
|
||||
protected override DbParameterCollection DbParameterCollection { get; } = new FakeParameterCollection();
|
||||
protected override DbTransaction? DbTransaction { get; set; }
|
||||
|
||||
public override void Cancel() { }
|
||||
|
||||
public override int ExecuteNonQuery() => 0;
|
||||
|
||||
public override object? ExecuteScalar() => null;
|
||||
|
||||
public override void Prepare() { }
|
||||
|
||||
protected override DbParameter CreateDbParameter() => new FakeParameter();
|
||||
|
||||
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
|
||||
{
|
||||
// Extraire le nom de table depuis "SELECT TOP 1 * FROM <Table>".
|
||||
var table = CommandText.Split("FROM ", StringSplitOptions.RemoveEmptyEntries)[^1].Trim();
|
||||
if (_passingTables.Contains(table))
|
||||
{
|
||||
return new FakeReader();
|
||||
}
|
||||
throw new InvalidOperationException("Code erreur : 70114");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeReader : DbDataReader
|
||||
{
|
||||
public override int FieldCount => 1;
|
||||
public override bool HasRows => true;
|
||||
public override bool IsClosed => false;
|
||||
public override int RecordsAffected => 0;
|
||||
public override object this[int ordinal] => throw new NotSupportedException();
|
||||
public override object this[string name] => throw new NotSupportedException();
|
||||
public override int Depth => 0;
|
||||
|
||||
public override bool GetBoolean(int ordinal) => throw new NotSupportedException();
|
||||
public override byte GetByte(int ordinal) => throw new NotSupportedException();
|
||||
public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length)
|
||||
=> throw new NotSupportedException();
|
||||
public override char GetChar(int ordinal) => throw new NotSupportedException();
|
||||
public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length)
|
||||
=> throw new NotSupportedException();
|
||||
public override string GetDataTypeName(int ordinal) => throw new NotSupportedException();
|
||||
public override DateTime GetDateTime(int ordinal) => throw new NotSupportedException();
|
||||
public override decimal GetDecimal(int ordinal) => throw new NotSupportedException();
|
||||
public override double GetDouble(int ordinal) => throw new NotSupportedException();
|
||||
public override Type GetFieldType(int ordinal) => throw new NotSupportedException();
|
||||
public override float GetFloat(int ordinal) => throw new NotSupportedException();
|
||||
public override Guid GetGuid(int ordinal) => throw new NotSupportedException();
|
||||
public override short GetInt16(int ordinal) => throw new NotSupportedException();
|
||||
public override int GetInt32(int ordinal) => throw new NotSupportedException();
|
||||
public override long GetInt64(int ordinal) => throw new NotSupportedException();
|
||||
public override string GetName(int ordinal) => throw new NotSupportedException();
|
||||
public override int GetOrdinal(string name) => throw new NotSupportedException();
|
||||
public override string GetString(int ordinal) => throw new NotSupportedException();
|
||||
public override object GetValue(int ordinal) => throw new NotSupportedException();
|
||||
public override int GetValues(object[] values) => throw new NotSupportedException();
|
||||
public override bool IsDBNull(int ordinal) => throw new NotSupportedException();
|
||||
public override bool NextResult() => false;
|
||||
public override bool Read() => true;
|
||||
public override IEnumerator GetEnumerator() => Array.Empty<object>().GetEnumerator();
|
||||
}
|
||||
|
||||
|
||||
private sealed class FakeParameterCollection : DbParameterCollection
|
||||
{
|
||||
public override int Count => 0;
|
||||
public override object SyncRoot => this;
|
||||
public override int Add(object value) => 0;
|
||||
public override void AddRange(Array values) { }
|
||||
public override void Clear() { }
|
||||
public override bool Contains(object value) => false;
|
||||
public override bool Contains(string value) => false;
|
||||
public override void CopyTo(Array array, int index) { }
|
||||
public override IEnumerator GetEnumerator() => Array.Empty<object>().GetEnumerator();
|
||||
public override int IndexOf(object value) => -1;
|
||||
public override int IndexOf(string value) => -1;
|
||||
public override void Insert(int index, object value) { }
|
||||
public override void Remove(object value) { }
|
||||
public override void RemoveAt(int index) { }
|
||||
public override void RemoveAt(string parameterName) { }
|
||||
protected override DbParameter GetParameter(int index) => new FakeParameter();
|
||||
protected override DbParameter GetParameter(string parameterName) => new FakeParameter();
|
||||
protected override void SetParameter(int index, DbParameter value) { }
|
||||
protected override void SetParameter(string parameterName, DbParameter value) { }
|
||||
}
|
||||
|
||||
private sealed class FakeParameter : DbParameter
|
||||
{
|
||||
public override DbType DbType { get; set; }
|
||||
public override ParameterDirection Direction { get; set; }
|
||||
public override bool IsNullable { get; set; }
|
||||
public override string ParameterName { get; set; } = "";
|
||||
public override string SourceColumn { get; set; } = "";
|
||||
public override object? Value { get; set; }
|
||||
public override bool SourceColumnNullMapping { get; set; }
|
||||
public override int Size { get; set; }
|
||||
public override void ResetDbType() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user