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
}