First version of the security module
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
<?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 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user