Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\AI\Actions\ActionRegistry;
use OpenNIT\Controllers\AIChatController;
/**
* Deckt ab, welchen Datensatz eine Chat-Aktion im Protokoll benennt.
*
* Fuehrt der KI-Chat eine Aktion aus, wird das protokolliert. Ohne den
* betroffenen Datensatz steht dort nur, *dass* etwas geschah – aus dem
* Protokoll fuehrt kein Weg zum Vertrag oder Ticket, und im Verlauf des
* Datensatzes selbst taucht die Aenderung gar nicht auf.
*/
return static function (): void {
/** Ruft die private Zuordnung auf. */
$objekt = static function (array $result): array {
$m = new ReflectionMethod(AIChatController::class, 'betroffenesObjekt');
$m->setAccessible(true);
return $m->invoke(null, $result);
};
// ----------------------------------------------------------------
// Zuordnung
// ----------------------------------------------------------------
test('Der angelegte Datensatz wird benannt', function () use ($objekt) {
assertSame(['ticket', 42], $objekt(['success' => true, 'ticket_id' => 42]));
assertSame(['contract', 7], $objekt(['success' => true, 'contract_id' => 7]));
assertSame(['asset', 3], $objekt(['success' => true, 'asset_id' => 3]));
assertSame(['page', 9], $objekt(['success' => true, 'page_id' => 9]));
});
test('Abweichende Namen treffen den Typ des Moduls', function () use ($objekt) {
// Die Tabellen heissen anders als der Schluessel im Ergebnis; das Protokoll
// muss denselben Typ fuehren wie die uebrigen Eintraege des Moduls.
assertSame(['personal_todo', 5], $objekt(['success' => true, 'todo_id' => 5]));
assertSame(['loan_lending', 5], $objekt(['success' => true, 'lending_id' => 5]));
assertSame(['kanban_task', 5], $objekt(['success' => true, 'task_id' => 5]));
assertSame(['stock_item', 5], $objekt(['success' => true, 'item_id' => 5]));
assertSame(['ip_address', 5], $objekt(['success' => true, 'ip_id' => 5]));
assertSame(['maintenance_event', 5], $objekt(['success' => true, 'event_id' => 5]));
});
test('Die Chat-Sitzung ist nicht der betroffene Datensatz', function () use ($objekt) {
// Sonst zeigte jeder Eintrag auf den Chat statt auf das, was sich geaendert hat.
assertSame([null, null], $objekt(['success' => true, 'session_id' => 12]));
assertSame(['ticket', 8], $objekt(['success' => true, 'session_id' => 12, 'ticket_id' => 8]));
});
test('Ohne verwertbare Kennung bleibt es beim Chat', function () use ($objekt) {
assertSame([null, null], $objekt(['success' => true, 'message' => 'erledigt']));
assertSame([null, null], $objekt(['success' => true, 'ticket_id' => 0]));
assertSame([null, null], $objekt(['success' => true, 'ticket_id' => null]));
assertSame([null, null], $objekt([]));
});
test('Nur Kennungen zaehlen, nicht jeder Wert', function () use ($objekt) {
// 'redirect' oder 'message' duerfen nicht als Datensatz durchgehen.
assertSame([null, null], $objekt(['success' => true, 'redirect' => '/tickets/5']));
});
// ----------------------------------------------------------------
// Zusammenspiel mit dem Protokolleintrag
// ----------------------------------------------------------------
test('Jede bekannte Aktion traegt eine lesbare Bezeichnung', function () {
// Der Eintrag fuehrt die Bezeichnung als Objektnamen; ein roher
// Schluessel wie 'create_contract' liest sich im Protokoll schlecht.
foreach (array_keys(ActionRegistry::all()) as $typ) {
$label = ActionRegistry::label($typ);
assertTrue($label !== '' && $label !== $typ, "Aktion {$typ} ohne eigene Bezeichnung");
}
});
test('Der Protokolleintrag benennt den Datensatz statt pauschal den Chat', function () {
$quelle = (string)file_get_contents(dirname(__DIR__) . '/src/Controllers/AIChatController.php');
$stelle = strpos($quelle, "'ai_action_executed'");
assertTrue($stelle !== false, 'Der Protokolleintrag der Aktionen fehlt');
$block = substr($quelle, $stelle, 400);
assertTrue(str_contains($block, "\$objektTyp ?? 'ai_chat'"), 'Der Eintrag traegt weiterhin pauschal den Chat');
assertTrue(str_contains($block, '$objektId'), 'Der Eintrag traegt keine Kennung');
});
};