Codebasis als Dateien ins Arbeitsrepo statt als Gitlink

QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142)
getrackt, ohne .gitmodules und ohne erreichbares Remote. Der
Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar
gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege
der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen.

Umstellung:
- Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert
  (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6)
- Gitlink aus dem Index entfernt
- Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB

Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte
bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
This commit is contained in:
Christoph Schwörer
2026-08-26 07:43:51 +02:00
parent 18edae75b6
commit f045b99a25
24664 changed files with 5846716 additions and 1 deletions
@@ -0,0 +1,231 @@
using Centron.BusinessLogic.Administration.AccessTokens;
using Centron.BusinessLogic.WebServices.Administration.AccessTokens;
using JetBrains.Annotations;
namespace Centron.Tests.BL.Administration.AccessTokens;
/// <summary>
/// Unit tests for AccessTokenWebServiceBL and AccessTokenBL.
/// These tests focus on logic that can be tested without full database setup.
/// For integration tests with rights checks, see the EndToEnd tests.
/// </summary>
[TestSubject(typeof(AccessTokenWebServiceBL))]
public class AccessTokenWebServiceBLTest
{
#region Token Hash Tests
[Fact]
public void AccessTokenBL_HashToken_ReturnsDeterministicHash()
{
// Arrange
var plainToken = "test-token-value";
// Act
var hash1 = AccessTokenBL.HashToken(plainToken);
var hash2 = AccessTokenBL.HashToken(plainToken);
// Assert
Assert.Equal(hash1, hash2);
}
[Fact]
public void AccessTokenBL_HashToken_ReturnsDifferentHashForDifferentTokens()
{
// Arrange
var token1 = "token-one";
var token2 = "token-two";
// Act
var hash1 = AccessTokenBL.HashToken(token1);
var hash2 = AccessTokenBL.HashToken(token2);
// Assert
Assert.NotEqual(hash1, hash2);
}
[Fact]
public void AccessTokenBL_HashToken_ReturnsNonEmptyString()
{
// Arrange
var plainToken = "test-token";
// Act
var hash = AccessTokenBL.HashToken(plainToken);
// Assert
Assert.NotNull(hash);
Assert.NotEmpty(hash);
// SHA-256 produces 64 hex characters
Assert.Equal(64, hash.Length);
}
[Fact]
public void AccessTokenBL_HashToken_ReturnsUppercaseHex()
{
// Arrange
var plainToken = "test-token";
// Act
var hash = AccessTokenBL.HashToken(plainToken);
// Assert
Assert.Matches("^[A-F0-9]+$", hash);
}
[Fact]
public void AccessTokenBL_HashToken_ConsistentAcrossRuns()
{
// This test verifies that the hash is deterministic and consistent
// Arrange
var plainToken = "consistent-test-token";
// Act
var actualHash = AccessTokenBL.HashToken(plainToken);
// Verify it's consistent across multiple calls
var hash1 = AccessTokenBL.HashToken(plainToken);
var hash2 = AccessTokenBL.HashToken(plainToken);
// Assert
Assert.Equal(hash1, hash2);
Assert.Equal(64, actualHash.Length); // SHA-256 = 256 bits = 32 bytes = 64 hex chars
}
[Theory]
[InlineData("")]
[InlineData("a")]
[InlineData("short")]
[InlineData("this-is-a-longer-token-value-for-testing")]
[InlineData("special!@#$%^&*()characters")]
[InlineData("unicode-äöü-日本語-🔒")]
public void AccessTokenBL_HashToken_HandlesVariousInputs(string plainToken)
{
// Act
var hash = AccessTokenBL.HashToken(plainToken);
// Assert
Assert.NotNull(hash);
Assert.Equal(64, hash.Length);
Assert.Matches("^[A-F0-9]+$", hash);
}
#endregion
#region Token Generation Tests
[Fact]
public void GenerateTestToken_ReturnsNonEmptyString()
{
// Act
var token = GenerateTestToken();
// Assert
Assert.NotNull(token);
Assert.NotEmpty(token);
}
[Fact]
public void GenerateTestToken_ReturnsUniqueTokens()
{
// Act
var token1 = GenerateTestToken();
var token2 = GenerateTestToken();
// Assert
Assert.NotEqual(token1, token2);
}
[Fact]
public void GenerateTestToken_ReturnsUrlSafeString()
{
// Act
var token = GenerateTestToken();
// Assert - URL-safe Base64 uses - and _ instead of + and /
Assert.DoesNotContain("+", token);
Assert.DoesNotContain("/", token);
Assert.DoesNotContain("=", token);
}
[Fact]
public void GenerateTestToken_HasSufficientLength()
{
// Act
var token = GenerateTestToken();
// Assert - 32 bytes encoded in Base64 should be ~43 characters
Assert.True(token.Length >= 32, $"Token length {token.Length} is too short for security");
}
#endregion
#region Hash Verification Tests
[Fact]
public void HashToken_VerificationWorkflow_MatchingToken_Succeeds()
{
// Arrange - Simulate the token creation and verification workflow
var plainToken = GenerateTestToken();
var storedHash = AccessTokenBL.HashToken(plainToken);
// Act - Later, when verifying
var providedHash = AccessTokenBL.HashToken(plainToken);
// Assert
Assert.Equal(storedHash, providedHash);
}
[Fact]
public void HashToken_VerificationWorkflow_WrongToken_Fails()
{
// Arrange - Simulate the token creation and verification workflow
var originalToken = GenerateTestToken();
var storedHash = AccessTokenBL.HashToken(originalToken);
var wrongToken = GenerateTestToken();
// Act - Attacker tries with wrong token
var providedHash = AccessTokenBL.HashToken(wrongToken);
// Assert
Assert.NotEqual(storedHash, providedHash);
}
[Fact]
public void HashToken_SimilarTokens_ProduceDifferentHashes()
{
// This tests that even minor changes produce completely different hashes
// Arrange
var token1 = "test-token-123";
var token2 = "test-token-124"; // One character different
// Act
var hash1 = AccessTokenBL.HashToken(token1);
var hash2 = AccessTokenBL.HashToken(token2);
// Assert - Hashes should be completely different (avalanche effect)
Assert.NotEqual(hash1, hash2);
// Count differing characters - should be significant
var differences = hash1.Zip(hash2, (a, b) => a != b).Count(d => d);
Assert.True(differences > 20, $"Hashes are too similar: only {differences} characters differ");
}
#endregion
#region Helper Methods
/// <summary>
/// Generates a test token similar to the internal AccessTokenBL method.
/// </summary>
private static string GenerateTestToken()
{
var bytes = new byte[32];
using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
{
rng.GetBytes(bytes);
}
return Convert.ToBase64String(bytes).Replace("+", "-").Replace("/", "_").TrimEnd('=');
}
#endregion
}
@@ -0,0 +1,391 @@
using System.Reflection;
using Centron.Tests.BL.SetupUtils;
using Centron.BusinessLogic.Administration.Logins.Auth;
using Centron.BusinessLogic.Administration.WebServiceConfiguration;
using Centron.DAO;
using Centron.DAO.Mappings;
using Centron.DAO.Mappings.Administration;
using Centron.DAO.Mappings.Administration.Applications;
using Centron.DAO.Mappings.Administration.Company;
using Centron.DAO.Mappings.Administration.Employees;
using Centron.DAO.Mappings.Administration.Logins;
using Centron.DAO.Mappings.Administration.Settings;
using Centron.DAO.Mappings.CustomerArea.Support;
using Centron.DAO.Mappings.States;
using Centron.DAO.Mappings.TemporaryEntities;
using Centron.DAO.TemporaryEntities;
using Centron.Interfaces.Administration.Employees;
using Centron.Interfaces.Administration.Logins;
using Centron.Interfaces.Administration.Settings;
using Centron.Interfaces.BL;
using Centron.Tests.BL.Fixtures;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using JetBrains.Annotations;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
namespace Centron.Tests.BL.Administration.Logins.Auth;
[TestSubject(typeof(AuthenticatorFactory))]
public class AuthenticatorFactoryTest : IClassFixture<LicenseManagerFixture>
{
private static Type Ba => typeof(BasicAuthenticator);
private static Type Ad => typeof(ActiveDirectoryAuthenticator);
private static Type Oi => typeof(OpenIdConnectAuthenticator);
public static IEnumerable<object?[]> DataAuthenticatorSystemNoneTestSuccess()
{
var data = new List<object?[]>();
// [expectedAuthenticatorType, authObject, method, ActiveDirectoryEnabled, JwtEnabled]
// Basic
data.Add([Ba, new BasicAuthObject(), false, false]);
data.Add([Ba, new BasicAuthObject(), false, true]);
// Active Directory
data.Add([Ad, new BasicAuthObject(), true, false]);
data.Add([Ad, new BasicAuthObject(), true, true]);
// OpenId Connect
data.Add([Oi, new OpenIdConnectAuthObject(), false, true]);
data.Add([Oi, new OpenIdConnectAuthObject(), true, true]);
return data;
}
[Theory]
[MemberData(nameof(DataAuthenticatorSystemNoneTestSuccess))]
internal void GetAuthenticatorSystemNoneSuccess(
Type expectedAuthenticatorType, AuthObject authObject, bool activeDirectoryEnabled, bool jwtEnabled)
{
// Arrange
const SystemAuthenticationMethod systemAuthMethod = SystemAuthenticationMethod.None;
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, systemAuthMethod, activeDirectoryEnabled, jwtEnabled);
var authenticatorFactory = new AuthenticatorFactory(daoSession, licenseManager);
// Act
var result = authenticatorFactory.GetAuthenticator(authObject);
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.IsType(expectedAuthenticatorType, result.Data);
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
internal void GetAuthenticatorSystemBasicSuccess(
bool activeDirectoryEnabled, bool jwtEnabled)
{
// Arrange
const SystemAuthenticationMethod systemAuthMethod = SystemAuthenticationMethod.Basic;
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, systemAuthMethod, activeDirectoryEnabled, jwtEnabled);
var authenticatorFactory = new AuthenticatorFactory(daoSession);
var authObject = new BasicAuthObject();
// Act
var result = authenticatorFactory.GetAuthenticator(authObject);
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.IsType<BasicAuthenticator>(result.Data);
}
[Theory]
[InlineData(true, false)]
[InlineData(true, true)]
internal void GetAuthenticatorSystemActiveDirectorySuccess(bool activeDirectoryEnabled, bool jwtEnabled)
{
// Arrange
const SystemAuthenticationMethod systemAuthMethod = SystemAuthenticationMethod.ActiveDirectory;
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, systemAuthMethod, activeDirectoryEnabled, jwtEnabled);
var authenticatorFactory = new AuthenticatorFactory(daoSession);
var authObject = new BasicAuthObject();
// Act
var result = authenticatorFactory.GetAuthenticator(authObject);
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.IsType<ActiveDirectoryAuthenticator>(result.Data);
}
[Theory]
[InlineData(false, true)]
[InlineData(true, true)]
internal void GetAuthenticatorSystemOpenIdConnectSuccess(bool activeDirectoryEnabled, bool jwtEnabled)
{
// Arrange
const SystemAuthenticationMethod systemAuthMethod = SystemAuthenticationMethod.OpenIdConnect;
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, systemAuthMethod, activeDirectoryEnabled, jwtEnabled);
var authenticatorFactory = new AuthenticatorFactory(daoSession, licenseManager);
var authObject = new OpenIdConnectAuthObject();
// Act
var result = authenticatorFactory.GetAuthenticator(authObject);
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.IsType<OpenIdConnectAuthenticator>(result.Data);
}
[Theory]
[InlineData(SystemAuthenticationMethod.None, false, false)]
[InlineData(SystemAuthenticationMethod.None, false, true)]
[InlineData(SystemAuthenticationMethod.None, true, false)]
[InlineData(SystemAuthenticationMethod.None, true, true)]
[InlineData(SystemAuthenticationMethod.Basic, false, false)]
[InlineData(SystemAuthenticationMethod.Basic, false, true)]
[InlineData(SystemAuthenticationMethod.Basic, true, false)]
[InlineData(SystemAuthenticationMethod.Basic, true, true)]
[InlineData(SystemAuthenticationMethod.ActiveDirectory, false, false)]
[InlineData(SystemAuthenticationMethod.ActiveDirectory, false, true)]
[InlineData(SystemAuthenticationMethod.ActiveDirectory, true, false)]
[InlineData(SystemAuthenticationMethod.ActiveDirectory, true, true)]
[InlineData(SystemAuthenticationMethod.OpenIdConnect, false, false)]
[InlineData(SystemAuthenticationMethod.OpenIdConnect, false, true)]
[InlineData(SystemAuthenticationMethod.OpenIdConnect, true, false)]
[InlineData(SystemAuthenticationMethod.OpenIdConnect, true, true)]
internal void GetAuthenticatorWebAccountSuccess(
SystemAuthenticationMethod method, bool activeDirectoryEnabled, bool jwtEnabled)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, method, activeDirectoryEnabled, jwtEnabled);
var authenticatorFactory = new AuthenticatorFactory(daoSession);
// This always prompts a WebAccountAuthenticator
var webAccountAuthObject = new WebAccountAuthObject();
// Act
var result = authenticatorFactory.GetAuthenticator(webAccountAuthObject);
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.IsType<WebAccountAuthenticator>(result.Data);
}
public static IEnumerable<object?[]> DataAuthenticatorFallbackTestSuccess()
{
var data = new List<object?[]>();
// [expectedMainAuthenticatorType, method, ActiveDirectoryEnabled, JwtEnabled]
data.Add([Ba, SystemAuthenticationMethod.None, false, false]);
data.Add([Ad, SystemAuthenticationMethod.None, true, false]);
data.Add([Ba, SystemAuthenticationMethod.Basic, false, false]);
data.Add([Ba, SystemAuthenticationMethod.Basic, true, false]);
data.Add([Ad, SystemAuthenticationMethod.ActiveDirectory, true, false]);
data.Add([null, SystemAuthenticationMethod.ActiveDirectory, false, false]);
data.Add([null, SystemAuthenticationMethod.OpenIdConnect, false, true]);
data.Add([null, SystemAuthenticationMethod.OpenIdConnect, false, false]);
return data;
}
[Theory]
[MemberData(nameof(DataAuthenticatorFallbackTestSuccess))]
public void GetAuthenticatorFallbackTestSuccess(
Type? expectedMainAuthenticatorType, SystemAuthenticationMethod systemAuthenticationMethod,
bool activeDirectoryEnabled, bool jwtEnabled)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, systemAuthenticationMethod, activeDirectoryEnabled, jwtEnabled);
var authenticatorFactory = new AuthenticatorFactory(daoSession);
const string username = "username";
var appUser = DbSetupUtils.SaveAppUser(session, username, "e@mail.com", 1);
appUser.AuthentificationKind = AuthentificationKind.CentronLogin;
session.Save(appUser);
session.Flush();
var authObject = new BasicAuthObject
{
UserName = username,
};
// Act
var result = authenticatorFactory.GetAuthenticator(authObject);
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.IsType<FallbackAuthenticator>(result.Data);
// assert that the main authenticator is of the expected type
var fallbackAuthenticator = (FallbackAuthenticator)result.Data;
var mainAuthenticator = fallbackAuthenticator
.GetType()
.GetField("<mainAuthenticator>P", BindingFlags.NonPublic | BindingFlags.Instance)?
.GetValue(fallbackAuthenticator);
if (expectedMainAuthenticatorType is null)
{
Assert.Null(mainAuthenticator);
return;
}
Assert.NotNull(mainAuthenticator);
Assert.IsType(expectedMainAuthenticatorType, mainAuthenticator);
}
[Theory]
#pragma warning disable CS0612 // Type or member is obsolete
[InlineData(AuthentificationKind.WindowsAuth)]
[InlineData(AuthentificationKind.OpenIdConnectAuth)]
#pragma warning restore CS0612 // Type or member is obsolete
public void GetAuthenticatorFallbackTestError(AuthentificationKind authKind)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, SystemAuthenticationMethod.None, true, true);
var authenticatorFactory = new AuthenticatorFactory(daoSession);
const string username = "username";
var appUser = DbSetupUtils.SaveAppUser(session, username, "e@mail.com", 1);
appUser.AuthentificationKind = authKind;
session.Save(appUser);
session.Flush();
var authObject = new BasicAuthObject
{
UserName = username,
};
// Act
var result = authenticatorFactory.GetAuthenticator(authObject);
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.IsType<FallbackAuthenticator>(result.Data);
// assert that the main authenticator is of the expected type
var fallbackAuthenticator = (FallbackAuthenticator)result.Data;
var secondaryAuthenticator = fallbackAuthenticator
.GetType()
.GetField("<secondaryAuthenticator>P", BindingFlags.NonPublic | BindingFlags.Instance)?
.GetValue(fallbackAuthenticator);
Assert.NotNull(secondaryAuthenticator);
Assert.IsType<FailingAuthenticator>(secondaryAuthenticator);
}
public static IEnumerable<object?[]> DataAuthenticatorTestError()
{
var data = new List<object?[]>();
// [authObject, method, ActiveDirectoryEnabled, JwtEnabled]
// Basic
// - wrong auth object
data.Add([new OpenIdConnectAuthObject(), SystemAuthenticationMethod.Basic, false, false]);
data.Add([new OpenIdConnectAuthObject(), SystemAuthenticationMethod.Basic, false, true]);
// Active Directory
// - wrong auth object
data.Add([new OpenIdConnectAuthObject(), SystemAuthenticationMethod.ActiveDirectory, false, false]);
data.Add([new OpenIdConnectAuthObject(), SystemAuthenticationMethod.ActiveDirectory, false, true]);
data.Add([new OpenIdConnectAuthObject(), SystemAuthenticationMethod.ActiveDirectory, true, false]);
data.Add([new OpenIdConnectAuthObject(), SystemAuthenticationMethod.ActiveDirectory, true, true]);
// - Active Directory not enabled
data.Add([new BasicAuthObject(), SystemAuthenticationMethod.ActiveDirectory, false, false]);
data.Add([new BasicAuthObject(), SystemAuthenticationMethod.ActiveDirectory, false, true]);
// OpenId Connect
// - wrong auth object
data.Add([new BasicAuthObject(), SystemAuthenticationMethod.OpenIdConnect, false, false]);
data.Add([new BasicAuthObject(), SystemAuthenticationMethod.OpenIdConnect, false, true]);
data.Add([new BasicAuthObject(), SystemAuthenticationMethod.OpenIdConnect, true, false]);
data.Add([new BasicAuthObject(), SystemAuthenticationMethod.OpenIdConnect, true, true]);
// - OIDC not enabled
data.Add([new OpenIdConnectAuthObject(), SystemAuthenticationMethod.OpenIdConnect, false, false]);
return data;
}
[Theory]
[MemberData(nameof(DataAuthenticatorTestError))]
public void GetAuthenticatorError(
AuthObject authObject, SystemAuthenticationMethod method, bool activeDirectoryEnabled, bool jwtEnabled)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
SetupAppSettings(session, method, activeDirectoryEnabled, jwtEnabled);
var authenticatorFactory = new AuthenticatorFactory(daoSession);
// Act
var result = authenticatorFactory.GetAuthenticator(authObject);
// Assert
Assert.Equal(ResultStatus.Error, result.Status);
}
private static ISession CreateSession()
{
var fluentConfiguration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m => m.FluentMappings.Add<AppUserMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeCompactMaps>())
.Mappings(m => m.FluentMappings.Add<SupportLevelMaps>())
.Mappings(m => m.FluentMappings.Add<MandatorMaps>())
.Mappings(m => m.FluentMappings.Add<CountryMaps>())
.Mappings(m => m.FluentMappings.Add<FederalStateMaps>())
.Mappings(m => m.FluentMappings.Add<AppUserGroupMaps>())
.Mappings(m => m.FluentMappings.Add<AppRightMaps>())
.Mappings(m => m.FluentMappings.Add<ApplicationVersionMaps>())
.Mappings(m => m.FluentMappings.Add<TicketMaps>())
.Mappings(m => m.FluentMappings.Add<AppSettingMaps>())
.Mappings(m => m.FluentMappings.Add<ApplicationSettingMaps>());
var sessionFactory = fluentConfiguration.BuildSessionFactory();
var session = sessionFactory.OpenSession();
new SchemaExport(fluentConfiguration.BuildConfiguration()).Execute(true, true, false, session.Connection,
Console.Out);
return session;
}
private static void SetupAppSettings(
ISession session, SystemAuthenticationMethod method = SystemAuthenticationMethod.None,
bool activeDirectoryEnabled = false, bool jwtEnabled = false)
{
// WebServiceConfigHelper
WebServiceConfigHelper.Current.ActiveDirectoryAuthEnabled = activeDirectoryEnabled;
// System authentication setting
DbSetupUtils.SaveApplicationSetting(
session,
ApplicationSettingID.SystemAuthenticationMethod,
valueInt: (int)method);
if (jwtEnabled)
{
DbSetupUtils.SaveApplicationSetting(
session,
ApplicationSettingID.JwtAuthority,
valueText: "https://localhost/some/test/uri");
DbSetupUtils.SaveApplicationSetting(
session,
ApplicationSettingID.JwtAudience,
valueText: "audience");
}
session.Flush();
}
}
@@ -0,0 +1,247 @@
using Centron.BusinessLogic.Administration.Licensing;
using Centron.Tests.BL.SetupUtils;
using Centron.BusinessLogic.Administration.Logins;
using Centron.BusinessLogic.Administration.Logins.Auth;
using Centron.DAO;
using Centron.DAO.Mappings;
using Centron.DAO.Mappings.Administration;
using Centron.DAO.Mappings.Administration.Applications;
using Centron.DAO.Mappings.Administration.Company;
using Centron.DAO.Mappings.Administration.Employees;
using Centron.DAO.Mappings.Administration.Logins;
using Centron.DAO.Mappings.CustomerArea.Support;
using Centron.DAO.Mappings.States;
using Centron.Data.Entities.Administration.Logins;
using Centron.Interfaces.Administration.Logins;
using Centron.Interfaces.BL;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using JetBrains.Annotations;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
namespace Centron.Tests.BL.Administration.Logins.Auth;
internal class ConcreteAuthenticator(
DAOSession session, AuthObject authObject, ILicenseManager licenseManager, LoggedInUser? loggedInUser = null)
: Authenticator(session, authObject, licenseManager)
{
protected override Result<LoggedInUser> AuthenticateInternal()
{
// only used for "GetTicket" tests
return loggedInUser ?? Result<LoggedInUser>.AsError("Authentication failed");
}
}
internal class ConcreteAuthObject : AuthObject;
[TestSubject(typeof(ConcreteAuthenticator))]
public class AuthenticatorTest
{
public static AuthObject DummyAuthObject => new ConcreteAuthObject
{
ApplicationName = ApplicationKind.Centron.LicenseGuid.ToString(),
AppVersion = "1.0.0.0",
MachineName = "machine"
};
public static IEnumerable<object?[]> GetDataValidateAppUser()
{
var yesterday = DateTime.Today - TimeSpan.FromDays(1);
var tomorrow = DateTime.Today + TimeSpan.FromDays(1);
return new List<object?[]>
{
new object?[] { ResultStatus.Success, false, null, null, 1 },
new object?[] { ResultStatus.Success, false, yesterday, null, 1 },
new object?[] { ResultStatus.Success, false, null, tomorrow, 1 },
new object?[] { ResultStatus.Success, false, yesterday, tomorrow, 1 },
new object?[] { ResultStatus.Error, true, null, null, 1 },
new object?[] { ResultStatus.Error, false, tomorrow, null, 1 },
new object?[] { ResultStatus.Error, false, null, yesterday, 1 },
new object?[] { ResultStatus.Error, false, tomorrow, yesterday, 1 },
new object?[] { ResultStatus.Error, false, null, yesterday, 1 },
new object?[] { ResultStatus.Error, false, null, null, 0 },
new object?[] { ResultStatus.Error, false, null, null, 2 },
};
}
[Theory]
[MemberData(nameof(GetDataValidateAppUser))]
public void ValidateAppUser(
ResultStatus expectedStatus, bool isAccountDisabled, DateTime? commencementDate, DateTime? leavingDate,
int state)
{
var session = CreateSession();
var daoSession = new DAOSession(session);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock();
var authenticator = new ConcreteAuthenticator(daoSession, DummyAuthObject, licenseManager);
var user = DbSetupUtils.SaveAppUser(
session,
"admin",
"admin@admin.com",
1,
isAccountDisabled: isAccountDisabled,
commencementDate: commencementDate,
leavingDate: leavingDate,
state: state);
var validationResult = authenticator.ValidateAppUser(user);
Assert.Equal(expectedStatus, validationResult.Status);
}
[Fact]
public void AuthenticateAppUserExistingTicket()
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock();
var authenticator = new ConcreteAuthenticator(daoSession, DummyAuthObject, licenseManager);
var user = DbSetupUtils.SaveAppUser(session, "admin", "admin@admin.com", 1);
var loggedInUser = new LoggedInUser(user);
var ticketBl = new TicketBL(daoSession);
ticketBl.CreateNewTicket(ApplicationKind.Centron, ApplicationKind.Centron.LicenseGuid, "machine", loggedInUser.User, null);
session.Flush();
// Act
var authenticationResult =
authenticator.AuthenticateUser(loggedInUser, ApplicationKind.Centron, "machine", "1.0.0.0");
// Assert
Assert.Equal(ResultStatus.Success, authenticationResult.Status);
}
public static IEnumerable<object?[]> GetDataAuthenticateAppUserNoTicket()
{
return new List<object?[]>
{
new object?[] { ResultStatus.Success, Result<Guid>.AsSuccess(LicenseGuids.Centron) },
new object?[] { ResultStatus.Error, Result<Guid>.AsError("") },
};
}
[Theory]
[MemberData(nameof(GetDataAuthenticateAppUserNoTicket))]
public void AuthenticateAppUserNoTicket(ResultStatus expectedStatus, Result<Guid> checkLicenseResult)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(checkLicenseResult);
var authenticator = new ConcreteAuthenticator(daoSession, DummyAuthObject, licenseManager);
var user = DbSetupUtils.SaveAppUser(session, "admin", "admin@admin.com", 1);
var loggedInUser = new LoggedInUser(user);
// Act
var authenticationResult =
authenticator.AuthenticateUser(loggedInUser, ApplicationKind.Centron, "machine",
"1.0.0.0");
// Assert
Assert.Equal(expectedStatus, authenticationResult.Status);
}
public static IEnumerable<object?[]> GetDataAppUserNoTicketDifferentArgs()
{
return new List<object?[]>
{
new object?[] { ResultStatus.Success, "machine", "1.0.0.0" },
new object?[] { ResultStatus.Warning, "machine", "not-a-real-version" },
new object?[] { ResultStatus.Warning, "machine", "" },
new object?[] { ResultStatus.Warning, "machine", null },
new object?[] { ResultStatus.Success, "", "1.0.0.0" },
new object?[] { ResultStatus.Success, null, "1.0.0.0" },
};
}
[Theory]
[MemberData(nameof(GetDataAppUserNoTicketDifferentArgs))]
public void AuthenticateAppUserNoTicketDifferentArgs(ResultStatus expectedStatus, string machine, string appVersion)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.Centron);
var authenticator = new ConcreteAuthenticator(daoSession, DummyAuthObject, licenseManager);
var user = DbSetupUtils.SaveAppUser(session, "admin", "admin@admin.com", 1);
var loggedInUser = new LoggedInUser(user);
// Act
var authenticationResult =
authenticator.AuthenticateUser(loggedInUser, ApplicationKind.Centron, machine, appVersion);
// Assert
Assert.Equal(expectedStatus, authenticationResult.Status);
}
[Fact]
public void GetTicketSuccess()
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
const string sub = "valid-sub";
const string email = "admin@admin.com";
var user = DbSetupUtils.SaveAppUser(session, "admin", email, 1, sub);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.Centron);
var authenticator = new ConcreteAuthenticator(daoSession, DummyAuthObject, licenseManager, new LoggedInUser(user)); // return user on authenticate
// Act
var result = authenticator.GetTicket();
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.NotEmpty(result.Data);
var ticketBl = new TicketBL(daoSession);
var ticketResult = ticketBl.GetTicket(result.Data);
Assert.Equal(ResultStatus.Success, ticketResult.Status);
Assert.NotNull(ticketResult.Data);
var ticket = ticketResult.Data;
Assert.Equal(user.I3D, ticket.UserI3D);
}
[Fact]
public void GetTicketFail()
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var user = DbSetupUtils.SaveAppUser(session, "admin", "admin@admin.com", 1);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock();
var authenticator = new ConcreteAuthenticator(daoSession, DummyAuthObject, licenseManager, null); // return Error on authenticate
// Act
var result = authenticator.GetTicket();
// Assert
Assert.Equal(ResultStatus.Error, result.Status);
}
private static ISession CreateSession()
{
var fluentConfiguration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m => m.FluentMappings.Add<AppUserMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeCompactMaps>())
.Mappings(m => m.FluentMappings.Add<SupportLevelMaps>())
.Mappings(m => m.FluentMappings.Add<MandatorMaps>())
.Mappings(m => m.FluentMappings.Add<CountryMaps>())
.Mappings(m => m.FluentMappings.Add<FederalStateMaps>())
.Mappings(m => m.FluentMappings.Add<AppUserGroupMaps>())
.Mappings(m => m.FluentMappings.Add<AppRightMaps>())
.Mappings(m => m.FluentMappings.Add<ApplicationVersionMaps>())
.Mappings(m => m.FluentMappings.Add<TicketMaps>());
var sessionFactory = fluentConfiguration.BuildSessionFactory();
var session = sessionFactory.OpenSession();
new SchemaExport(fluentConfiguration.BuildConfiguration()).Execute(true, true, false, session.Connection,
Console.Out);
return session;
}
}
@@ -0,0 +1,119 @@
using Centron.Tests.BL.SetupUtils;
using Centron.BusinessLogic.Administration.Logins.Auth;
using Centron.DAO;
using Centron.DAO.Mappings;
using Centron.DAO.Mappings.Administration;
using Centron.DAO.Mappings.Administration.Applications;
using Centron.DAO.Mappings.Administration.Company;
using Centron.DAO.Mappings.Administration.Employees;
using Centron.DAO.Mappings.Administration.Logins;
using Centron.DAO.Mappings.CustomerArea.Support;
using Centron.DAO.Mappings.States;
using Centron.Interfaces.Administration.Logins;
using Centron.Interfaces.BL;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using JetBrains.Annotations;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
namespace Centron.Tests.BL.Administration.Logins.Auth;
[TestSubject(typeof(BasicAuthenticator))]
public class BasicAuthenticatorTest
{
private const string _correctUsername = "admin";
private const string _correctPassword = "TestPassword123!";
private const string _otherUsername = "other";
private const string _otherPassword = "OtherPassword123!!";
[Fact]
public void AuthenticateSuccess()
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var user = DbSetupUtils.SaveAppUser(session, _correctUsername, "admin@admin.com", 1,
plainTextPassword: _correctPassword);
var otherUser = DbSetupUtils.SaveAppUser(session, _otherUsername, "different@mail.com", 2,
plainTextPassword: _otherPassword);
var authObject = new BasicAuthObject
{
ApplicationName = ApplicationKind.Centron.Name,
MachineName = "machine",
AppVersion = "1.0.0.0",
UserName = _correctUsername,
Password = _correctPassword
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock();
var authenticator = new BasicAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.Authenticate();
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.Equal(user.I3D, result.Data.User.I3D);
}
[Theory]
[InlineData("invalid-username", _correctPassword)]
[InlineData(_otherUsername, _correctPassword)]
[InlineData("", _correctPassword)]
[InlineData(null, _correctPassword)]
[InlineData(_correctUsername, "invalid-password")]
[InlineData(_correctUsername, _otherPassword)]
[InlineData(_correctUsername, "")]
[InlineData(_correctUsername, null)]
public void AuthenticateFailOnUsernamePassword(string? username, string? password)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var user = DbSetupUtils.SaveAppUser(session, _correctUsername, "admin@admin.com", 1,
plainTextPassword: _correctPassword);
var otherUser = DbSetupUtils.SaveAppUser(session, _otherUsername, "different@mail.com", 2,
plainTextPassword: _otherPassword);
var authObject = new BasicAuthObject
{
ApplicationName = ApplicationKind.Centron.Name,
MachineName = "machine",
AppVersion = "1.0.0.0",
UserName = username,
Password = password
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock();
var authenticator = new BasicAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.Authenticate();
// Assert
Assert.Equal(ResultStatus.Error, result.Status);
}
private static ISession CreateSession()
{
var fluentConfiguration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m => m.FluentMappings.Add<AppUserMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeCompactMaps>())
.Mappings(m => m.FluentMappings.Add<SupportLevelMaps>())
.Mappings(m => m.FluentMappings.Add<MandatorMaps>())
.Mappings(m => m.FluentMappings.Add<CountryMaps>())
.Mappings(m => m.FluentMappings.Add<FederalStateMaps>())
.Mappings(m => m.FluentMappings.Add<AppUserGroupMaps>())
.Mappings(m => m.FluentMappings.Add<AppRightMaps>())
.Mappings(m => m.FluentMappings.Add<ApplicationVersionMaps>())
.Mappings(m => m.FluentMappings.Add<TicketMaps>());
var sessionFactory = fluentConfiguration.BuildSessionFactory();
var session = sessionFactory.OpenSession();
new SchemaExport(fluentConfiguration.BuildConfiguration()).Execute(true, true, false, session.Connection,
Console.Out);
return session;
}
}
@@ -0,0 +1,193 @@
using System.Security.Claims;
using Centron.Tests.BL.SetupUtils;
using Centron.BusinessLogic.Administration.Logins;
using Centron.BusinessLogic.Administration.Logins.Auth;
using Centron.DAO;
using Centron.DAO.Mappings;
using Centron.DAO.Mappings.Administration;
using Centron.DAO.Mappings.Administration.Applications;
using Centron.DAO.Mappings.Administration.Company;
using Centron.DAO.Mappings.Administration.Employees;
using Centron.DAO.Mappings.Administration.Logins;
using Centron.DAO.Mappings.CustomerArea.Support;
using Centron.DAO.Mappings.States;
using Centron.Interfaces.Administration.Logins;
using Centron.Interfaces.BL;
using Centron.Tests.BL.Fixtures;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using JetBrains.Annotations;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
namespace Centron.Tests.BL.Administration.Logins.Auth;
[TestSubject(typeof(OpenIdConnectAuthenticator))]
public class OpenIdConnectAuthenticatorTest : IClassFixture<LicenseManagerFixture>
{
[Fact]
public void AuthenticateSuccess()
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
const string sub = "valid-sub";
const string email = "admin@admin.com";
var user = DbSetupUtils.SaveAppUser(session, "admin", email, 1, sub);
var otherUser = DbSetupUtils.SaveAppUser(session, "other", "different@mail.com", 2, "different-sub");
var identity = new ClaimsIdentity(new List<Claim>
{
new(OpenIdConnectAuthObject.SubjectIdentifierKey, sub),
new(OpenIdConnectAuthObject.EmailKey, email)
}, "test");
var authObject = new OpenIdConnectAuthObject
{
Identity = identity,
ApplicationName = ApplicationKind.Centron.Name,
MachineName = "machine",
AppVersion = "1.0.0.0"
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var authenticator = new OpenIdConnectAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.Authenticate();
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.Equal(user.I3D, result.Data.User.I3D);
}
[Theory]
[InlineData("invalid-sub")]
[InlineData("")]
[InlineData(null)]
public void AuthenticateFailOnSub(string? sub)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
const string email = "admin@admin.com";
var user = DbSetupUtils.SaveAppUser(session, "admin", email, 1, sub);
var otherUser = DbSetupUtils.SaveAppUser(session, "other", "different@mail.com", 2, "different-sub");
var authObject = new OpenIdConnectAuthObject
{
Identity = new ClaimsIdentity(new List<Claim>
{
new(OpenIdConnectAuthObject.SubjectIdentifierKey, "valid-sub"),
new(OpenIdConnectAuthObject.EmailKey, email)
}, "test"),
ApplicationName = ApplicationKind.Centron.LicenseGuid.ToString(),
MachineName = "machine",
AppVersion = "1.0.0.0"
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var authenticator = new OpenIdConnectAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.Authenticate();
// Assert
Assert.Equal(ResultStatus.Error, result.Status);
}
[Fact]
public void GetTicketSuccess()
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
const string sub = "valid-sub";
const string email = "admin@admin.com";
var user = DbSetupUtils.SaveAppUser(session, "admin", email, 1, sub);
var identity = new ClaimsIdentity(new List<Claim>
{
new(OpenIdConnectAuthObject.SubjectIdentifierKey, sub),
new(OpenIdConnectAuthObject.EmailKey, email)
}, "test");
var authObject = new OpenIdConnectAuthObject
{
Identity = identity,
ApplicationName = ApplicationKind.Centron.LicenseGuid.ToString(),
MachineName = "machine",
AppVersion = "1.0.0.0"
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.Centron, LicenseGuids.OpenIDConnectAuthentication);
var authenticator = new OpenIdConnectAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.GetTicket();
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.NotEmpty(result.Data);
var ticketBl = new TicketBL(daoSession);
var ticketResult = ticketBl.GetTicket(result.Data);
Assert.Equal(ResultStatus.Success, ticketResult.Status);
Assert.NotNull(ticketResult.Data);
var ticket = ticketResult.Data;
Assert.Equal(user.I3D, ticket.UserI3D);
}
[Theory]
[InlineData("invalid-sub")]
[InlineData("")]
[InlineData(null)]
public void GetTicketFailOnSub(string? sub)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
const string email = "admin@admin.com";
var user = DbSetupUtils.SaveAppUser(session, "admin", email, 1, sub);
var otherUser = DbSetupUtils.SaveAppUser(session, "other", "different@mail.com", 2, "different-sub");
var authObject = new OpenIdConnectAuthObject
{
Identity = new ClaimsIdentity(new List<Claim>
{
new(OpenIdConnectAuthObject.SubjectIdentifierKey, "valid-sub"),
new(OpenIdConnectAuthObject.EmailKey, email)
}, "test"),
ApplicationName = ApplicationKind.Centron.LicenseGuid.ToString(),
MachineName = "machine",
AppVersion = "1.0.0.0"
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var authenticator = new OpenIdConnectAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.GetTicket();
// Assert
Assert.Equal(ResultStatus.Error, result.Status);
}
private static ISession CreateSession()
{
var fluentConfiguration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m => m.FluentMappings.Add<AppUserMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeCompactMaps>())
.Mappings(m => m.FluentMappings.Add<SupportLevelMaps>())
.Mappings(m => m.FluentMappings.Add<MandatorMaps>())
.Mappings(m => m.FluentMappings.Add<CountryMaps>())
.Mappings(m => m.FluentMappings.Add<FederalStateMaps>())
.Mappings(m => m.FluentMappings.Add<AppUserGroupMaps>())
.Mappings(m => m.FluentMappings.Add<AppRightMaps>())
.Mappings(m => m.FluentMappings.Add<ApplicationVersionMaps>())
.Mappings(m => m.FluentMappings.Add<TicketMaps>());
var sessionFactory = fluentConfiguration.BuildSessionFactory();
var session = sessionFactory.OpenSession();
new SchemaExport(fluentConfiguration.BuildConfiguration()).Execute(true, true, false, session.Connection,
Console.Out);
return session;
}
}
@@ -0,0 +1,190 @@
using Centron.Tests.BL.SetupUtils;
using Centron.Tests.BL.DatabaseMappings;
using Centron.BusinessLogic.Administration.Logins.Auth;
using Centron.DAO;
using Centron.DAO.Mappings;
using Centron.DAO.Mappings.Accounts;
using Centron.DAO.Mappings.Administration;
using Centron.DAO.Mappings.Administration.Applications;
using Centron.DAO.Mappings.Administration.Company;
using Centron.DAO.Mappings.Administration.Employees;
using Centron.DAO.Mappings.Administration.FileManagement;
using Centron.DAO.Mappings.Administration.Logins;
using Centron.DAO.Mappings.Administration.Settings;
using Centron.DAO.Mappings.TemporaryEntities;
using Centron.DAO.Mappings.CustomerArea;
using Centron.DAO.Mappings.CustomerArea.CRM.Activities;
using Centron.DAO.Mappings.CustomerArea.CustomerDetails;
using Centron.DAO.Mappings.CustomerArea.Support;
using Centron.DAO.Mappings.EmployeeArea;
using Centron.DAO.Mappings.Logistics.Warehousing;
using Centron.DAO.Mappings.Merchandise.Articles;
using Centron.DAO.Mappings.Merchandise.Materialgroups;
using Centron.DAO.Mappings.Sales.Customers;
using Centron.DAO.Mappings.States;
using Centron.DAO.Mappings.Warehousing;
using Centron.DAO.Mappings.Warehousing.InventoryManagement;
using Centron.DAO.Mappings.Warehousing.StockManagement;
using Centron.Interfaces.Administration.Logins;
using Centron.Interfaces.BL;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using JetBrains.Annotations;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
namespace Centron.Tests.BL.Administration.Logins.Auth;
[TestSubject(typeof(WebAccountAuthenticator))]
public class WebAccountAuthenticatorTest
{
private const string _correctPassword = "TestPassword123!";
private const string _correctUsername = "admin";
[Fact]
public void AuthenticateSuccess()
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var responsibleUser = DbSetupUtils.SaveAppUser(session, "responsible-user", "responsible@email.com", 1);
DbSetupUtils.SetupResponsibleEmployeeForWebaccounts(session, responsibleUser.Employee);
var user = DbSetupUtils.SaveWebAccount(session, _correctUsername, "admin@admin.com", 2, plainTextPassword: _correctPassword);
var otherUser = DbSetupUtils.SaveWebAccount(session, "other", "different@mail.com", 3, plainTextPassword: "other-password");
var authObject = new WebAccountAuthObject
{
ApplicationName = ApplicationKind.Centron.Name,
MachineName = "machine",
AppVersion = "1.0.0.0",
UserName = _correctUsername,
Password = _correctPassword
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock();
var authenticator = new WebAccountAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.Authenticate();
// Assert
Assert.Equal(ResultStatus.Success, result.Status);
Assert.NotNull(result.Data);
Assert.Equal(user.I3D, result.Data.WebAccount.I3D);
Assert.Equal(responsibleUser.I3D, result.Data.User.I3D);
}
[Theory]
[InlineData("invalid-username", _correctPassword)]
[InlineData("", _correctPassword)]
[InlineData(null, _correctPassword)]
[InlineData(_correctUsername, "invalid-password")]
[InlineData(_correctUsername, "")]
[InlineData(_correctUsername, null)]
public void AuthenticateFailOnUsernamePassword(string? username, string? password)
{
// Arrange
var session = CreateSession();
var daoSession = new DAOSession(session);
var responsibleUser = DbSetupUtils.SaveAppUser(session, "responsible-user", "responsible@email.com", 1);
DbSetupUtils.SetupResponsibleEmployeeForWebaccounts(session, responsibleUser.Employee);
var user = DbSetupUtils.SaveWebAccount(session, _correctUsername, "admin@admin.com", 2, plainTextPassword: _correctPassword);
var otherUser = DbSetupUtils.SaveWebAccount(session, "other", "different@mail.com", 3, plainTextPassword: "other-password");
var authObject = new WebAccountAuthObject
{
ApplicationName = ApplicationKind.Centron.Name,
MachineName = "machine",
AppVersion = "1.0.0.0",
UserName = username,
Password = password
};
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock();
var authenticator = new WebAccountAuthenticator(daoSession, authObject, licenseManager);
// Act
var result = authenticator.Authenticate();
// Assert
Assert.Equal(ResultStatus.Error, result.Status);
}
private static ISession CreateSession()
{
var fluentConfiguration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m => m.FluentMappings.Add<AppUserMaps>())
.Mappings(m => m.FluentMappings.Add<AppSettingMaps>())
.Mappings(m => m.FluentMappings.Add<ApplicationSettingMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeCompactMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeDepartmentMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeArticleMaps>())
.Mappings(m => m.FluentMappings.Add<SupportLevelMaps>())
.Mappings(m => m.FluentMappings.Add<MandatorMaps>())
.Mappings(m => m.FluentMappings.Add<CountryMaps>())
.Mappings(m => m.FluentMappings.Add<FederalStateMaps>())
.Mappings(m => m.FluentMappings.Add<AppUserGroupMaps>())
.Mappings(m => m.FluentMappings.Add<AppRightMaps>())
.Mappings(m => m.FluentMappings.Add<ApplicationVersionMaps>())
.Mappings(m => m.FluentMappings.Add<WebAccountMaps>())
.Mappings(m => m.FluentMappings.Add<WebRightsCategoriesMaps>())
.Mappings(m => m.FluentMappings.Add<WebRightsMaps>())
.Mappings(m => m.FluentMappings.Add<ContactPersonMaps>())
.Mappings(m => m.FluentMappings.Add<AddressMaps>())
.Mappings(m => m.FluentMappings.Add<AccountMaps>())
.Mappings(m => m.FluentMappings.Add<AccountAddressMaps>())
.Mappings(m => m.FluentMappings.Add<AccountAddressContactMaps>())
.Mappings(m => m.FluentMappings.Add<GeoInfoMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerCompactMaps>())
// IMPORTANT: 'CustomerMapsForTest' instead of 'CustomerMaps' due to syntax error on column 'Limit'
.Mappings(m => m.FluentMappings.Add<CustomerMapsForTest>())
.Mappings(m => m.FluentMappings.Add<CustomerDepartmentMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerSalutationMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerBusinessLineMaps>())
.Mappings(m => m.FluentMappings.Add<BusinessLineMaps>())
.Mappings(m => m.FluentMappings.Add<SupplierMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerInterestMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerProductMaps>())
.Mappings(m => m.FluentMappings.Add<ArticleMaps>())
.Mappings(m => m.FluentMappings.Add<ArticleUnitMaps>())
.Mappings(m => m.FluentMappings.Add<ArticleLightMaps>())
.Mappings(m => m.FluentMappings.Add<PartListArticleMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerArticleMaps>())
.Mappings(m => m.FluentMappings.Add<ArticleCompactMaps>())
.Mappings(m => m.FluentMappings.Add<ArticleStockInfoMaps>())
.Mappings(m => m.FluentMappings.Add<SecondaryStockArticleMaps>())
.Mappings(m => m.FluentMappings.Add<ValueAddedTaxMaps>())
.Mappings(m => m.FluentMappings.Add<CostObjectMaps>())
.Mappings(m => m.FluentMappings.Add<StockMaps>())
.Mappings(m => m.FluentMappings.Add<StorageAreaMaps>())
.Mappings(m => m.FluentMappings.Add<SpecialAgreementMaps>())
.Mappings(m => m.FluentMappings.Add<SpecialAgreementArticleMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerSpecialPriceMaps>())
.Mappings(m => m.FluentMappings.Add<InterestMaps>())
.Mappings(m => m.FluentMappings.Add<ProductMaps>())
.Mappings(m => m.FluentMappings.Add<ActivityMaps>())
.Mappings(m => m.FluentMappings.Add<MaterialGroupMaps>())
.Mappings(m => m.FluentMappings.Add<MaterialGroupCompactMaps>())
.Mappings(m => m.FluentMappings.Add<MaterialMarkupMaps>())
.Mappings(m => m.FluentMappings.Add<SecondaryMaterialGroupMaps>())
.Mappings(m => m.FluentMappings.Add<SecondaryMaterialGroupCompactMaps>())
.Mappings(m => m.FluentMappings.Add<SecondaryMaterialMarkupMaps>())
.Mappings(m => m.FluentMappings.Add<AccountMaps>())
.Mappings(m => m.FluentMappings.Add<DirectoryMaps>())
.Mappings(m => m.FluentMappings.Add<CustomerDirectoryMaps>())
.Mappings(m => m.FluentMappings.Add<DocumentMaps>())
.Mappings(m => m.FluentMappings.Add<CostCenterMaps>())
.Mappings(m => m.FluentMappings.Add<TicketMaps>());
var sessionFactory = fluentConfiguration.BuildSessionFactory();
var session = sessionFactory.OpenSession();
new SchemaExport(fluentConfiguration.BuildConfiguration()).Execute(true, true, false, session.Connection,
Console.Out);
return session;
}
}
@@ -0,0 +1,439 @@
using Centron.DAO;
using Centron.Tests.BL.SetupUtils;
using Centron.DAO.Mappings;
using Centron.DAO.Mappings.Administration;
using Centron.DAO.Mappings.Administration.Company;
using Centron.DAO.Mappings.Administration.Employees;
using Centron.DAO.Mappings.CustomerArea.Support;
using Centron.DAO.Mappings.States;
using Centron.Data.WebServices.Administration;
using Centron.Data.WebServices.Administration.Login;
using Centron.Interfaces.Administration.Logins;
using Microsoft.Graph.Models;
namespace Centron.Tests.BL.Administration.Logins;
using Centron.BusinessLogic.Administration.Logins;
using Centron.Interfaces.BL;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;
using System.Collections.Generic;
using DAO;
using JetBrains.Annotations;
using NHibernate;
using Xunit;
[TestSubject(typeof(EntraIDUsersBL))]
public class EntraIDUsersBLTest
{
[Fact]
public async Task MatchEntraIDUsersWorksAsExpected()
{
var session = CreateSession();
DbSetupUtils.SaveAppUser(session, "admin", "admin@admin.com", 1, "1-unique");
DbSetupUtils.SaveAppUser(session, "root", "root@admin.com", 10, null);
DbSetupUtils.SaveAppUser(session, "Hans", "Hans@mail.com", 2, "2-matched");
DbSetupUtils.SaveAppUser(session, "Jake", "Jake@mail.com", 3, "3-matched");
DbSetupUtils.SaveAppUser(session, "Maria", "Maria@mail.com", 4, "4-unmatched");
DbSetupUtils.SaveAppUser(session, "Eve", "", 5, "5-matched");
DbSetupUtils.SaveAppUser(session, "Dave", "Dave@mail.com", 7, null);
DbSetupUtils.SaveAppUser(session, "Lia", "Lia@mail.com", 8, "8-true");
await session.FlushAsync();
List<User> entraUsers =
[
CreateUser("Hans@mail.com", "2-matched"),
CreateUser("jake@mail.com", "3-matched"),
CreateUser("Maria@mail.com", "6-unmatched"),
CreateUser("Eve@mail.com", "5-matched"),
CreateUser("Dave@mail.com", "7-notsyncedyet"),
CreateUser("Lia@mail.com", "8-false"),
CreateUser("Lia-false@mail.com", "8-true"),
CreateUser("john@mail.com", "10-unique")
];
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers =
[
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = new EntraIDUserDTO { Email = "Hans@mail.com", SubjectIdentifier = "2-matched" },
AppUser = new AppUserDTO
{ Name = "Hans", EmployeeI3D = 2, I3D = 2, OpenIdConnectSubjectIdentifier = "2-matched" }
},
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = new EntraIDUserDTO { Email = "jake@mail.com", SubjectIdentifier = "3-matched" },
AppUser = new AppUserDTO
{ Name = "Jake", EmployeeI3D = 3, I3D = 3, OpenIdConnectSubjectIdentifier = "3-matched" }
}
],
UnmatchedUsers =
[
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = new EntraIDUserDTO
{ Email = "Dave@mail.com", SubjectIdentifier = "7-notsyncedyet" },
AppUser = new AppUserDTO
{ Name = "Dave", EmployeeI3D = 7, I3D = 7, OpenIdConnectSubjectIdentifier = null }
}
],
MisconfiguredSub =
[
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = null,
AppUser = new AppUserDTO
{ Name = "admin", EmployeeI3D = 1, I3D = 1, OpenIdConnectSubjectIdentifier = "1-unique" }
},
new MatchedEntraIDCentronUserDTO
{
EntraIdUser =
new EntraIDUserDTO { Email = "Maria@mail.com", SubjectIdentifier = "6-unmatched" },
AppUser = new AppUserDTO
{ Name = "Maria", EmployeeI3D = 4, I3D = 4, OpenIdConnectSubjectIdentifier = "4-unmatched" }
},
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = new EntraIDUserDTO { Email = "Eve@mail.com", SubjectIdentifier = "5-matched" },
AppUser = new AppUserDTO
{ Name = "Eve", EmployeeI3D = 5, I3D = 5, OpenIdConnectSubjectIdentifier = "5-matched" }
},
new MatchedEntraIDCentronUserDTO()
{
EntraIdUser = new EntraIDUserDTO { Email = "Lia-false@mail.com", SubjectIdentifier = "8-true" },
AppUser = new AppUserDTO
{ Name = "Lia", EmployeeI3D = 8, I3D = 8, OpenIdConnectSubjectIdentifier = "8-true" }
}
],
UsersNotInCentron =
[
new EntraIDUserDTO { Email = "john@mail.com", SubjectIdentifier = "10-unique" }
],
UsersNotInEntra =
[
new AppUserDTO { Name = "root", EmployeeI3D = 10, I3D = 10, OpenIdConnectSubjectIdentifier = null }
]
});
var daoSession = new DAOSession(session);
var graphServiceClient = MockSetupUtils.PrepareGraphMock(entraUsers);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
[Fact]
public async Task MatchEntraIDUsersMatch()
{
var session = CreateSession();
DbSetupUtils.SaveAppUser(session, "user", "user@mail.com", 1, "match");
await session.FlushAsync();
List<User> entraUsers =
[
CreateUser("user@mail.com", "match"),
];
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers =
[
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = new EntraIDUserDTO { Email = "user@mail.com", SubjectIdentifier = "match" },
AppUser = new AppUserDTO
{ Name = "user", EmployeeI3D = 1, I3D = 1, OpenIdConnectSubjectIdentifier = "match" }
}
],
UnmatchedUsers = [],
MisconfiguredSub = [],
UsersNotInCentron = [],
UsersNotInEntra = []
});
var daoSession = new DAOSession(session);
var graphServiceClient = MockSetupUtils.PrepareGraphMock(entraUsers);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
[Fact]
public async Task MatchEntraIDUsersUnmatched()
{
var session = CreateSession();
DbSetupUtils.SaveAppUser(session, "user", "user@mail.com", 1, null);
await session.FlushAsync();
List<User> entraUsers =
[
CreateUser("user@mail.com", "not-synced-yet"),
];
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers = [],
UnmatchedUsers =
[
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = new EntraIDUserDTO
{ Email = "user@mail.com", SubjectIdentifier = "not-synced-yet" },
AppUser = new AppUserDTO
{ Name = "user", EmployeeI3D = 1, I3D = 1, OpenIdConnectSubjectIdentifier = null }
}
],
MisconfiguredSub = [],
UsersNotInCentron = [],
UsersNotInEntra = []
});
var daoSession = new DAOSession(session);
var graphServiceClient = MockSetupUtils.PrepareGraphMock(entraUsers);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
[Fact]
public async Task MatchEntraIDUsersMisconfiguredWrongEmail()
{
var session = CreateSession();
DbSetupUtils.SaveAppUser(session, "user", "wrong-user@mail.com", 1, "match");
await session.FlushAsync();
List<User> entraUsers =
[
CreateUser("correct-user@mail.com", "match"),
];
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers = [],
UnmatchedUsers = [],
MisconfiguredSub =
[
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = new EntraIDUserDTO
{ Email = "correct-user@mail.com", SubjectIdentifier = "match" },
AppUser = new AppUserDTO
{ Name = "user", EmployeeI3D = 1, I3D = 1, OpenIdConnectSubjectIdentifier = "match" }
}
],
UsersNotInCentron = [],
UsersNotInEntra = []
});
var daoSession = new DAOSession(session);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var graphServiceClient = MockSetupUtils.PrepareGraphMock(entraUsers);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
[Fact]
public async Task MatchEntraIDUsersMisconfiguredNoEntraUserWithSub()
{
var session = CreateSession();
DbSetupUtils.SaveAppUser(session, "user", "user@mail.com", 1, "non-existent-sub");
await session.FlushAsync();
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers = [],
UnmatchedUsers = [],
MisconfiguredSub =
[
new MatchedEntraIDCentronUserDTO
{
EntraIdUser = null,
AppUser = new AppUserDTO
{
Name = "user", EmployeeI3D = 1, I3D = 1, OpenIdConnectSubjectIdentifier = "non-existent-sub"
}
}
],
UsersNotInCentron = [],
UsersNotInEntra = []
});
var daoSession = new DAOSession(session);
var graphServiceClient = MockSetupUtils.PrepareGraphMock([]);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
[Fact]
public async Task MatchEntraIDUsersNotInCentron()
{
var session = CreateSession();
await session.FlushAsync();
List<User> entraUsers =
[
CreateUser("user@mail.com", "not-synced-yet"),
];
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers = [],
UnmatchedUsers = [],
MisconfiguredSub = [],
UsersNotInCentron =
[
new EntraIDUserDTO { Email = "user@mail.com", SubjectIdentifier = "not-synced-yet" }
],
UsersNotInEntra = []
});
var daoSession = new DAOSession(session);
var graphServiceClient = MockSetupUtils.PrepareGraphMock(entraUsers);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
[Fact]
public async Task MatchEntraIDUsersNotInEntra()
{
var session = CreateSession();
DbSetupUtils.SaveAppUser(session, "user", "user@mail.com", 1, null);
await session.FlushAsync();
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers = [],
UnmatchedUsers = [],
MisconfiguredSub = [],
UsersNotInCentron = [],
UsersNotInEntra =
[
new AppUserDTO { Name = "user", EmployeeI3D = 1, I3D = 1, OpenIdConnectSubjectIdentifier = null }
]
});
var daoSession = new DAOSession(session);
var graphServiceClient = MockSetupUtils.PrepareGraphMock([]);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
[Fact]
public async Task MatchEntraIDUsersNotActive()
{
var session = CreateSession();
DbSetupUtils.SaveAppUser(session, "disabled", "disabled@mail.com", 1, isAccountDisabled: true);
DbSetupUtils.SaveAppUser(session, "disabled-from-today", "disabled-from-today@mail.com", 2,
disabledFromDate: DateTime.Today);
DbSetupUtils.SaveAppUser(session, "disabled-from-yesterday", "disabled-from-yesterday@mail.com", 3,
disabledFromDate: DateTime.Today - TimeSpan.FromDays(1));
DbSetupUtils.SaveAppUser(session, "disabled-to-today", "disabled-to-today@mail.com", 4,
disabledToDate: DateTime.Today);
DbSetupUtils.SaveAppUser(session, "disabled-to-tomorrow", "disabled-to-tomorrow@mail.com", 5,
disabledToDate: DateTime.Today + TimeSpan.FromDays(1));
DbSetupUtils.SaveAppUser(session, "disabled-in-range", "disabled-in-range@mail.com", 6,
disabledFromDate: DateTime.Today - TimeSpan.FromDays(1), disabledToDate: DateTime.Today + TimeSpan.FromDays(1));
await session.FlushAsync();
List<User> entraUsers =
[
CreateUser("disabled@mail.com", "not-synced-yet-1"),
CreateUser("disabled-from-today@mail.com", "not-synced-yet-2"),
CreateUser("disabled-from-yesterday@mail.com", "not-synced-yet-3"),
CreateUser("disabled-to-today@mail.com", "not-synced-yet-4"),
CreateUser("disabled-to-tomorrow@mail.com", "not-synced-yet-5"),
CreateUser("disabled-in-range@mail.com", "not-synced-yet-6"),
];
var expectedResult =
Result<MatchUsersResultDTO>.AsSuccess(new MatchUsersResultDTO
{
MatchedUsers = [],
UnmatchedUsers = [],
MisconfiguredSub = [],
UsersNotInCentron = [
new EntraIDUserDTO { Email = "disabled@mail.com", SubjectIdentifier = "not-synced-yet-1" },
new EntraIDUserDTO { Email = "disabled-from-today@mail.com", SubjectIdentifier = "not-synced-yet-2" },
new EntraIDUserDTO { Email = "disabled-from-yesterday@mail.com", SubjectIdentifier = "not-synced-yet-3" },
new EntraIDUserDTO { Email = "disabled-to-today@mail.com", SubjectIdentifier = "not-synced-yet-4" },
new EntraIDUserDTO { Email = "disabled-to-tomorrow@mail.com", SubjectIdentifier = "not-synced-yet-5" },
new EntraIDUserDTO { Email = "disabled-in-range@mail.com", SubjectIdentifier = "not-synced-yet-6" }
],
UsersNotInEntra = []
});
var daoSession = new DAOSession(session);
var graphServiceClient = MockSetupUtils.PrepareGraphMock(entraUsers);
var licenseManager = MockSetupUtils.PrepareLicenseManagerMock(LicenseGuids.OpenIDConnectAuthentication);
var entraIDUsersBL = new EntraIDUsersBL(daoSession, graphServiceClient, licenseManager);
var matchEntraIdUsers = await entraIDUsersBL.MatchEntraIDUsers();
Assert.Equivalent(expectedResult.Data, matchEntraIdUsers.Data, true);
}
private User CreateUser(string email, string subjectId)
{
return new User
{
Id = subjectId,
UserPrincipalName = email,
};
}
public ISession CreateSession()
{
var fluentConfiguration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m => m.FluentMappings.Add<AppUserMaps>())
.Mappings(m => m.FluentMappings.Add<EmployeeCompactMaps>())
.Mappings(m => m.FluentMappings.Add<SupportLevelMaps>())
.Mappings(m => m.FluentMappings.Add<MandatorMaps>())
.Mappings(m => m.FluentMappings.Add<CountryMaps>())
.Mappings(m => m.FluentMappings.Add<FederalStateMaps>())
.Mappings(m => m.FluentMappings.Add<AppUserGroupMaps>())
.Mappings(m => m.FluentMappings.Add<AppRightMaps>());
ISessionFactory sessionFactory = fluentConfiguration.BuildSessionFactory();
ISession session = sessionFactory.OpenSession();
new SchemaExport(fluentConfiguration.BuildConfiguration()).Execute(true, true, false, session.Connection,
Console.Out);
return session;
}
}
@@ -0,0 +1,254 @@
using System.Globalization;
using Centron.BusinessLogic.Administration.Masterdata;
using Centron.DAO;
using Centron.DAO.Mappings;
using Centron.DAO.Mappings.Administration;
using Centron.Data.Entities.Administration.MasterData;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using JetBrains.Annotations;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
namespace Centron.Tests.BL.Administration.Masterdata;
/// <summary>
/// Tests for the AssetConditionBL class, specifically testing the BR-DE-18 Skonto formatting
/// for ZUGFeRD/XRechnung export functionality.
/// </summary>
[TestSubject(typeof(AssetConditionBL))]
public class AssetConditionBLTest
{
[Fact]
public void GetPaymentConditionSkontoInBR_DE_18Format_ReturnsNull_WhenNoConditionExists()
{
// Arrange
using var session = CreateSession();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
var nonExistentI3D = 999;
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(nonExistentI3D);
// Assert
Assert.Null(result);
}
[Fact]
public void GetPaymentConditionSkontoInBR_DE_18Format_ReturnsNull_WhenNoSkontoData()
{
// Arrange
using var session = CreateSession();
var condition = new AssetCondition
{
ShortName = "NO_SKONTO",
Skonto1OffDay = 0,
Skonto1Percent = 0,
Skonto2OffDay = 0,
Skonto2Percent = 0
};
session.Save(condition);
session.Flush();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(condition.I3D);
// Assert
Assert.Null(result);
}
[Fact]
public void GetPaymentConditionSkontoInBR_DE_18Format_FormatsCorrectly_WhenSingleSkontoLevel()
{
// Arrange
using var session = CreateSession();
var condition = new AssetCondition
{
ShortName = "SKONTO_14_2.5",
Skonto1OffDay = 14,
Skonto1Percent = 2.5,
Skonto2OffDay = 0,
Skonto2Percent = 0
};
session.Save(condition);
session.Flush();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(condition.I3D);
// Assert
Assert.Equal("#SKONTO#TAGE=14#PROZENT=2.50#", result);
}
[Fact]
public void GetPaymentConditionSkontoInBR_DE_18Format_FormatsCorrectly_WhenTwoSkontoLevels()
{
// Arrange
using var session = CreateSession();
var condition = new AssetCondition
{
ShortName = "SKONTO_MULTI",
Skonto1OffDay = 10,
Skonto1Percent = 3.0,
Skonto2OffDay = 30,
Skonto2Percent = 1.5
};
session.Save(condition);
session.Flush();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(condition.I3D);
// Assert
Assert.Equal($"#SKONTO#TAGE=10#PROZENT=3.00#{Environment.NewLine}#SKONTO#TAGE=30#PROZENT=1.50#", result);
}
[Fact]
public void GetPaymentConditionSkontoInBR_DE_18Format_UsesInvariantCulture_WhenGermanCultureIsSet()
{
// Arrange
var originalCulture = Thread.CurrentThread.CurrentCulture;
try
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE");
using var session = CreateSession();
var condition = new AssetCondition
{
ShortName = "SKONTO_DE",
Skonto1OffDay = 14,
Skonto1Percent = 2.75,
Skonto2OffDay = 0,
Skonto2Percent = 0
};
session.Save(condition);
session.Flush();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(condition.I3D);
// Assert
Assert.Equal("#SKONTO#TAGE=14#PROZENT=2.75#", result);
Assert.DoesNotContain(",", result); // Should use dot, not comma
}
finally
{
Thread.CurrentThread.CurrentCulture = originalCulture;
}
}
[Fact]
public void GetPaymentConditionSkontoInBR_DE_18Format_ReturnsCorrectFormat_WhenOnlySkonto2HasData()
{
// Arrange
using var session = CreateSession();
var condition = new AssetCondition
{
ShortName = "SKONTO2_ONLY",
Skonto1OffDay = 0,
Skonto1Percent = 0,
Skonto2OffDay = 21,
Skonto2Percent = 1.25
};
session.Save(condition);
session.Flush();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(condition.I3D);
// Assert
Assert.Equal("#SKONTO#TAGE=21#PROZENT=1.25#", result);
}
[Theory]
[InlineData(14, 0)] // Days without percent
[InlineData(0, 2.5)] // Percent without days
public void GetPaymentConditionSkontoInBR_DE_18Format_ReturnsNull_WhenIncompleteData(int days, double percent)
{
// Arrange
using var session = CreateSession();
var condition = new AssetCondition
{
ShortName = "INCOMPLETE",
Skonto1OffDay = days,
Skonto1Percent = percent,
Skonto2OffDay = 0,
Skonto2Percent = 0
};
session.Save(condition);
session.Flush();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(condition.I3D);
// Assert
Assert.Null(result);
}
[Theory]
[InlineData(7, 0.5, "#SKONTO#TAGE=7#PROZENT=0.50#")]
[InlineData(5, 10.99, "#SKONTO#TAGE=5#PROZENT=10.99#")]
[InlineData(365, 99.99, "#SKONTO#TAGE=365#PROZENT=99.99#")]
public void GetPaymentConditionSkontoInBR_DE_18Format_HandlesEdgeCases_Correctly(int days, double percent, string expected)
{
// Arrange
using var session = CreateSession();
var condition = new AssetCondition
{
ShortName = "EDGE_CASE",
Skonto1OffDay = days,
Skonto1Percent = percent,
Skonto2OffDay = 0,
Skonto2Percent = 0
};
session.Save(condition);
session.Flush();
var daoSession = new DAOSession(session);
var sut = new AssetConditionBL(daoSession);
// Act
var result = sut.GetPaymentConditionSkontoInBR_DE_18Format(condition.I3D);
// Assert
Assert.Equal(expected, result);
}
private static ISession CreateSession()
{
var fluentConfig = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory())
.Mappings(m =>
{
m.FluentMappings.Add<AssetConditionMaps>();
});
var sessionFactory = fluentConfig.BuildSessionFactory();
var session = sessionFactory.OpenSession();
// Create schema in the in-memory database
new SchemaExport(fluentConfig.BuildConfiguration())
.Execute(false, true, false, session.Connection, null);
return session;
}
}