M0 Check DB Testdata

This commit is contained in:
2026-07-12 00:12:02 +02:00
parent 2e6d79e896
commit d9e70ce353
19 changed files with 886 additions and 28 deletions
+191
View File
@@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
require __DIR__ . '/dev-db.php';
require __DIR__ . '/golden-master-data.php';
$pdo = dev_pdo();
$expected = gm_expected_summaries();
$members = gm_members();
$emails = array_keys($expected);
$failures = [];
$passes = 0;
function gm_assert_equal(string $label, mixed $expected, mixed $actual, array &$failures, int &$passes): void
{
if ($expected === $actual) {
$passes++;
echo "PASS {$label}\n";
return;
}
$failures[] = "{$label}: expected " . var_export($expected, true) . ', got ' . var_export($actual, true);
echo "FAIL {$label}\n";
}
function gm_fetch_member_summaries(PDO $pdo, array $emails): array
{
$placeholders = implode(',', array_fill(0, count($emails), '?'));
$year = gm_current_year();
$sql = "
SELECT
m.Email AS email,
COALESCE(p.total_payments, 0) AS total_payments,
COALESCE(c.total_costs, 0) AS total_costs,
COALESCE(c.total_marks, 0) AS total_marks,
COALESCE(p.total_payments, 0) - COALESCE(c.total_costs, 0) AS balance,
COALESCE(yp.year_payments, 0) AS year_payments,
COALESCE(yc.year_costs, 0) AS year_costs,
COALESCE(yc.year_marks, 0) AS year_marks
FROM kl_Mitarbeiter m
LEFT JOIN (
SELECT MitarbeiterID, SUM(Betrag) AS total_payments
FROM kl_Einzahlungen
GROUP BY MitarbeiterID
) p ON p.MitarbeiterID = m.MitarbeiterID
LEFT JOIN (
SELECT MitarbeiterID, SUM(Kosten) AS total_costs, SUM(AnzahlStriche) AS total_marks
FROM kl_Kaffeeverbrauch
GROUP BY MitarbeiterID
) c ON c.MitarbeiterID = m.MitarbeiterID
LEFT JOIN (
SELECT MitarbeiterID, SUM(Betrag) AS year_payments
FROM kl_Einzahlungen
WHERE YEAR(Datum) = ?
GROUP BY MitarbeiterID
) yp ON yp.MitarbeiterID = m.MitarbeiterID
LEFT JOIN (
SELECT MitarbeiterID, SUM(Kosten) AS year_costs, SUM(AnzahlStriche) AS year_marks
FROM kl_Kaffeeverbrauch
WHERE YEAR(Datum) = ?
GROUP BY MitarbeiterID
) yc ON yc.MitarbeiterID = m.MitarbeiterID
WHERE m.Email IN ({$placeholders})
";
$stmt = $pdo->prepare($sql);
$stmt->execute(array_merge([$year, $year], $emails));
$actual = [];
while ($row = $stmt->fetch()) {
$actual[$row['email']] = [
'total_payments' => gm_decimal_to_cents($row['total_payments']),
'total_costs' => gm_decimal_to_cents($row['total_costs']),
'total_marks' => (int)$row['total_marks'],
'balance' => gm_decimal_to_cents($row['balance']),
'year_payments' => gm_decimal_to_cents($row['year_payments']),
'year_costs' => gm_decimal_to_cents($row['year_costs']),
'year_marks' => (int)$row['year_marks'],
];
}
return $actual;
}
function gm_fetch_sheet_emails(PDO $pdo, array $emails, string $mode): array
{
$placeholders = implode(',', array_fill(0, count($emails), '?'));
$stmt = $pdo->prepare(
"SELECT MAX(DATE(v.Datum))
FROM kl_Kaffeeverbrauch v
JOIN kl_Mitarbeiter m ON m.MitarbeiterID = v.MitarbeiterID
WHERE m.Email IN ({$placeholders})
AND DATE(v.Datum) < CURDATE()"
);
$stmt->execute($emails);
$referenceDate = $stmt->fetchColumn() ?: date('Y-m-d');
$operator = $mode === 'front' ? '>=' : '<';
$sql = "
SELECT m.Email AS email
FROM kl_Mitarbeiter m
LEFT JOIN kl_Kaffeeverbrauch v
ON m.MitarbeiterID = v.MitarbeiterID
AND v.Datum >= DATE_ADD(?, INTERVAL -100 DAY)
WHERE m.aktiv = 1
AND m.Email IN ({$placeholders})
GROUP BY m.MitarbeiterID, m.Email
HAVING COALESCE(SUM(v.AnzahlStriche), 0) {$operator} 10
ORDER BY m.Email
";
$stmt = $pdo->prepare($sql);
$stmt->execute(array_merge([$referenceDate], $emails));
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
$actualSummaries = gm_fetch_member_summaries($pdo, $emails);
gm_assert_equal('member summary count', count($expected), count($actualSummaries), $failures, $passes);
foreach ($expected as $email => $summary) {
gm_assert_equal("summary exists {$email}", true, isset($actualSummaries[$email]), $failures, $passes);
if (!isset($actualSummaries[$email])) {
continue;
}
foreach ($summary as $key => $expectedValue) {
gm_assert_equal("{$email} {$key}", $expectedValue, $actualSummaries[$email][$key], $failures, $passes);
}
}
$stmt = $pdo->prepare('SELECT Email, aktiv, admin, paypalname FROM kl_Mitarbeiter WHERE Email IN (' . implode(',', array_fill(0, count($emails), '?')) . ')');
$stmt->execute($emails);
$memberRows = [];
while ($row = $stmt->fetch()) {
$memberRows[$row['Email']] = $row;
}
foreach ($members as $member) {
$row = $memberRows[$member['email']] ?? null;
gm_assert_equal("member flags exist {$member['email']}", true, $row !== null, $failures, $passes);
if ($row === null) {
continue;
}
gm_assert_equal("{$member['email']} active", $member['active'], (int)$row['aktiv'], $failures, $passes);
gm_assert_equal("{$member['email']} admin", $member['admin'], (int)$row['admin'], $failures, $passes);
gm_assert_equal("{$member['email']} paypalname", $member['paypalname'], $row['paypalname'], $failures, $passes);
}
$front = gm_fetch_sheet_emails($pdo, $emails, 'front');
$back = gm_fetch_sheet_emails($pdo, $emails, 'back');
gm_assert_equal('front sheet emails', gm_expected_front_sheet_emails(), $front, $failures, $passes);
gm_assert_equal('back sheet emails', gm_expected_back_sheet_emails(), $back, $failures, $passes);
$stmt = $pdo->prepare('SELECT MitarbeiterID FROM kl_Mitarbeiter WHERE Name = ? OR paypalname = ?');
$stmt->execute(['GM PayPal Alias', 'GM PayPal Alias']);
$paypalId = $stmt->fetchColumn();
gm_assert_equal('csv paypalname lookup finds participant', true, $paypalId !== false, $failures, $passes);
$stmt = $pdo->prepare(
'SELECT COUNT(*)
FROM kl_Einzahlungen
WHERE MitarbeiterID = ?
AND Betrag = ?
AND DATE(Datum) = DATE(?)'
);
$stmt->execute([$paypalId ?: 0, gm_cents_to_decimal(750), gm_dates()['recent']]);
gm_assert_equal('csv duplicate basis exists', 1, (int)$stmt->fetchColumn(), $failures, $passes);
$stmt = $pdo->prepare('SELECT COUNT(*) FROM kl_Mitarbeiter WHERE Name = ? OR paypalname = ?');
$stmt->execute(['GM Unbekannt', 'GM Unbekannt']);
gm_assert_equal('csv unknown lookup misses participant', 0, (int)$stmt->fetchColumn(), $failures, $passes);
$activeNoticeCount = (int)$pdo
->query("SELECT COUNT(*) FROM kl_hinweise WHERE nachricht LIKE '[GM]%' AND gueltig_bis >= NOW()")
->fetchColumn();
$expiredNoticeCount = (int)$pdo
->query("SELECT COUNT(*) FROM kl_hinweise WHERE nachricht LIKE '[GM]%' AND gueltig_bis < NOW()")
->fetchColumn();
gm_assert_equal('active GM notice count', 1, $activeNoticeCount, $failures, $passes);
gm_assert_equal('expired GM notice count', 1, $expiredNoticeCount, $failures, $passes);
if ($failures !== []) {
echo "\nGolden master check failed with " . count($failures) . " failure(s):\n";
foreach ($failures as $failure) {
echo "- {$failure}\n";
}
exit(1);
}
echo "\nGolden master check passed with {$passes} assertions.\n";
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
function dev_required_env(string $name): string
{
$value = getenv($name);
if ($value === false || trim($value) === '') {
fwrite(STDERR, "Missing environment variable: {$name}\n");
exit(1);
}
return $value;
}
function dev_pdo(): PDO
{
$dsn = sprintf(
'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
dev_required_env('DB_HOST'),
getenv('DB_PORT') ?: '3306',
dev_required_env('DB_NAME')
);
return new PDO($dsn, dev_required_env('DB_USER'), dev_required_env('DB_PASS'), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
]);
}
function dev_apply_schema(PDO $pdo): void
{
$schema = file_get_contents(__DIR__ . '/../database/mysql-dev-schema.sql');
if ($schema === false) {
fwrite(STDERR, "Could not read schema file.\n");
exit(1);
}
$pdo->exec($schema);
}
+269
View File
@@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
function gm_current_year(): int
{
return (int)date('Y');
}
/**
* @return array<string, string>
*/
function gm_dates(): array
{
$year = gm_current_year();
return [
'recent' => date('Y-m-d 10:00:00', strtotime('-1 day')),
'current' => sprintf('%04d-01-15 09:00:00', $year),
'previous' => sprintf('%04d-12-20 09:00:00', $year - 1),
'notice_active' => date('Y-m-d 12:00:00', strtotime('+30 days')),
'notice_expired' => date('Y-m-d 12:00:00', strtotime('-30 days')),
];
}
/**
* @return array<string, array{name: string, email: string, active: int, admin: int, paypalname: ?string}>
*/
function gm_members(): array
{
return [
'admin' => [
'name' => 'GM Admin',
'email' => 'gm-admin@test.local',
'active' => 1,
'admin' => 1,
'paypalname' => null,
],
'positive' => [
'name' => 'GM Positiv',
'email' => 'gm-positive@test.local',
'active' => 1,
'admin' => 0,
'paypalname' => null,
],
'negative' => [
'name' => 'GM Negativ',
'email' => 'gm-negative@test.local',
'active' => 1,
'admin' => 0,
'paypalname' => null,
],
'zero' => [
'name' => 'GM Nullsaldo',
'email' => 'gm-zero@test.local',
'active' => 1,
'admin' => 0,
'paypalname' => null,
],
'inactive' => [
'name' => 'GM Inaktiv',
'email' => 'gm-inactive@test.local',
'active' => 0,
'admin' => 0,
'paypalname' => null,
],
'paypal' => [
'name' => 'GM Paypal',
'email' => 'gm-paypal@test.local',
'active' => 1,
'admin' => 0,
'paypalname' => 'GM PayPal Alias',
],
'empty' => [
'name' => 'GM Ohne Buchungen',
'email' => 'gm-empty@test.local',
'active' => 1,
'admin' => 0,
'paypalname' => null,
],
'heavy' => [
'name' => 'GM Vieltrinker',
'email' => 'gm-heavy@test.local',
'active' => 1,
'admin' => 0,
'paypalname' => null,
],
];
}
/**
* @return array<string, list<array{amount_cents: int, date_key: string}>>
*/
function gm_payments(): array
{
return [
'admin' => [
['amount_cents' => 300, 'date_key' => 'current'],
],
'positive' => [
['amount_cents' => 2000, 'date_key' => 'current'],
['amount_cents' => 500, 'date_key' => 'previous'],
],
'negative' => [
['amount_cents' => 100, 'date_key' => 'current'],
],
'zero' => [
['amount_cents' => 200, 'date_key' => 'current'],
],
'inactive' => [
['amount_cents' => 1000, 'date_key' => 'current'],
],
'paypal' => [
['amount_cents' => 750, 'date_key' => 'recent'],
],
'heavy' => [
['amount_cents' => 500, 'date_key' => 'current'],
],
];
}
/**
* @return array<string, list<array{marks: int, unit_price_cents: int, date_key: string, entry_type: ?int}>>
*/
function gm_consumptions(): array
{
return [
'admin' => [
['marks' => 1, 'unit_price_cents' => 20, 'date_key' => 'recent', 'entry_type' => 2],
],
'positive' => [
['marks' => 5, 'unit_price_cents' => 20, 'date_key' => 'recent', 'entry_type' => 2],
['marks' => 2, 'unit_price_cents' => 20, 'date_key' => 'previous', 'entry_type' => null],
],
'negative' => [
['marks' => 10, 'unit_price_cents' => 20, 'date_key' => 'recent', 'entry_type' => null],
['marks' => 5, 'unit_price_cents' => 20, 'date_key' => 'previous', 'entry_type' => null],
],
'zero' => [
['marks' => 10, 'unit_price_cents' => 20, 'date_key' => 'recent', 'entry_type' => null],
],
'inactive' => [
['marks' => 20, 'unit_price_cents' => 20, 'date_key' => 'recent', 'entry_type' => null],
],
'paypal' => [
['marks' => 5, 'unit_price_cents' => 20, 'date_key' => 'recent', 'entry_type' => 2],
],
'heavy' => [
['marks' => 12, 'unit_price_cents' => 20, 'date_key' => 'recent', 'entry_type' => null],
],
];
}
/**
* @return array<string, array{total_payments: int, total_costs: int, total_marks: int, balance: int, year_payments: int, year_costs: int, year_marks: int}>
*/
function gm_expected_summaries(): array
{
return [
'gm-admin@test.local' => [
'total_payments' => 300,
'total_costs' => 20,
'total_marks' => 1,
'balance' => 280,
'year_payments' => 300,
'year_costs' => 20,
'year_marks' => 1,
],
'gm-positive@test.local' => [
'total_payments' => 2500,
'total_costs' => 140,
'total_marks' => 7,
'balance' => 2360,
'year_payments' => 2000,
'year_costs' => 100,
'year_marks' => 5,
],
'gm-negative@test.local' => [
'total_payments' => 100,
'total_costs' => 300,
'total_marks' => 15,
'balance' => -200,
'year_payments' => 100,
'year_costs' => 200,
'year_marks' => 10,
],
'gm-zero@test.local' => [
'total_payments' => 200,
'total_costs' => 200,
'total_marks' => 10,
'balance' => 0,
'year_payments' => 200,
'year_costs' => 200,
'year_marks' => 10,
],
'gm-inactive@test.local' => [
'total_payments' => 1000,
'total_costs' => 400,
'total_marks' => 20,
'balance' => 600,
'year_payments' => 1000,
'year_costs' => 400,
'year_marks' => 20,
],
'gm-paypal@test.local' => [
'total_payments' => 750,
'total_costs' => 100,
'total_marks' => 5,
'balance' => 650,
'year_payments' => 750,
'year_costs' => 100,
'year_marks' => 5,
],
'gm-empty@test.local' => [
'total_payments' => 0,
'total_costs' => 0,
'total_marks' => 0,
'balance' => 0,
'year_payments' => 0,
'year_costs' => 0,
'year_marks' => 0,
],
'gm-heavy@test.local' => [
'total_payments' => 500,
'total_costs' => 240,
'total_marks' => 12,
'balance' => 260,
'year_payments' => 500,
'year_costs' => 240,
'year_marks' => 12,
],
];
}
/**
* @return list<string>
*/
function gm_expected_front_sheet_emails(): array
{
return [
'gm-heavy@test.local',
'gm-negative@test.local',
'gm-zero@test.local',
];
}
/**
* @return list<string>
*/
function gm_expected_back_sheet_emails(): array
{
return [
'gm-admin@test.local',
'gm-empty@test.local',
'gm-paypal@test.local',
'gm-positive@test.local',
];
}
function gm_cents_to_decimal(int $cents): string
{
return number_format($cents / 100, 2, '.', '');
}
function gm_decimal_to_cents(mixed $value): int
{
return (int)round(((float)$value) * 100);
}
+129
View File
@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
require __DIR__ . '/dev-db.php';
require __DIR__ . '/golden-master-data.php';
$pdo = dev_pdo();
dev_apply_schema($pdo);
$dates = gm_dates();
$members = gm_members();
$payments = gm_payments();
$consumptions = gm_consumptions();
$emails = array_values(array_map(static fn (array $member): string => $member['email'], $members));
$placeholders = implode(',', array_fill(0, count($emails), '?'));
$pdo->beginTransaction();
try {
$step = 'select existing fixture ids';
$stmt = $pdo->prepare("SELECT MitarbeiterID FROM kl_Mitarbeiter WHERE Email IN ({$placeholders})");
$stmt->execute($emails);
$ids = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
if ($ids !== []) {
$idPlaceholders = implode(',', array_fill(0, count($ids), '?'));
$step = 'delete fixture payments';
$stmt = $pdo->prepare("DELETE FROM kl_Einzahlungen WHERE MitarbeiterID IN ({$idPlaceholders})");
$stmt->execute($ids);
$step = 'delete fixture consumption';
$stmt = $pdo->prepare("DELETE FROM kl_Kaffeeverbrauch WHERE MitarbeiterID IN ({$idPlaceholders})");
$stmt->execute($ids);
}
$step = 'delete fixture members';
$stmt = $pdo->prepare("DELETE FROM kl_Mitarbeiter WHERE Email IN ({$placeholders})");
$stmt->execute($emails);
$step = 'delete fixture notices';
$pdo->exec("DELETE FROM kl_hinweise WHERE nachricht LIKE '[GM]%'");
$step = 'delete fixture survey locks';
$pdo->exec("DELETE FROM CoffeeSurveyVotedEmails WHERE EmailNorm LIKE 'gm-%@test.local'");
$insertMember = $pdo->prepare(
'INSERT INTO kl_Mitarbeiter (Name, Email, paypalname, aktiv, admin)
VALUES (?, ?, ?, ?, ?)'
);
$selectMemberId = $pdo->prepare('SELECT MitarbeiterID FROM kl_Mitarbeiter WHERE Email = ?');
$memberIds = [];
foreach ($members as $key => $member) {
$step = 'insert member ' . $key;
$insertMember->execute([
$member['name'],
$member['email'],
$member['paypalname'],
$member['active'],
$member['admin'],
]);
$selectMemberId->execute([$member['email']]);
$memberIds[$key] = (int)$selectMemberId->fetchColumn();
}
$insertPayment = $pdo->prepare(
'INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum)
VALUES (?, ?, ?)'
);
foreach ($payments as $memberKey => $entries) {
foreach ($entries as $entry) {
$step = 'insert payment ' . $memberKey;
$insertPayment->execute([
$memberIds[$memberKey],
gm_cents_to_decimal($entry['amount_cents']),
$dates[$entry['date_key']],
]);
}
}
$insertConsumption = $pdo->prepare(
'INSERT INTO kl_Kaffeeverbrauch
(MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum, Eintragsart)
VALUES (?, ?, ?, ?, ?, ?)'
);
foreach ($consumptions as $memberKey => $entries) {
foreach ($entries as $entry) {
$step = 'insert consumption ' . $memberKey;
$costCents = $entry['marks'] * $entry['unit_price_cents'];
$insertConsumption->execute([
$memberIds[$memberKey],
$entry['marks'],
gm_cents_to_decimal($costCents),
gm_cents_to_decimal($entry['unit_price_cents']),
$dates[$entry['date_key']],
$entry['entry_type'],
]);
}
}
$step = 'insert notices';
$insertNotice = $pdo->prepare('INSERT INTO kl_hinweise (nachricht, gueltig_bis) VALUES (?, ?)');
$insertNotice->execute(['[GM] Aktiver Hinweis fuer Golden-Master-Test', $dates['notice_active']]);
$insertNotice->execute(['[GM] Abgelaufener Hinweis fuer Golden-Master-Test', $dates['notice_expired']]);
$metadata = [
'golden_master_seed' => 'seeded',
'golden_master_seed_year' => (string)gm_current_year(),
'golden_master_seed_members' => (string)count($members),
'golden_master_seed_source' => 'scripts/seed-golden-master.php',
];
$insertMeta = $pdo->prepare(
'INSERT INTO dev_baseline_metadata (meta_key, meta_value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE meta_value = VALUES(meta_value)'
);
foreach ($metadata as $key => $value) {
$step = 'insert metadata ' . $key;
$insertMeta->execute([$key, $value]);
}
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
fwrite(STDERR, "Golden master seed failed at {$step}: {$e->getMessage()}\n");
exit(1);
}
echo "Golden master fixtures seeded: " . count($members) . " members.\n";