OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
104
tests/Unit/app/Core/Events/EventVocabularyTest.php
Normal file
104
tests/Unit/app/Core/Events/EventVocabularyTest.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Unit\app\Core\Events;
|
||||
|
||||
use Codeception\Test\Unit;
|
||||
use Leantime\Core\Events\Contracts\LeantimeEvent;
|
||||
use Leantime\Core\Events\Contracts\LeantimeFilter;
|
||||
use Leantime\Core\Events\EventVerb;
|
||||
|
||||
/**
|
||||
* Enforces the shared event vocabulary across all domains:
|
||||
*
|
||||
* - event classes are named {Entity}{Verb} with the verb from the central EventVerb
|
||||
* enum (TicketCreated, MilestoneDeleted — never TicketChanged/TicketEdited)
|
||||
* - filter classes are named {Thing}Filter (TodoWidgetTasksFilter)
|
||||
*
|
||||
* Scans every class in app/Domain/* /Events and app/Core/* /Events that implements
|
||||
* LeantimeEvent or LeantimeFilter. Failing this test means a synonym crept in — use an
|
||||
* existing verb or (rarely) add one to EventVerb.
|
||||
*/
|
||||
class EventVocabularyTest extends Unit
|
||||
{
|
||||
public function test_event_class_names_end_with_central_vocabulary_verb(): void
|
||||
{
|
||||
$discovered = $this->discoverEventClasses();
|
||||
|
||||
// Guard against a vacuous pass: if discovery silently finds nothing (e.g. a
|
||||
// broken base path), the loop below would assert nothing. The pilot ships ten
|
||||
// contract classes in Tickets, so discovery must find them.
|
||||
$this->assertContains(
|
||||
\Leantime\Domain\Tickets\Events\TicketUpdated::class,
|
||||
$discovered,
|
||||
'Event class discovery found nothing — the vocabulary check would pass vacuously.'
|
||||
);
|
||||
|
||||
$violations = [];
|
||||
|
||||
foreach ($discovered as $class) {
|
||||
$implements = class_implements($class);
|
||||
$shortName = substr($class, strrpos($class, '\\') + 1);
|
||||
|
||||
if (in_array(LeantimeFilter::class, $implements, true)) {
|
||||
if (! str_ends_with($shortName, 'Filter')) {
|
||||
$violations[] = "$class — filter classes must be named {Thing}Filter";
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array(LeantimeEvent::class, $implements, true)) {
|
||||
$endsWithVerb = false;
|
||||
foreach (EventVerb::cases() as $verb) {
|
||||
if (str_ends_with($shortName, $verb->name)) {
|
||||
$endsWithVerb = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $endsWithVerb) {
|
||||
$violations[] = "$class — event classes must be named {Entity}{Verb} with a verb from EventVerb";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $violations, "Event vocabulary violations:\n".implode("\n", $violations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all classes in Domain and Core Events/ folders that implement one of the
|
||||
* class-based hook contracts.
|
||||
*
|
||||
* @return array<int, class-string>
|
||||
*/
|
||||
private function discoverEventClasses(): array
|
||||
{
|
||||
// Anchor on the canonical app-root constant rather than a brittle relative
|
||||
// dirname() hop, so the scan can't silently miss the Events folders.
|
||||
$appRoot = defined('APP_ROOT') ? APP_ROOT : dirname(__DIR__, 5);
|
||||
|
||||
$files = array_merge(
|
||||
glob($appRoot.'/app/Domain/*/Events/*.php') ?: [],
|
||||
glob($appRoot.'/app/Core/*/Events/*.php') ?: [],
|
||||
);
|
||||
|
||||
$classes = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$relative = str_replace([$appRoot.'/app/', '/', '.php'], ['', '\\', ''], $file);
|
||||
$class = 'Leantime\\'.$relative;
|
||||
|
||||
if (! class_exists($class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$implements = class_implements($class) ?: [];
|
||||
if (in_array(LeantimeEvent::class, $implements, true)
|
||||
|| in_array(LeantimeFilter::class, $implements, true)) {
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user