forked from fa/breadcrumb-the-shire
First version of the security module
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Module\Security\Service;
|
||||
|
||||
use MintyPHP\Module\Security\Service\SecurityCheckProcess;
|
||||
use MintyPHP\Module\Security\Service\SecurityReportPdfService;
|
||||
use MintyPHP\Service\Branding\BrandingLogoService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SecurityReportPdfServiceTest extends TestCase
|
||||
{
|
||||
private function makeService(): SecurityReportPdfService
|
||||
{
|
||||
$branding = $this->createMock(BrandingLogoService::class);
|
||||
$branding->method('hasLogo')->willReturn(false);
|
||||
|
||||
// Null tenant-logo service: render falls back to the static brand asset.
|
||||
return new SecurityReportPdfService(new SecurityCheckProcess(), $branding, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function enrichedCheck(): array
|
||||
{
|
||||
$process = new SecurityCheckProcess();
|
||||
$processState = [];
|
||||
foreach ($process->manualStepKeys() as $key) {
|
||||
$processState[$key] = ['done' => true, 'at' => '2026-06-18 10:00:00'];
|
||||
}
|
||||
$snapshot = ['version' => 1, 'items' => [
|
||||
['type' => 'section', 'label' => 'Transport'],
|
||||
['type' => 'check', 'key' => 'tls', 'label' => 'TLS enforced'],
|
||||
['type' => 'check', 'key' => 'hsts', 'label' => 'HSTS header'],
|
||||
['type' => 'section', 'label' => 'Application'],
|
||||
['type' => 'check', 'key' => 'csrf', 'label' => 'CSRF protection'],
|
||||
]];
|
||||
$techState = [
|
||||
'tls' => ['done' => true, 'note' => 'TLS 1.3'],
|
||||
'hsts' => ['done' => true, 'note' => ''],
|
||||
'csrf' => ['done' => false, 'note' => 'pending review'],
|
||||
];
|
||||
|
||||
return [
|
||||
'debitor_name' => 'Acme GmbH',
|
||||
'debitor_no' => 'D-100',
|
||||
'domain_url' => 'acme.de',
|
||||
'domain_no' => 'DNS-1',
|
||||
'product_name' => 'Intranet',
|
||||
'product_code' => 'intranet',
|
||||
'status' => SecurityCheckProcess::STATUS_IN_PROGRESS,
|
||||
'process_state' => $processState,
|
||||
'tech_schema_snapshot' => $snapshot,
|
||||
'tech_state' => $techState,
|
||||
'progress' => $process->computeProgress($processState, $snapshot, $techState),
|
||||
];
|
||||
}
|
||||
|
||||
public function testBuildReportContextMapsCheckDataIntoRenderShape(): void
|
||||
{
|
||||
$context = $this->makeService()->buildReportContext($this->enrichedCheck());
|
||||
|
||||
$this->assertSame('Acme GmbH', $context['customer_name']);
|
||||
$this->assertSame('D-100', $context['customer_no']);
|
||||
$this->assertSame('acme.de', $context['domain']);
|
||||
$this->assertSame('Intranet', $context['product']);
|
||||
$this->assertNotSame('', $context['title']);
|
||||
|
||||
// Summary mirrors the progress math (3 tech items, 2 done).
|
||||
$this->assertSame(3, $context['summary']['tech_total']);
|
||||
$this->assertSame(2, $context['summary']['tech_done']);
|
||||
|
||||
// Five manual milestones, numbered with the technical step skipped.
|
||||
$this->assertCount(5, $context['steps']);
|
||||
$this->assertSame(1, $context['steps'][0]['number']);
|
||||
$this->assertTrue($context['steps'][0]['done']);
|
||||
$this->assertSame('2026-06-18 10:00:00', $context['steps'][0]['completed_at']);
|
||||
// 'report' is step 6 (the tech checklist step 5 is skipped from the milestone list).
|
||||
$this->assertSame(6, $context['steps'][4]['number']);
|
||||
|
||||
// Tech items grouped under their sections, carrying state + notes.
|
||||
$this->assertCount(2, $context['tech_sections']);
|
||||
$this->assertSame('Transport', $context['tech_sections'][0]['label']);
|
||||
$this->assertCount(2, $context['tech_sections'][0]['items']);
|
||||
$this->assertSame('TLS enforced', $context['tech_sections'][0]['items'][0]['label']);
|
||||
$this->assertTrue($context['tech_sections'][0]['items'][0]['done']);
|
||||
$this->assertSame('TLS 1.3', $context['tech_sections'][0]['items'][0]['note']);
|
||||
$this->assertSame('Application', $context['tech_sections'][1]['label']);
|
||||
$this->assertFalse($context['tech_sections'][1]['items'][0]['done']);
|
||||
}
|
||||
|
||||
public function testBuildReportContextHandlesEmptyCheckSafely(): void
|
||||
{
|
||||
$context = $this->makeService()->buildReportContext([]);
|
||||
|
||||
$this->assertSame('', $context['customer_name']);
|
||||
$this->assertSame('', $context['domain']);
|
||||
$this->assertSame([], $context['tech_sections']);
|
||||
$this->assertSame(0, $context['summary']['total']);
|
||||
$this->assertSame(0, $context['summary']['percent']);
|
||||
// The fixed process always yields its five manual milestones, all open.
|
||||
$this->assertCount(5, $context['steps']);
|
||||
$this->assertFalse($context['steps'][0]['done']);
|
||||
}
|
||||
|
||||
public function testBuildReportFilenameSlugifiesCustomerAndDomain(): void
|
||||
{
|
||||
$name = $this->makeService()->buildReportFilename($this->enrichedCheck());
|
||||
|
||||
$this->assertSame('security-check-report-acme-gmbh-acme-de.pdf', $name);
|
||||
}
|
||||
|
||||
public function testBuildReportFilenameFallsBackWhenNoIdentifiers(): void
|
||||
{
|
||||
$name = $this->makeService()->buildReportFilename([]);
|
||||
|
||||
$this->assertSame('security-check-report.pdf', $name);
|
||||
}
|
||||
|
||||
public function testRenderCustomerReportPdfProducesPdfBytes(): void
|
||||
{
|
||||
$pdf = $this->makeService()->renderCustomerReportPdf($this->enrichedCheck(), [
|
||||
'app_name' => 'CoreCore',
|
||||
]);
|
||||
|
||||
$this->assertNotSame('', $pdf);
|
||||
$this->assertStringStartsWith('%PDF-', $pdf);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user