Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions backend/app/Services/ModuleManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Support\CoreVersionConstraint;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ModuleManager
{
Expand Down Expand Up @@ -44,6 +45,24 @@ public function discover(): Collection
continue;
}

// A module lives in a directory named after itself — that is how
// installFromZip() puts it there and how loadEnabled() finds it.
// Anything else carrying a module.json is a leftover: a backup an
// updater parked next to the module, a half-finished copy, an
// unpacked archive. Those used to show up here as a second, also
// "enabled" install of the same module, because enablement matches
// on the manifest name and the copy carries the same one — two
// cards, two directories, the same provider class, and no way for
// the operator to tell which is which.
if ($manifest['name'] !== $entry) {
Log::warning('module.directory_name_mismatch', [
'directory' => $entry,
'declares' => $manifest['name'],
]);

continue;
}

$manifest['enabled'] = in_array($manifest['name'], $enabled);
$manifest['directory'] = $entry;
$manifest['has_error'] = false;
Expand Down
89 changes: 89 additions & 0 deletions backend/tests/Feature/ModuleDiscoveryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Tests\Feature;

use App\Services\ModuleManager;
use Tests\TestCase;

/**
* Tylko katalog nazwany jak moduł jest modułem.
*
* Przypadek z produkcji: updater modułu Enterprise odkładał poprzednią wersję
* obok, jako `Enterprise.poprzednia-20260930195044`. Katalog nosił komplet
* plików razem z module.json, więc discover() pokazywał go jako drugą
* instalację tego samego modułu — i jako WŁĄCZONĄ, bo włączenie dopasowuje się
* po nazwie z manifestu, a kopia ma tę samą. Administrator widział dwie karty
* „OpenMES Enterprise", obie z tym samym providerem, bez sposobu odróżnienia
* której dotyczy „Uninstall".
*/
class ModuleDiscoveryTest extends TestCase
{
private string $modulesPath;

/** @var array<int, string> */
private array $doPosprzatania = [];

protected function setUp(): void
{
parent::setUp();

$this->modulesPath = base_path('modules');
}

protected function tearDown(): void
{
foreach ($this->doPosprzatania as $katalog) {
if (is_dir($katalog)) {
@unlink("{$katalog}/module.json");
@rmdir($katalog);
}
}

parent::tearDown();
}

private function zrobKatalogModulu(string $katalog, string $nazwaWManifescie): void
{
$sciezka = "{$this->modulesPath}/{$katalog}";
@mkdir($sciezka, 0755, true);

file_put_contents("{$sciezka}/module.json", json_encode([
'name' => $nazwaWManifescie,
'display_name' => $nazwaWManifescie,
'version' => '1.0.0',
'provider' => "Modules\\{$nazwaWManifescie}\\Providers\\Provider",
]));

$this->doPosprzatania[] = $sciezka;
}

public function test_katalog_nazwany_jak_modul_jest_wykrywany(): void
{
$this->zrobKatalogModulu('DiscoveryProbe', 'DiscoveryProbe');

$nazwy = app(ModuleManager::class)->discover()->pluck('name')->all();

$this->assertContains('DiscoveryProbe', $nazwy);
}

public function test_kopia_obok_modulu_nie_jest_drugim_modulem(): void
{
$this->zrobKatalogModulu('DiscoveryProbe', 'DiscoveryProbe');
$this->zrobKatalogModulu('DiscoveryProbe.poprzednia-20260930195044', 'DiscoveryProbe');

$moduly = app(ModuleManager::class)->discover()
->where('name', 'DiscoveryProbe');

$this->assertCount(1, $moduly, 'kopia poprzedniej wersji pokazała się jako osobny moduł');
$this->assertSame('DiscoveryProbe', $moduly->first()['directory']);
}

public function test_katalog_deklarujacy_obca_nazwe_jest_pomijany(): void
{
$this->zrobKatalogModulu('DiscoveryProbe', 'ZupelnieCosInnego');

$nazwy = app(ModuleManager::class)->discover()->pluck('name')->all();

$this->assertNotContains('ZupelnieCosInnego', $nazwy);
}
}
Loading