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,197 @@
using Centron.DAO;
using Centron.Data.WebServices.Sales.Receipts;
using Centron.Tests.EndToEnd.Infrastructure.Verifier;
using DevExpress.CodeParser;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Forms;
using Xunit;
using Centron.Core;
using Centron.Core.Utils;
using DevExpress.Spreadsheet;
namespace Centron.Tests.EndToEnd.Infrastructure.Verifier
{
public class CentronVerifier
{
public CentronVerifier()
{
this.Settings = new CentronVerifierSettings();
this.Settings.IgnoreProperty<ReceiptBaseDTO>(f => f.CreatedThroughApplicationVersion);
this.Settings.IgnoreProperty<ReceiptBaseDTO>(f => f.ChangedThroughApplicationVersion);
}
public CentronVerifierSettings Settings { get; }
public bool OverrideExpectedFiles { get; set; }
public void VerifyTable(string name, string tableName, string where = null, CentronVerifierSettings settings = null, [CallerFilePath] string sourceFile = "")
{
where = where ?? "1 = 1";
string sqlStatement = $"SELECT * FROM {tableName} WHERE {where}";
this.VerifySql(name, sqlStatement, settings, sourceFile);
}
public void VerifySql(string name, string sqlStatement, CentronVerifierSettings settings = null, [CallerFilePath] string sourceFile = "")
{
using (var session = new DAOSession())
{
var table = session.Advanced.RawSqlAccess.GetRawSqlResult(sqlStatement);
this.Verify(name, table, settings, sourceFile: sourceFile);
}
}
public void VerifyExcel(string fileName, byte[] excel, [CallerFilePath] string sourceFile = "")
{
var workbook = new Workbook();
workbook.LoadDocument(excel);
var worksheet = workbook.Worksheets[0];
string[,] excelCellValues = new string[worksheet.Rows.LastUsedIndex + 1, worksheet.Columns.LastUsedIndex + 1];
for (int i = 0; i <= worksheet.Rows.LastUsedIndex; i++)
for (int j = 0; j <= worksheet.Columns.LastUsedIndex; j++)
{
var testCell = worksheet.Cells[i, j].Value.ToString();
excelCellValues[i, j] = testCell;
}
this.Verify(fileName, excelCellValues, sourceFile: sourceFile);
}
public void Verify(string name, object data, CentronVerifierSettings settings = null, [CallerFilePath] string sourceFile = "")
{
Guard.NotNullOrWhiteSpace(name, nameof(name));
//data can be NULL
//differentPerTargetFramework can be anything
//settings can be NULL
Guard.NotNullOrWhiteSpace(sourceFile, nameof(sourceFile));
if (settings != null)
{
settings.MergeWith(this.Settings);
}
else
{
settings = this.Settings;
}
var json = this.SerializeAsJson(data, settings);
var expectedFileName = this.GetExpectedFileName(name, sourceFile);
var actualFileName = this.GetActualFileName(name, sourceFile);
var directory = Path.GetDirectoryName(expectedFileName);
if (Directory.Exists(directory) == false)
Directory.CreateDirectory(directory);
if (this.OverrideExpectedFiles)
{
File.WriteAllText(expectedFileName, json, Encoding.UTF8);
return;
}
File.WriteAllText(actualFileName, json, Encoding.UTF8);
var expected = File.Exists(expectedFileName)
? File.ReadAllText(expectedFileName, Encoding.UTF8)
: string.Empty;
expected = expected.Replace(@"\r", @"");
json = json.Replace(@"\r", @"");
try
{
Assert.Equal(expected, json, ignoreLineEndingDifferences:true);
}
catch (Exception e)
{
if(Debugger.IsAttached)
this.StartDiffTool(expectedFileName, actualFileName);
Console.WriteLine(json);
throw new Exception(name + " failed", e);
}
}
private string SerializeAsJson(object data, CentronVerifierSettings verifierSettings)
{
var settings = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
Converters = new List<JsonConverter>
{
verifierSettings.CleanupGuidValues ? new CleanupValuesConverter<Guid>() : null,
verifierSettings.CleanupDateTimeValues ? new CleanupValuesConverter<DateTime>(new DateTimeEqualityComparer()) : null,
verifierSettings.CleanupDateTimeOffsetValues ? new CleanupValuesConverter<DateTimeOffset>(new DateTimeOffsetEqualityComparer()) : null,
}
.Where(f => f != null)
.ToList(),
ContractResolver = verifierSettings.GetContractResolver(),
};
var ignoredColumns = verifierSettings.GetIgnoredColumns();
if (ignoredColumns.Any() && data is DataTable table)
{
foreach (var ignoredColumn in ignoredColumns)
{
if (table.Columns.Contains(ignoredColumn))
table.Columns.Remove(ignoredColumn);
}
}
var serializer = JsonSerializer.Create(settings);
var builder = new StringBuilder();
using (var stringWriter = new StringWriter(builder))
using (var writer = new JsonTextWriter(stringWriter) { QuoteChar = '\'', QuoteName = false })
{
serializer.Serialize(writer, data);
}
builder.Replace(@"\\", @"\");
return builder.ToString();
}
private string GetExpectedFileName(string name, string sourceFile)
{
return $"{Path.GetDirectoryName(sourceFile)}/{name}.expected.txt";
}
private string GetActualFileName(string name, string sourceFile)
{
return $"{Path.GetDirectoryName(sourceFile)}/{name}.actual.txt";
}
private void StartDiffTool(string expectedFileName, string actualFileName)
{
try
{
if (File.Exists(expectedFileName) == false || File.Exists(actualFileName) == false)
return;
string vsPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\devenv.exe";
if (File.Exists(vsPath))
ProcessHelper.Start(vsPath, $"/diff \"{actualFileName}\" \"{expectedFileName}\"");
else
ProcessHelper.Start("code", $"--diff \"{actualFileName}\" \"{expectedFileName}\"");
}
catch (Exception)
{
//Ooops
}
}
}
}
@@ -0,0 +1,273 @@
using DevExpress.CodeParser;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Centron.Core;
namespace Centron.Tests.EndToEnd.Infrastructure.Verifier
{
public class CentronVerifierSettings
{
private readonly List<string> _ignoredColumns;
private readonly Dictionary<Type, List<string>> _ignoredProperties;
private readonly Dictionary<(Type type, string propertyName), List<(string regex, string replace)>> _cleanupProperties;
private readonly Dictionary<Type, List<string>> _noDateCleanupProperties;
public CentronVerifierSettings()
{
this._ignoredColumns = new List<string>();
this._ignoredProperties = new Dictionary<Type, List<string>>();
this._cleanupProperties = new Dictionary<(Type type, string propertyName), List<(string regex, string replace)>>();
this._noDateCleanupProperties = new Dictionary<Type, List<string>>();
this.CleanupGuidValues = true;
this.CleanupDateTimeValues = true;
this.CleanupDateTimeOffsetValues = true;
}
public void IgnoreColumn(string columnName)
{
this._ignoredColumns.Add(columnName);
}
public void IgnoreProperties<T>(params Expression<Func<T, object>> [] properties)
{
foreach (var property in properties)
this.IgnoreProperty(property);
}
public void IgnoreProperty<T>(Expression<Func<T, object>> property)
{
Guard.NotNull(property, nameof(property));
if (this._ignoredProperties.ContainsKey(typeof(T)) == false)
{
this._ignoredProperties[typeof(T)] = new List<string>();
}
this._ignoredProperties[typeof(T)].Add(this.GetPropertyName(property));
}
public void CleanupProperty<T>(Expression<Func<T, object>> property, string regex, string replace)
{
Guard.NotNull(property, nameof(property));
Guard.NotNullOrWhiteSpace(regex, nameof(regex));
//replace can be null
var propertyName = this.GetPropertyName(property);
var key = (typeof(T), propertyName);
if (this._cleanupProperties.ContainsKey(key) == false)
{
this._cleanupProperties[key] = new List<(string regex, string replace)>();
}
this._cleanupProperties[key].Add((regex, replace));
}
public void NoDateCleanup<T>(Expression<Func<T, string>> property)
{
Guard.NotNull(property, nameof(property));
var propertyName = this.GetPropertyName(property);
if (this._noDateCleanupProperties.ContainsKey(typeof(T)) == false)
{
this._noDateCleanupProperties[typeof(T)] = new List<string>();
}
this._noDateCleanupProperties[typeof(T)].Add(propertyName);
}
public void MergeWith(CentronVerifierSettings settings)
{
Guard.NotNull(settings, nameof(settings));
foreach (var ignoredProperty in settings._ignoredProperties)
{
if (this._ignoredProperties.ContainsKey(ignoredProperty.Key) == false)
{
this._ignoredProperties.Add(ignoredProperty.Key, new List<string>(ignoredProperty.Value)); //Make sure to create a copy of the list, so we can't change the original one by accident
}
else
{
this._ignoredProperties[ignoredProperty.Key].AddRange(ignoredProperty.Value);
}
}
foreach (var cleanupProperty in settings._cleanupProperties)
{
if (this._cleanupProperties.ContainsKey(cleanupProperty.Key) == false)
{
this._cleanupProperties.Add(cleanupProperty.Key, new List<(string, string)>(cleanupProperty.Value)); //Make sure to create a copy of the list, so we can't change the original one by accident
}
else
{
this._cleanupProperties[cleanupProperty.Key].AddRange(cleanupProperty.Value);
}
}
foreach (var noDate in settings._noDateCleanupProperties)
{
if (this._noDateCleanupProperties.ContainsKey(noDate.Key) == false)
{
this._noDateCleanupProperties.Add(noDate.Key, new List<string>(noDate.Value)); //Make sure to create a copy of the list, so we can't change the original one by accident
}
else
{
this._noDateCleanupProperties[noDate.Key].AddRange(noDate.Value);
}
}
this.CleanupGuidValues = settings.CleanupGuidValues || this.CleanupGuidValues;
this.CleanupDateTimeValues = settings.CleanupDateTimeValues || this.CleanupDateTimeValues;
this.CleanupDateTimeOffsetValues = settings.CleanupDateTimeOffsetValues || this.CleanupDateTimeOffsetValues;
}
public IContractResolver GetContractResolver()
{
return new VerifierContractResolver(this);
}
public List<string> GetIgnoredColumns()
{
return this._ignoredColumns;
}
public bool CleanupGuidValues { get; set; }
public bool CleanupDateTimeValues { get; set; }
public bool CleanupDateTimeOffsetValues { get; set; }
private string GetPropertyName<T, TResult>(Expression<Func<T, TResult>> property)
{
var unary = property.Body as UnaryExpression;
var expr = property.Body as MemberExpression ?? unary?.Operand as MemberExpression;
var prop = expr.Member as PropertyInfo;
return prop.Name;
}
#region Internal
private class VerifierContractResolver : DefaultContractResolver
{
private readonly CentronVerifierSettings _settings;
public VerifierContractResolver(CentronVerifierSettings settings)
{
Guard.NotNull(settings, nameof(settings));
this._settings = settings;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return base.CreateProperties(type, memberSerialization).OrderBy(f => f.PropertyName).ToList();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
foreach (var ignoredProperty in this._settings._ignoredProperties)
{
if (property.DeclaringType.IsAssignableFrom(ignoredProperty.Key) && ignoredProperty.Value.Contains(property.PropertyName))
{
property.Ignored = true;
}
}
foreach (var cleanupProperty in this._settings._cleanupProperties)
{
if (property.DeclaringType.IsAssignableFrom(cleanupProperty.Key.type) && cleanupProperty.Key.propertyName == property.PropertyName)
{
foreach (var cleanup in cleanupProperty.Value)
{
property.ValueProvider = new CleanupValueProvider(property.ValueProvider, cleanup.regex, cleanup.replace);
}
}
}
if (property.PropertyType == typeof(string))
{
bool doNotCleanupDate = this._settings._noDateCleanupProperties
.Where(f => property.DeclaringType.IsAssignableFrom(f.Key))
.Where(f => f.Value.Contains(property.PropertyName))
.Any();
if (doNotCleanupDate == false)
property.ValueProvider = new CleanupDateValueProvider(property.ValueProvider);
}
return property;
}
}
private class CleanupValueProvider : IValueProvider
{
private readonly IValueProvider _inner;
private readonly string _regex;
private readonly string _replace;
public CleanupValueProvider(IValueProvider inner, string regex, string replace)
{
Guard.NotNull(inner, nameof(inner));
Guard.NotNullOrWhiteSpace(regex, nameof(regex));
//replace can be NULL
this._inner = inner;
this._regex = regex;
this._replace = replace;
}
public object GetValue(object target)
{
var value = this._inner.GetValue(target);
if (value == null)
return null;
return Regex.Replace((string)value, this._regex, this._replace);
}
public void SetValue(object target, object value)
{
this._inner.SetValue(target, value);
}
}
private class CleanupDateValueProvider : IValueProvider
{
private readonly IValueProvider _inner;
public CleanupDateValueProvider(IValueProvider inner)
{
Guard.NotNull(inner, nameof(inner));
this._inner = inner;
}
public object GetValue(object target)
{
var value = this._inner.GetValue(target);
if (value == null)
return null;
value = Regex.Replace((string)value, RegexHelper.Date, "EinDatum");
value = Regex.Replace((string)value, RegexHelper.RtfCreatedDate, string.Empty);
return value;
}
public void SetValue(object target, object value)
{
this._inner.SetValue(target, value);
}
}
#endregion
}
}
@@ -0,0 +1,34 @@
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Centron.Tests.EndToEnd.Infrastructure.Verifier
{
internal class CleanupValuesConverter<T> : JsonConverter
where T : struct
{
public CleanupValuesConverter(IEqualityComparer<T> equalityComparer = null)
{
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T) || objectType == typeof(T?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
return;
writer.WriteRawValue(typeof(T).Name);
}
}
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace Centron.Tests.EndToEnd.Infrastructure.Verifier
{
internal class DateTimeEqualityComparer : EqualityComparer<DateTime>
{
public override bool Equals(DateTime x, DateTime y)
{
return x.Year == y.Year &&
x.Month == y.Month &&
x.Day == y.Day &&
x.Hour == y.Hour &&
x.Minute == y.Minute &&
x.Second == y.Second;
}
public override int GetHashCode(DateTime obj)
{
return Tuple.Create(obj.Year, obj.Month, obj.Day, obj.Hour, obj.Minute, obj.Second).GetHashCode();
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace Centron.Tests.EndToEnd.Infrastructure.Verifier
{
internal class DateTimeOffsetEqualityComparer : EqualityComparer<DateTimeOffset>
{
public override bool Equals(DateTimeOffset x, DateTimeOffset y)
{
return x.Year == y.Year &&
x.Month == y.Month &&
x.Day == y.Day &&
x.Hour == y.Hour &&
x.Minute == y.Minute &&
x.Second == y.Second &&
x.Offset == y.Offset;
}
public override int GetHashCode(DateTimeOffset obj)
{
return Tuple.Create(obj.Year, obj.Month, obj.Day, obj.Hour, obj.Minute, obj.Second, obj.Offset).GetHashCode();
}
}
}