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
187 lines
9.4 KiB
C#
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";
|
|
}
|
|
}
|
|
}
|