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
357 lines
12 KiB
C#
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
|
|
}
|
|
} |