Files
Christoph Schwörer f045b99a25 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
2026-08-26 07:43:51 +02:00

357 lines
12 KiB
C#

using System;
using System.Data.SqlClient;
using System.IO;
using System.IO.Compression;
using System.Runtime.ExceptionServices;
using Centron.BusinessLogic;
using Centron.BusinessLogic.Administration.Scripts;
using Centron.BusinessLogic.Sales.Receipts;
using Centron.Common;
using Centron.Core.Extensions;
using Centron.Core.Utils;
using Centron.DAO;
using Centron.DAO.DAOConnections;
using Centron.Interfaces.BL;
using Xunit;
namespace Centron.Tests.EndToEnd.Infrastructure
{
public class Database
{
#region Database Server
public static string Server
{
get
{
var server = GetEnvironmentVariable("CENTRON_TESTS_DATABASE_SERVER");
if (string.IsNullOrWhiteSpace(server) == false)
return server;
throw new Exception("You have to configure the SQL server for EndToEndTests");
// return "YOUR-MACHINE\\SQL2017"; //Enter your local SQL server here
}
}
public static string Username
{
get
{
var username = GetEnvironmentVariable("CENTRON_TESTS_DATABASE_USERNAME");
if (string.IsNullOrWhiteSpace(username) == false)
return username;
throw new Exception("You have to configure the SQL server username for EndToEndTests");
// return "sql-username"; //Enter your local SQL username here
}
}
public static string Password
{
get
{
var password = GetEnvironmentVariable("CENTRON_TESTS_DATABASE_PASSWORD");
if (string.IsNullOrWhiteSpace(password) == false)
return password;
throw new Exception("You have to configure the SQL server password for EndToEndTests");
// return "password"; //Enter your local SQL password here
}
}
#endregion
private static object _lock = new object();
private static Exception _initializeException;
private static string _databaseName;
private static string _snapshotName;
public static IDisposable Initialize()
{
lock (_lock)
{
if (string.IsNullOrWhiteSpace(_databaseName) == false || string.IsNullOrWhiteSpace(_snapshotName) == false)
throw new Exception("Database.Initialize can only be called once.");
// If there was an error when initializing the Database before
// Rethrow that exception here, so we don't waste a lot of time restoring the database again
if (_initializeException is not null)
ExceptionDispatchInfo.Throw(_initializeException); // This keeps the original exception stack-trace
// Start database initialization in the beginning to let it run parallel to database restore
DAOFactory.Instance.InitializeAsync();
string databaseName = GetDatabaseName();
string snapshotName = GetSnapshotName(databaseName);
RestoreDatabase(databaseName);
try
{
Connect(databaseName);
UpdateDatabase();
}
catch (Exception e)
{
try
{
// If something fails when connecting to the database, or executing scripts
// we want to Drop the database again.
// The test will not be able to run, but we were leaving old database backups behind.
// So, lets clean up after ourselves and Drop the database if something goes wrong.
DropDatabase(databaseName);
}
catch
{
// Dropping the database failed. Unlucky, but not much we can do.
}
_initializeException = e;
throw;
}
try
{
CreateSnapshot(snapshotName, databaseName);
}
catch (Exception e)
{
// The test will not be able to run, but we were leaving old database backups behind.
// So, lets clean up after ourselves and Drop the database if something goes wrong.
try
{
DropSnapshot(snapshotName);
}
catch
{
//Dropping the snapshot failed. Unlucky, but not much we can do.
}
try
{
DropDatabase(databaseName);
}
catch
{
//Dropping the database failed. Unlucky, but not much we can do.
}
_initializeException = e;
throw;
}
_databaseName = databaseName;
_snapshotName = snapshotName;
return new DisposableAction(() =>
{
DropSnapshot(snapshotName);
DropDatabase(databaseName);
});
}
}
public static void RestoreFromSnapshot()
{
RestoreFromSnapshot(_databaseName, _snapshotName);
ReceiptItemSpecialArticleHelperBL.ClearSpecialArticlesCache();
}
#region Names
private static string GetDatabaseName()
{
var user = Environment.UserName;
var time = DateTime.Now.ToString("dd-MM-yyyy-HH:mm:ss");
var guid = Guid.NewGuid().ToString("N");
return $"EndToEndTests-{user}-{time}-{guid}".Truncate(120); // The MSSQL server database name max length is 128 characters, just make sure to not go too long
}
private static string GetSnapshotName(string databaseName)
{
return databaseName + "-snapshot";
}
#endregion
#region Snapshots
private static void CreateSnapshot(string snapshotName, string databaseName)
{
ExecuteSql(command =>
{
command.CommandText = GetCreateSnapshotSQL(snapshotName, databaseName);
command.ExecuteNonQuery();
});
}
private static string GetCreateSnapshotSQL(string snapshotName, string databaseName)
{
string dataPath = null;
ExecuteSql(command =>
{
command.CommandText = "SELECT serverproperty('InstanceDefaultDataPath')";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
dataPath = reader.GetString(0);
}
}
});
return $@"CREATE DATABASE [{snapshotName}] ON
(
NAME = CentronWork_Data,
FILENAME = N'{dataPath}{databaseName.Replace("-", "").Replace(":", "")}.ss'
)
AS SNAPSHOT OF [{databaseName}]";
}
private static void DropSnapshot(string snapshotName)
{
ExecuteSql(command =>
{
command.CommandText = GetDropSnapshotSQL(snapshotName);
command.ExecuteNonQuery();
});
}
private static string GetDropSnapshotSQL(string snapshotName)
{
return $"DROP DATABASE [{snapshotName}]";
}
private static void RestoreFromSnapshot(string databaseName, string snapshotName)
{
ExecuteSql(command =>
{
command.CommandText = GetRestoreFromSnapshotSQL(databaseName, snapshotName);
command.ExecuteNonQuery();
});
}
private static string GetRestoreFromSnapshotSQL(string databaseName, string snapshotName)
{
return $@"
ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
RESTORE DATABASE [{databaseName}]
FROM DATABASE_SNAPSHOT = '{snapshotName}';
ALTER DATABASE [{databaseName}] SET MULTI_USER WITH ROLLBACK IMMEDIATE; ";
}
#endregion
#region Database
private static void RestoreDatabase(string databaseName)
{
ExecuteSql(command =>
{
command.CommandText = GetRestoreDatabaseSQL(databaseName);
command.CommandTimeout = (int)TimeSpan.FromMinutes(2).TotalSeconds;
command.ExecuteNonQuery();
});
}
private static string GetRestoreDatabaseSQL(string databaseName)
{
var backupPath = GetEnvironmentVariable("DATABASE_BACKUP_PATH");
if (string.IsNullOrEmpty(backupPath))
backupPath = Path.GetFullPath(".\\Infrastructure\\DatabaseBackup.bak");
string dataPath = null;
string logPath = null;
ExecuteSql(command =>
{
command.CommandText = "SELECT serverproperty('InstanceDefaultDataPath'), serverproperty('InstanceDefaultLogPath')";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
dataPath = reader.GetString(0);
logPath = reader.GetString(1);
}
}
});
return $@"RESTORE DATABASE [{databaseName}] FROM DISK = N'{backupPath}'
WITH
REPLACE,
MOVE N'CentronWork_Data' TO N'{dataPath}{databaseName.Replace("-", "").Replace(":", "")}.mdf',
MOVE N'CentronWork_Log' TO N'{logPath}{databaseName.Replace("-", "").Replace(":", "")}.ldf'";
}
private static void DropDatabase(string databaseName)
{
ExecuteSql(command =>
{
command.CommandText = GetDropDatabaseSQL(databaseName);
command.ExecuteNonQuery();
});
}
private static string GetDropDatabaseSQL(string databaseName)
{
return $"ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{databaseName}]";
}
#endregion
#region Private Methods
private static void Connect(string databaseName)
{
DAOFactory.Instance.SetConnection(new InCodeDAOConnection(Server, databaseName, Username, Password), "c-entron Tests");
DAOFactory.Instance.InitializeAsync().Wait();
Assert.True(DAOFactory.Instance.IsInitialized, "DAOFactory.Instance.IsInitialized");
Assert.True(DAOFactory.Instance.HasConnection, "DAOFactory.Instance.HasConnection");
}
private static void UpdateDatabase()
{
using (var session = new BLSession())
{
var scriptEngineBL = session.GetBL<ScriptEngineBL>();
var version = new Version(20, 0, 0, 0);
var result = scriptEngineBL.ExecuteScripts(currentVersionOverride: version);
Assert.Null(result.Message);
Assert.Equal(ResultStatus.Success, result.Status);
}
}
private static void ExecuteSql(Action<SqlCommand> execute)
{
var builder = new SqlConnectionStringBuilder
{
DataSource = Server,
UserID = Username,
Password = Password,
InitialCatalog = "master"
};
using (var connection = new SqlConnection(builder.ToString()))
{
connection.Open();
using (var command = connection.CreateCommand())
{
execute(command);
}
}
}
private static string GetEnvironmentVariable(string name)
{
var targets = new[]
{
EnvironmentVariableTarget.Process,
EnvironmentVariableTarget.User,
EnvironmentVariableTarget.Machine
};
foreach (var target in targets)
{
var value = Environment.GetEnvironmentVariable(name, target);
if (string.IsNullOrWhiteSpace(value) == false)
return value;
}
return null;
}
#endregion
}
}