Files
breadcrumb-the-shire/modules/security/tests/Module/Security/Service/SecurityCheckProcessTest.php

236 lines
9.0 KiB
PHP

<?php
namespace MintyPHP\Tests\Module\Security\Service;
use MintyPHP\Module\Security\Service\SecurityCheckProcess;
use PHPUnit\Framework\TestCase;
class SecurityCheckProcessTest extends TestCase
{
public function testStepsContainSixOrderedSteps(): void
{
$steps = (new SecurityCheckProcess())->steps();
$this->assertCount(6, $steps);
$this->assertSame('beauftragung', $steps[0]['key']);
$this->assertSame(SecurityCheckProcess::STEP_PERFORM_CHECK, $steps[4]['key']);
$this->assertSame('report', $steps[5]['key']);
}
public function testManualStepKeysExcludeTechChecklistStep(): void
{
$keys = (new SecurityCheckProcess())->manualStepKeys();
$this->assertCount(5, $keys);
$this->assertNotContains(SecurityCheckProcess::STEP_PERFORM_CHECK, $keys);
$this->assertContains(SecurityCheckProcess::STEP_INFORMATION, $keys);
}
public function testInfoStepFieldsAreDefined(): void
{
$fields = (new SecurityCheckProcess())->stepFields(SecurityCheckProcess::STEP_INFORMATION);
$this->assertNotEmpty($fields);
$keys = array_column($fields, 'key');
$this->assertContains('technical_contact', $keys);
$this->assertContains('external_systems', $keys);
}
public function testReportStepRequiresOfficialCustomerReportField(): void
{
$process = new SecurityCheckProcess();
$fields = $process->stepFields(SecurityCheckProcess::STEP_REPORT);
$this->assertSame(
[SecurityCheckProcess::FIELD_CUSTOMER_REPORT],
array_column($fields, 'key')
);
// The owner must fill it before the final step can be completed.
$this->assertContains(
SecurityCheckProcess::FIELD_CUSTOMER_REPORT,
$process->requiredFieldKeys(SecurityCheckProcess::STEP_REPORT)
);
}
public function testRequiredFieldKeysExcludeOptionalFields(): void
{
$keys = (new SecurityCheckProcess())->requiredFieldKeys(SecurityCheckProcess::STEP_INFORMATION);
$this->assertContains('technical_contact', $keys);
$this->assertContains('deployment', $keys);
$this->assertContains('system_info', $keys);
$this->assertContains('external_systems', $keys);
$this->assertNotContains('special_notes', $keys);
}
public function testSchedulingAndBlockerStepsRequireTwoDatetimeFields(): void
{
$process = new SecurityCheckProcess();
$expected = [
'scheduling' => ['golive_start', 'golive_end'],
'blocker_appointment' => ['blocker_start', 'blocker_end'],
];
foreach ($expected as $stepKey => $expectedKeys) {
$fields = $process->stepFields($stepKey);
$this->assertCount(2, $fields);
foreach ($fields as $field) {
$this->assertSame(SecurityCheckProcess::FIELD_DATETIME, $field['type']);
$this->assertTrue($field['required']);
}
$this->assertSame($expectedKeys, $process->requiredFieldKeys($stepKey));
}
}
public function testTechCheckItemsExtractsOnlyCheckItemsWithKeys(): void
{
$snapshot = ['items' => [
['type' => 'section', 'label' => 'Group'],
['type' => 'check', 'key' => 'tls', 'label' => 'TLS'],
['type' => 'check', 'key' => '', 'label' => 'No key'],
['type' => 'check', 'label' => 'Missing key'],
'not-an-array',
]];
$items = SecurityCheckProcess::techCheckItems($snapshot);
$this->assertCount(1, $items);
$this->assertSame('tls', $items[0]['key']);
}
public function testComputeProgressOpenWhenNothingDone(): void
{
$progress = (new SecurityCheckProcess())->computeProgress([], [], []);
$this->assertSame(5, $progress['manual_total']);
$this->assertSame(0, $progress['tech_total']);
$this->assertSame(0, $progress['done']);
$this->assertSame(0, $progress['percent']);
$this->assertSame(SecurityCheckProcess::STATUS_OPEN, $progress['status']);
$this->assertTrue($progress['step6_done']); // no tech items → trivially done
}
public function testComputeProgressInProgressWhenPartiallyDone(): void
{
$processState = ['beauftragung' => ['done' => true]];
$progress = (new SecurityCheckProcess())->computeProgress($processState, [], []);
$this->assertSame(1, $progress['done']);
$this->assertSame(SecurityCheckProcess::STATUS_IN_PROGRESS, $progress['status']);
}
public function testComputeProgressCompletedWhenAllDone(): void
{
$process = new SecurityCheckProcess();
$snapshot = ['items' => [
['type' => 'check', 'key' => 'a', 'label' => 'A'],
['type' => 'check', 'key' => 'b', 'label' => 'B'],
]];
$processState = [];
foreach ($process->manualStepKeys() as $key) {
$processState[$key] = ['done' => true];
}
$techState = ['a' => ['done' => true], 'b' => ['done' => true]];
$progress = $process->computeProgress($processState, $snapshot, $techState);
$this->assertSame(7, $progress['total']); // 5 manual + 2 tech
$this->assertSame(7, $progress['done']);
$this->assertSame(100, $progress['percent']);
$this->assertTrue($progress['step6_done']);
$this->assertSame(SecurityCheckProcess::STATUS_COMPLETED, $progress['status']);
}
public function testComputeProgressStep6NotDoneWhenSomeTechItemsOpen(): void
{
$snapshot = ['items' => [
['type' => 'check', 'key' => 'a', 'label' => 'A'],
['type' => 'check', 'key' => 'b', 'label' => 'B'],
]];
$techState = ['a' => ['done' => true]];
$progress = (new SecurityCheckProcess())->computeProgress([], $snapshot, $techState);
$this->assertFalse($progress['step6_done']);
$this->assertSame(SecurityCheckProcess::STATUS_IN_PROGRESS, $progress['status']);
}
public function testComputeProgressArchivedOverridesDerivedStatus(): void
{
$progress = (new SecurityCheckProcess())->computeProgress([], [], [], true);
$this->assertSame(SecurityCheckProcess::STATUS_ARCHIVED, $progress['status']);
}
public function testReportPrerequisitesMetWhenAllPrecedingStepsComplete(): void
{
$process = new SecurityCheckProcess();
$snapshot = ['items' => [['type' => 'check', 'key' => 'a', 'label' => 'A']]];
$processState = [];
foreach ($process->manualStepKeys() as $key) {
// The report step itself is intentionally left open — it does not gate itself.
$processState[$key] = ['done' => $key !== SecurityCheckProcess::STEP_REPORT];
}
$techState = ['a' => ['done' => true]];
$this->assertTrue($process->reportPrerequisitesMet($processState, $snapshot, $techState));
}
public function testReportPrerequisitesNotMetWhenAManualStepIsOpen(): void
{
$process = new SecurityCheckProcess();
$snapshot = ['items' => [['type' => 'check', 'key' => 'a', 'label' => 'A']]];
$processState = [];
foreach ($process->manualStepKeys() as $key) {
$processState[$key] = ['done' => true];
}
$processState['scheduling']['done'] = false; // one preceding step still open
$techState = ['a' => ['done' => true]];
$this->assertFalse($process->reportPrerequisitesMet($processState, $snapshot, $techState));
}
public function testReportPrerequisitesNotMetWhenTechChecklistIncomplete(): void
{
$process = new SecurityCheckProcess();
$snapshot = ['items' => [
['type' => 'check', 'key' => 'a', 'label' => 'A'],
['type' => 'check', 'key' => 'b', 'label' => 'B'],
]];
$processState = [];
foreach ($process->manualStepKeys() as $key) {
$processState[$key] = ['done' => true];
}
$techState = ['a' => ['done' => true]]; // 'b' still open
$this->assertFalse($process->reportPrerequisitesMet($processState, $snapshot, $techState));
}
public function testReportPrerequisitesMetWithEmptyTechChecklist(): void
{
$process = new SecurityCheckProcess();
$processState = [];
foreach ($process->manualStepKeys() as $key) {
$processState[$key] = ['done' => true];
}
// No tech items → checklist trivially complete; only the manual steps gate.
$this->assertTrue($process->reportPrerequisitesMet($processState, [], []));
}
public function testStatusVariantMapping(): void
{
$this->assertSame('neutral', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_OPEN));
$this->assertSame('warning', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_IN_PROGRESS));
$this->assertSame('success', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_COMPLETED));
$this->assertSame('neutral', SecurityCheckProcess::statusVariant(SecurityCheckProcess::STATUS_ARCHIVED));
}
}