Files
Masterarbeit/QuellCode/CentronERP/tests/Centron.Tests.EndToEnd/Tests/MasterDataList/MainDeviceSerialNumberTests.cs
T
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

187 lines
9.4 KiB
C#

using System;
using Centron.BusinessLogic;
using Centron.BusinessLogic.WebServices.Sales.Receipts.MasterDataLists;
using Centron.Tests.EndToEnd.Infrastructure;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.MasterDataList
{
/// <summary>
/// Regression tests for ticket 163981 — removing or replacing the main device's
/// serial number on a Stammblatt and propagating the change to its click counters.
/// </summary>
public class MainDeviceSerialNumberTests : EndToEndTest
{
public MainDeviceSerialNumberTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
public override void Execute()
{
this.RemoveMainDeviceSerialNumber_NoCounters();
this.RemoveMainDeviceSerialNumber_WithCounters_Fails();
this.SwapMainDeviceSerialNumber_WithCounters_UpdatesAll();
}
private void RemoveMainDeviceSerialNumber_NoCounters()
{
// Find a Stammblatt with a serial number but no active click counters.
// Use the MasterDataList view so the test sees the same SerialNumberI3D the BL does
// (the view derives it from Barcode.Status = 9, not from GeraeteKopf.SeriennummerI3D).
var masterDataListI3D = this.Sql<int>(@"
SELECT TOP 1 mdl.I3D
FROM MasterDataList mdl
WHERE mdl.SerialNumberI3D > 0
AND NOT EXISTS (
SELECT 1 FROM GeraeteClickZaehler gz
WHERE gz.GeraeteKopfI3D = mdl.I3D AND gz.SeriennummerI3D = mdl.SerialNumberI3D AND gz.Status = 1
)
ORDER BY mdl.I3D");
if (masterDataListI3D <= 0)
throw new InvalidOperationException("Precondition failed: no master data list with a serial number and without active click counters found.");
var oldSerialNumber = this.Sql<string>($"SELECT SerialNumber FROM MasterDataList WHERE I3D = {masterDataListI3D}");
var historyCutoffI3D = this.GetCurrentMaxHistoryI3D();
using var bl = new BLSession();
var loggedInUser = this.GetLoggedInUser();
var result = bl.GetBL<MasterDataListWebServiceBL>().RemoveMainDeviceSerialNumber(masterDataListI3D, loggedInUser);
this.Verifier.Verify("RemoveMainDeviceSerialNumber_NoCounters_Result", result);
this.Verifier.VerifySql(
"RemoveMainDeviceSerialNumber_NoCounters_Db",
$"SELECT I3D, SeriennummerI3D, Seriennummer FROM GeraeteKopf WHERE I3D = {masterDataListI3D}");
this.Verifier.VerifySql(
"RemoveMainDeviceSerialNumber_NoCounters_History",
BuildSerialNumberHistoryQuery(
masterDataListI3D,
historyCutoffI3D,
loggedInUser.User.Employee.I3D,
$"Seriennummer vom Hauptgerät \"{oldSerialNumber}\" wurde entfernt"));
}
private void RemoveMainDeviceSerialNumber_WithCounters_Fails()
{
// Find a Stammblatt with a serial number that still has active counters referencing it.
var masterDataListI3D = this.Sql<int>(@"
SELECT TOP 1 mdl.I3D
FROM MasterDataList mdl
INNER JOIN GeraeteClickZaehler gz ON gz.GeraeteKopfI3D = mdl.I3D AND gz.SeriennummerI3D = mdl.SerialNumberI3D AND gz.Status = 1
WHERE mdl.SerialNumberI3D > 0
ORDER BY mdl.I3D");
if (masterDataListI3D <= 0)
throw new InvalidOperationException("Precondition failed: no master data list with a serial number and active click counters found.");
using var bl = new BLSession();
var loggedInUser = this.GetLoggedInUser();
var result = bl.GetBL<MasterDataListWebServiceBL>().RemoveMainDeviceSerialNumber(masterDataListI3D, loggedInUser);
this.Verifier.Verify("RemoveMainDeviceSerialNumber_WithCounters_Fails_Result", new
{
result.Status,
result.Message
});
}
private void SwapMainDeviceSerialNumber_WithCounters_UpdatesAll()
{
// Find a Stammblatt with serial + active counters AND for which a different in-stock barcode
// (state = 1) for the same article exists — both must hold for the swap to be exercisable.
var masterDataListI3D = this.Sql<int>(@"
SELECT TOP 1 mdl.I3D
FROM MasterDataList mdl
INNER JOIN MasterDataListItems mi ON mi.HeadI3D = mdl.I3D AND mi.IsMainItem = 1
INNER JOIN GeraeteClickZaehler gz ON gz.GeraeteKopfI3D = mdl.I3D AND gz.SeriennummerI3D = mdl.SerialNumberI3D AND gz.Status = 1
WHERE mdl.SerialNumberI3D > 0
AND EXISTS (
SELECT 1 FROM Barcode b
WHERE b.ArtikelI3D = mi.ArticleI3D
AND b.Status = 1
AND b.I3D <> mdl.SerialNumberI3D
)
ORDER BY mdl.I3D");
if (masterDataListI3D <= 0)
throw new InvalidOperationException("Precondition failed: no master data list with a serial number, active click counters, and an alternative in-stock barcode for the main item's article found.");
// Pick the first available in-stock barcode (state = 1) that matches the main item's article.
var newSerialNumberI3D = this.Sql<int>($@"
SELECT TOP 1 b.I3D
FROM Barcode b
INNER JOIN MasterDataList mdl ON mdl.I3D = {masterDataListI3D}
INNER JOIN MasterDataListItems mi ON mi.HeadI3D = mdl.I3D AND mi.IsMainItem = 1
WHERE b.ArtikelI3D = mi.ArticleI3D
AND b.Status = 1
AND b.I3D <> mdl.SerialNumberI3D
ORDER BY b.I3D");
if (newSerialNumberI3D <= 0)
throw new InvalidOperationException("Precondition failed: no alternative in-stock barcode found for the main item's article.");
var oldSerialNumber = this.Sql<string>($"SELECT SerialNumber FROM MasterDataList WHERE I3D = {masterDataListI3D}");
var newSerialNumber = this.Sql<string>($"SELECT Barcode FROM Barcode WHERE I3D = {newSerialNumberI3D}");
var historyCutoffI3D = this.GetCurrentMaxHistoryI3D();
using var bl = new BLSession();
var loggedInUser = this.GetLoggedInUser();
var result = bl.GetBL<MasterDataListWebServiceBL>().SwapMainDeviceSerialNumber(masterDataListI3D, newSerialNumberI3D, loggedInUser);
this.Verifier.Verify("SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_Result", new
{
result.Status,
result.Message
});
this.Verifier.VerifySql(
"SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_DbHead",
$"SELECT I3D, SeriennummerI3D, Seriennummer FROM GeraeteKopf WHERE I3D = {masterDataListI3D}");
this.Verifier.VerifySql(
"SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_DbCounters",
$"SELECT I3D, GeraeteKopfI3D, SeriennummerI3D FROM GeraeteClickZaehler WHERE GeraeteKopfI3D = {masterDataListI3D} ORDER BY I3D");
this.Verifier.VerifySql(
"SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_History",
BuildSerialNumberHistoryQuery(
masterDataListI3D,
historyCutoffI3D,
loggedInUser.User.Employee.I3D,
$"Seriennummer vom Hauptgerät wurde getauscht von \"{oldSerialNumber}\" auf \"{newSerialNumber}\""));
}
/// <summary>
/// Highest AnlageLog I3D before the operation under test runs, used as a cutoff so the history
/// assertion only sees rows this test actually produced and not leftovers from earlier runs.
/// </summary>
private int GetCurrentMaxHistoryI3D()
{
return this.Sql<int>("SELECT ISNULL(MAX(I3D), 0) FROM AnlageLog");
}
/// <summary>
/// Reads the Stammblatt history entries (AnlageLog) that the operation under test wrote.
/// The cutoff limits the result to rows this run produced, so leftovers from earlier runs cannot
/// satisfy the check, and an unexpected additional entry shows up as an extra row in the snapshot.
/// MatchesExpected covers the exact description including old and new serial number.
/// Employee and date are compared as flags because their raw values differ per run.
/// </summary>
private static string BuildSerialNumberHistoryQuery(int masterDataListI3D, int historyCutoffI3D, int expectedEmployeeI3D, string expectedDescription)
{
var expectedLiteral = expectedDescription.Replace("'", "''");
return $@"
SELECT Beschreibung,
CASE WHEN Beschreibung = N'{expectedLiteral}' THEN 1 ELSE 0 END AS MatchesExpected,
CASE WHEN PersonalI3D = {expectedEmployeeI3D} THEN 1 ELSE 0 END AS IsExpectedEmployee,
CASE WHEN Datum IS NOT NULL THEN 1 ELSE 0 END AS HasDate
FROM AnlageLog
WHERE AnlageI3D = {masterDataListI3D}
AND I3D > {historyCutoffI3D}
ORDER BY I3D";
}
}
}