d2d.emegrendeles.hu/tests/Feature/DeploymentPackageTest.php
E98Developer 6027511585 FIX Deployment csomagoló phase1 git ikon és a commit törzsére kiterjesztett keresés
- a topbar menüpont a most bemásolt git ikont használja; a fájl ico_gitpng néven
  érkezett, átnevezve ico_git.png-re, hogy illeszkedjen az ico_*.png konvencióhoz,
  amiből a nav az útvonalat építi
- a git log a törzset (%b) is elkéri, és a keresés a tárgysor mellett ebben is keres:
  a hash-re ritkán keres ember, az érdemi leírás viszont gyakran a törzsben van
- a --shortstat sora a törzs után érkezik, ezért a parse leválasztja róla, különben
  a "N files changed" szöveg a törzsbe és a keresésbe is beszivárogna

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:59:04 +02:00

308 lines
11 KiB
PHP

<?php
use App\Filament\Pages\DeploymentPackage;
use App\Models\FeatureFlag;
use App\Models\Role;
use App\Models\User;
use App\Services\Deployment\ChangeSetAnalyzer;
use App\Services\Deployment\DeploymentGuard;
use App\Services\Deployment\GitRepository;
use App\Services\FeatureFlagRegistrar;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
use Livewire\Livewire;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Process\Process;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
/**
* Fejlesztői gépet szimuláló környezet: az .env kapcsoló és a stage whitelist rendben.
*/
function deploymentAllowEnvironment(): void
{
Config::set('deployment.enabled', true);
Config::set('app.stage', 'local');
}
function deploymentDeveloper(bool $flagEnabled = true): User
{
$role = Role::firstOrCreate(['name' => 'developer'], ['display_name' => 'Developer']);
$user = User::factory()->create();
$user->addRole($role);
FeatureFlag::create([
'name' => 'DeploymentPackage',
'label' => 'Deployment csomagoló',
'enabled' => $flagEnabled,
'stages' => null,
'roles' => ['developer'],
]);
app(FeatureFlagRegistrar::class)->registerAll();
return $user;
}
/**
* @param array<int, string> $arguments
*/
function deploymentGit(string $path, array $arguments): string
{
$process = new Process(array_merge(['git'], $arguments), $path);
$process->mustRun();
return $process->getOutput();
}
/**
* Determinisztikus teszt-repó: átnevezés, törlés, ékezetes fájlnév és migráció is van benne.
*
* @return array{path:string, commits:array<int, string>}
*/
function deploymentTestRepository(): array
{
$path = sys_get_temp_dir().DIRECTORY_SEPARATOR.'deployment-package-test-'.uniqid();
File::makeDirectory($path.'/app', 0777, true);
File::makeDirectory($path.'/database/migrations', 0777, true);
deploymentGit($path, ['init']);
deploymentGit($path, ['config', 'user.email', 'teszt@example.com']);
deploymentGit($path, ['config', 'user.name', 'Teszt']);
deploymentGit($path, ['config', 'commit.gpgsign', 'false']);
File::put($path.'/app/Elso.php', "<?php\n// elso\n");
File::put($path.'/app/Regi.php', "<?php\n// regi\n");
File::put($path.'/app/Atnevezendo.php', "<?php\n// atnevezendo\n");
deploymentGit($path, ['add', '-A']);
deploymentGit($path, ['commit', '-m', 'első commit']);
deploymentGit($path, ['branch', '-M', 'main']);
File::put($path.'/app/Elso.php', "<?php\n// elso modositva\n");
File::put($path.'/app/Árlista.php', "<?php\n// ekezetes\n");
File::put($path.'/database/migrations/2026_01_01_000000_teszt.php', "<?php\n// migracio\n");
File::put($path.'/.env', "APP_KEY=titok\n");
deploymentGit($path, ['add', '-A', '-f']);
deploymentGit($path, ['commit', '-m', "második commit\n\nA törzsben említett árlista részlet."]);
deploymentGit($path, ['mv', 'app/Atnevezendo.php', 'app/Atnevezett.php']);
deploymentGit($path, ['rm', 'app/Regi.php']);
deploymentGit($path, ['commit', '-m', 'harmadik commit']);
$log = trim(deploymentGit($path, ['log', '--reverse', '--pretty=format:%H']));
Config::set('deployment.repo_path', $path);
return ['path' => $path, 'commits' => explode("\n", $log)];
}
function deploymentCleanup(string $path): void
{
// A .git/objects tartalma Windowson csak olvasható, ezért törlés előtt fel kell oldani.
foreach (File::allFiles($path, true) as $file) {
@chmod($file->getPathname(), 0777);
}
try {
File::deleteDirectory($path);
} catch (Throwable) {
// A takarítás nem futtathat el tesztet - a temp mappa maradványa nem befolyásol semmit.
}
}
test('a kikapcsolt env kapcsoló mindenkitől elveszi a hozzáférést', function () {
deploymentAllowEnvironment();
Config::set('deployment.enabled', false);
$developer = deploymentDeveloper();
expect(app(DeploymentGuard::class)->isAllowed($developer))->toBeFalse()
->and(app(DeploymentGuard::class)->denialReason($developer))->toContain('DEPLOYMENT_PACKAGE_ENABLED');
});
test('a nem fejlesztői stage-en akkor sem érhető el, ha a kapcsoló be van kapcsolva', function () {
deploymentAllowEnvironment();
Config::set('app.stage', 'PROD');
$developer = deploymentDeveloper();
expect(app(DeploymentGuard::class)->isAllowed($developer))->toBeFalse()
->and(app(DeploymentGuard::class)->denialReason($developer))->toContain('fejlesztői környezetben');
});
test('a developer szerep nélküli felhasználó nem éri el', function () {
deploymentAllowEnvironment();
Role::create(['name' => 'admin', 'display_name' => 'Admin']);
$admin = User::factory()->create();
$admin->addRole(Role::where('name', 'admin')->first());
FeatureFlag::create([
'name' => 'DeploymentPackage',
'label' => 'Deployment csomagoló',
'enabled' => true,
'stages' => null,
'roles' => null,
]);
app(FeatureFlagRegistrar::class)->registerAll();
expect(app(DeploymentGuard::class)->isAllowed($admin))->toBeFalse();
});
test('a kikapcsolt feature flag elrejti a felületet a fejlesztő elől is', function () {
deploymentAllowEnvironment();
$developer = deploymentDeveloper(flagEnabled: false);
expect(app(DeploymentGuard::class)->isAllowed($developer))->toBeFalse()
->and(DeploymentPackage::canAccess())->toBeFalse();
});
test('minden feltétel teljesülése esetén engedélyezett', function () {
deploymentAllowEnvironment();
$developer = deploymentDeveloper();
expect(app(DeploymentGuard::class)->isAllowed($developer))->toBeTrue()
->and(app(DeploymentGuard::class)->denialReason($developer))->toBeNull();
});
test('a jogosulatlan felhasználó 403-at kap az oldal betöltésekor', function () {
deploymentAllowEnvironment();
$developer = deploymentDeveloper(flagEnabled: false);
$this->actingAs($developer);
expect(fn () => Livewire::test(DeploymentPackage::class))->toThrow(HttpException::class);
});
test('a commitlista a legfrissebbtől visszafelé, fájlszámmal együtt jön', function () {
$repository = deploymentTestRepository();
$commits = app(GitRepository::class)->commits('main', 10);
expect($commits)->toHaveCount(3)
->and($commits[0]['subject'])->toBe('harmadik commit')
->and($commits[2]['subject'])->toBe('első commit')
->and($commits[0]['hash'])->toHaveLength(40)
->and($commits[0]['file_count'])->toBe(2)
// A törzs a tárgysor nélkül jön, és nem tapad hozzá a --shortstat sora sem.
->and($commits[1]['body'])->toBe('A törzsben említett árlista részlet.')
->and($commits[0]['body'])->toBe('');
deploymentCleanup($repository['path']);
});
test('a keresés a commit törzsében is talál, nem csak a tárgysorban', function () {
deploymentAllowEnvironment();
$repository = deploymentTestRepository();
$this->actingAs(deploymentDeveloper());
Livewire::test(DeploymentPackage::class)
->set('data.search', 'árlista részlet')
->assertSee('második commit')
->assertDontSee('harmadik commit');
deploymentCleanup($repository['path']);
});
test('a diff felismeri az új, módosult, törölt és átnevezett fájlokat', function () {
$repository = deploymentTestRepository();
[$first, , $third] = $repository['commits'];
$files = collect(app(GitRepository::class)->changedFiles($first, $third))->keyBy('path');
expect($files->get('app/Elso.php')['status'])->toBe('M')
->and($files->get('app/Regi.php')['status'])->toBe('D')
->and($files->get('app/Atnevezett.php')['status'])->toBe('R')
->and($files->get('app/Atnevezett.php')['old_path'])->toBe('app/Atnevezendo.php')
// Ékezetes útvonal: core.quotePath=false nélkül "app/\303\201rlista.php" jönne vissza.
->and($files->has('app/Árlista.php'))->toBeTrue();
deploymentCleanup($repository['path']);
});
test('az elemzés kizárja a tiltott fájlokat és külön kezeli a törlendőket', function () {
$repository = deploymentTestRepository();
[$first, , $third] = $repository['commits'];
$changedFiles = app(GitRepository::class)->changedFiles($first, $third);
$changeSet = app(ChangeSetAnalyzer::class)->analyze($changedFiles);
expect($changeSet['copied'])->toContain('app/Árlista.php', 'app/Atnevezett.php', 'app/Elso.php')
->and($changeSet['copied'])->not->toContain('.env')
->and($changeSet['counts']['excluded'])->toBe(1)
// A törölt fájl mellett az átnevezés régi útvonala is törlendő a célgépen.
->and($changeSet['deleted'])->toContain('app/Regi.php', 'app/Atnevezendo.php');
$titles = collect($changeSet['warnings'])->pluck('title')->implode(' | ');
expect($titles)->toContain('Migráció')
->and($titles)->toContain('Törlendő fájl');
deploymentCleanup($repository['path']);
});
test('az érvénytelen commit azonosítót nem adja tovább a gitnek', function () {
$repository = deploymentTestRepository();
$git = app(GitRepository::class);
expect($git->resolveCommit('../../etc/passwd'))->toBeNull()
->and($git->resolveCommit('main; rm -rf /'))->toBeNull()
->and($git->resolveCommit('HEAD'))->toBeNull()
->and($git->resolveCommit($repository['commits'][0]))->toBe($repository['commits'][0]);
deploymentCleanup($repository['path']);
});
test('a fordított tartomány felismerhető', function () {
$repository = deploymentTestRepository();
[$first, , $third] = $repository['commits'];
$git = app(GitRepository::class);
expect($git->isAncestor($first, $third))->toBeTrue()
->and($git->isAncestor($third, $first))->toBeFalse();
deploymentCleanup($repository['path']);
});
test('az oldal kilistázza a commitokat és a kijelölt tartomány fájljait', function () {
deploymentAllowEnvironment();
$repository = deploymentTestRepository();
[$first, , $third] = $repository['commits'];
$this->actingAs(deploymentDeveloper());
Livewire::test(DeploymentPackage::class)
->assertSuccessful()
->assertSee('harmadik commit')
->call('selectFrom', $first)
->call('selectTo', $third)
->assertSet('fromCommit', $first)
->assertSet('toCommit', $third)
->assertSee('app/Atnevezett.php')
->assertSee('app/Regi.php');
deploymentCleanup($repository['path']);
});
test('az oldal nem fogad el kamu commit azonosítót', function () {
deploymentAllowEnvironment();
$repository = deploymentTestRepository();
$this->actingAs(deploymentDeveloper());
Livewire::test(DeploymentPackage::class)
->call('selectFrom', '../../etc/passwd')
->assertSet('fromCommit', null);
deploymentCleanup($repository['path']);
});