Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\AI\ContextBuilder;
return static function (): void {
// ContextBuilder ohne Konstruktor instanziieren – die getesteten Methoden
// (extractSnippets, topicAclSql) berühren die DB nicht.
$make = static function (): ContextBuilder {
return (new ReflectionClass(ContextBuilder::class))->newInstanceWithoutConstructor();
};
test('topicAclSql: null = keine Einschränkung', function () use ($make) {
$cb = $make();
setPrivate($cb, 'accessibleTopicIds', null);
assertSame('', callPrivate($cb, 'topicAclSql', ['p.topic_id']));
});
test('topicAclSql: leeres Array = nichts sichtbar', function () use ($make) {
$cb = $make();
setPrivate($cb, 'accessibleTopicIds', []);
assertSame(' AND 0=1', callPrivate($cb, 'topicAclSql', ['p.topic_id']));
});
test('topicAclSql: IN-Liste mit Integern', function () use ($make) {
$cb = $make();
setPrivate($cb, 'accessibleTopicIds', [3, 7, 9]);
assertSame(' AND p.topic_id IN (3,7,9)', callPrivate($cb, 'topicAclSql', ['p.topic_id']));
});
test('setAccessibleTopicIds castet auf int (kein SQL-Injection-Vektor)', function () use ($make) {
$cb = $make();
$cb->setAccessibleTopicIds(['3; DROP TABLE pages', '7']);
assertSame(' AND t.id IN (3,7)', callPrivate($cb, 'topicAclSql', ['t.id']));
});
test('extractSnippets: Fenster um Treffer', function () use ($make) {
$cb = $make();
$text = str_repeat('Lorem ipsum dolor sit amet. ', 40) . 'VPN Konfiguration hier. ' . str_repeat('Noch mehr Text. ', 40);
$out = callPrivate($cb, 'extractSnippets', [$text, ['vpn'], 600]);
assertTrue(stripos($out, 'VPN Konfiguration') !== false, 'Snippet enthält Treffer');
assertTrue(mb_strlen($out) <= 700, 'Budget grob eingehalten');
});
test('extractSnippets: ohne Treffer = Seitenanfang', function () use ($make) {
$cb = $make();
$text = 'Der erste Abschnitt der Seite. ' . str_repeat('x ', 500);
$out = callPrivate($cb, 'extractSnippets', [$text, ['gibtsnicht'], 300]);
assertTrue(strpos($out, 'Der erste Abschnitt') === 0, 'beginnt am Seitenanfang');
});
};