initialization
This commit is contained in:
commit
9ff09693ed
92 files changed
+46209
No files matched your search
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Generators\HighEntropyGenerator;
|
||||
use App\Generators\PasswordGeneratorInterface;
|
||||
use App\Generators\PhraseAbbreviatorGenerator;
|
||||
use App\Generators\PwgenGenerator;
|
||||
use App\Managers\PasswordGeneratorManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class GeneratorValidationTest extends TestCase
|
||||
{
|
||||
public function testDefaultGenerationWorksAcrossSupportedModes(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
|
||||
foreach ([
|
||||
'high-entropy',
|
||||
'xkcdpass',
|
||||
'diceware',
|
||||
'pwgen',
|
||||
'passkey',
|
||||
'phrase-abbreviator',
|
||||
] as $type) {
|
||||
$defaults = $manager->getDefaultOptions($type);
|
||||
$result = $manager->generate($type, 1, $defaults);
|
||||
|
||||
$this->assertArrayHasKey('passwords', $result);
|
||||
$this->assertNotEmpty($result['passwords']);
|
||||
$this->assertNotSame('', trim((string) $result['passwords'][0]));
|
||||
}
|
||||
}
|
||||
|
||||
public function testHighEntropyRejectsAllFalseCharacterSets(): void
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
|
||||
(new HighEntropyGenerator())->generate(1, [
|
||||
'length' => 16,
|
||||
'include_lowercase' => false,
|
||||
'include_uppercase' => false,
|
||||
'include_numbers' => false,
|
||||
'include_symbols' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testPhraseAbbreviatorUsesFallbackSentenceWhenMissing(): void
|
||||
{
|
||||
$generator = new PhraseAbbreviatorGenerator();
|
||||
|
||||
$validated = $generator->validateOptions(['sentence' => '']);
|
||||
|
||||
$this->assertNotSame('', $validated['sentence']);
|
||||
$this->assertSame('The quick brown fox jumps over the lazy dog.', $validated['sentence']);
|
||||
}
|
||||
|
||||
public function testGeneratorDefinitionsMatchFrontendMetadataContract(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
$definitions = $manager->getGeneratorDefinitions();
|
||||
|
||||
$this->assertNotEmpty($definitions);
|
||||
$this->assertArrayHasKey('high-entropy', $definitions);
|
||||
|
||||
$highEntropy = $definitions['high-entropy'];
|
||||
$this->assertSame('high-entropy_title', $highEntropy['nameKey']);
|
||||
$this->assertSame('high-entropy_description', $highEntropy['descriptionKey']);
|
||||
$this->assertNotEmpty($highEntropy['options']);
|
||||
$this->assertSame('high-entropy_option_length', $highEntropy['options'][0]['labelKey']);
|
||||
}
|
||||
|
||||
public function testGeneratedPasswordsExposeSecurityAssessmentAndEntropyDetails(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
$result = $manager->generate('diceware', 1, [
|
||||
'words' => 6,
|
||||
'show_dice_rolls' => true,
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('passwords', $result);
|
||||
$this->assertArrayHasKey('security_assessment', $result);
|
||||
$this->assertNotSame('', trim((string) $result['security_assessment']));
|
||||
$this->assertArrayHasKey('raw_technical_details', $result);
|
||||
$this->assertArrayHasKey('entropy_bits', $result['raw_technical_details']);
|
||||
$this->assertGreaterThan(0, $result['raw_technical_details']['entropy_bits']);
|
||||
}
|
||||
|
||||
public function testPwgenGeneratorDoesNotExposeRemovedTypePresetOption(): void
|
||||
{
|
||||
$generator = new PwgenGenerator();
|
||||
$options = $generator->getOptionDefinitions();
|
||||
|
||||
$this->assertArrayNotHasKey('type', $options);
|
||||
$this->assertSame('custom', $generator->validateOptions([])['type'] ?? 'custom');
|
||||
}
|
||||
|
||||
public function testXkcdEntropyReflectsWordLengthFiltering(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
|
||||
$unfiltered = $manager->generate('xkcdpass', 1, [
|
||||
'words' => 4,
|
||||
'wordlist' => 'en',
|
||||
'separator' => '-',
|
||||
'max_word_length' => 0,
|
||||
'space_handling' => 'allow',
|
||||
'add_number' => false,
|
||||
]);
|
||||
|
||||
$shortFiltered = $manager->generate('xkcdpass', 1, [
|
||||
'words' => 4,
|
||||
'wordlist' => 'en',
|
||||
'separator' => '-',
|
||||
'max_word_length' => 4,
|
||||
'space_handling' => 'allow',
|
||||
'add_number' => false,
|
||||
]);
|
||||
|
||||
$this->assertGreaterThan($shortFiltered['entropy_bits'], $unfiltered['entropy_bits']);
|
||||
}
|
||||
|
||||
public function testXkcdResponseIncludesEntropyBreakdownDetails(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
$result = $manager->generate('xkcdpass', 1, [
|
||||
'words' => 4,
|
||||
'wordlist' => 'en',
|
||||
'separator' => '-_',
|
||||
'max_word_length' => 6,
|
||||
'space_handling' => 'skip',
|
||||
'add_number' => true,
|
||||
'number_position' => 'random',
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('raw_technical_details', $result);
|
||||
$this->assertArrayHasKey('entropy_breakdown', $result['raw_technical_details']);
|
||||
|
||||
$breakdown = $result['raw_technical_details']['entropy_breakdown'];
|
||||
$this->assertSame('xkcd-candidate-space-v1', $breakdown['entropy_model']);
|
||||
$this->assertArrayHasKey('effective_wordlist_candidates', $breakdown);
|
||||
$this->assertGreaterThan(0, $breakdown['effective_wordlist_candidates']);
|
||||
$this->assertArrayHasKey('separator_entropy_bits', $breakdown);
|
||||
$this->assertArrayHasKey('number_entropy_bits', $breakdown);
|
||||
}
|
||||
|
||||
public function testGeneratorsExposeEntropyModelConfidenceInBreakdown(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
|
||||
$cases = [
|
||||
'diceware' => ['words' => 6],
|
||||
'passkey' => ['syllables' => 6],
|
||||
'pwgen' => ['length' => 12, 'separator' => '-_', 'include_numbers' => true, 'include_capitals' => true],
|
||||
];
|
||||
|
||||
foreach ($cases as $type => $options) {
|
||||
$result = $manager->generate($type, 1, $options);
|
||||
|
||||
$this->assertArrayHasKey('raw_technical_details', $result);
|
||||
$this->assertArrayHasKey('entropy_breakdown', $result['raw_technical_details']);
|
||||
$this->assertArrayHasKey('entropy_model', $result['raw_technical_details']['entropy_breakdown']);
|
||||
$this->assertArrayHasKey('model_confidence', $result['raw_technical_details']['entropy_breakdown']);
|
||||
$this->assertContains(
|
||||
$result['raw_technical_details']['entropy_breakdown']['model_confidence'],
|
||||
['high', 'medium', 'low']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testPwgenAndXkcdCurrentOptionsProduceExpectedBehavior(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
|
||||
$pwgen16 = (string) $manager->generate('pwgen', 1, ['length' => 16])['passwords'][0];
|
||||
$pwgen12 = (string) $manager->generate('pwgen', 1, ['length' => 12])['passwords'][0];
|
||||
$pwgen8 = (string) $manager->generate('pwgen', 1, ['length' => 8])['passwords'][0];
|
||||
|
||||
$this->assertSame(16, strlen($pwgen16));
|
||||
$this->assertSame(12, strlen($pwgen12));
|
||||
$this->assertSame(8, strlen($pwgen8));
|
||||
|
||||
$xkcdGerman = (string) $manager->generate('xkcdpass', 1, [
|
||||
'wordlist' => 'de',
|
||||
'words' => 4,
|
||||
'separator' => '-',
|
||||
'add_number' => true,
|
||||
'number_position' => 'end',
|
||||
])['passwords'][0];
|
||||
|
||||
$xkcdEnglish = (string) $manager->generate('xkcdpass', 1, [
|
||||
'wordlist' => 'en',
|
||||
'words' => 4,
|
||||
'separator' => '-',
|
||||
'add_number' => true,
|
||||
'number_position' => 'end',
|
||||
])['passwords'][0];
|
||||
|
||||
$xkcdSlaengikon = (string) $manager->generate('xkcdpass', 1, [
|
||||
'wordlist' => 'ch',
|
||||
'words' => 4,
|
||||
'separator' => '_',
|
||||
'add_number' => true,
|
||||
'number_position' => 'end',
|
||||
])['passwords'][0];
|
||||
|
||||
$this->assertSame(1, preg_match('/\d/', $xkcdGerman));
|
||||
$this->assertSame(1, preg_match('/\d/', $xkcdEnglish));
|
||||
$this->assertSame(1, preg_match('/\d/', $xkcdSlaengikon));
|
||||
}
|
||||
|
||||
public function testDicewareUsesLargeEffectiveCandidateSpace(): void
|
||||
{
|
||||
$manager = new PasswordGeneratorManager();
|
||||
$result = $manager->generate('diceware', 1, [
|
||||
'words' => 6,
|
||||
'show_dice_rolls' => true,
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('raw_technical_details', $result);
|
||||
$this->assertArrayHasKey('entropy_breakdown', $result['raw_technical_details']);
|
||||
$this->assertArrayHasKey('wordlist_size', $result['raw_technical_details']['entropy_breakdown']);
|
||||
$this->assertGreaterThanOrEqual(7000, $result['raw_technical_details']['entropy_breakdown']['wordlist_size']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Api\OpenApiGenerator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class OpenApiGeneratorTest extends TestCase
|
||||
{
|
||||
public function testGenerateIncludesPublicApiRoutes(): void
|
||||
{
|
||||
$spec = OpenApiGenerator::generate('http://localhost:8080');
|
||||
|
||||
$this->assertSame('3.0.3', $spec['openapi']);
|
||||
$this->assertArrayHasKey('/api/v1/passwords', $spec['paths']);
|
||||
$this->assertArrayHasKey('/api/v1/passwords/generate', $spec['paths']);
|
||||
$this->assertArrayHasKey('/api/v1/health', $spec['paths']);
|
||||
$this->assertArrayHasKey('/api/v1/locales/{lang}', $spec['paths']);
|
||||
$this->assertSame('http://localhost:8080', $spec['servers'][0]['url']);
|
||||
|
||||
$this->assertSame('boolean', $spec['paths']['/api/v1/passwords']['get']['responses']['200']['content']['application/json']['schema']['properties']['success']['type']);
|
||||
$this->assertArrayHasKey('types', $spec['paths']['/api/v1/passwords']['get']['responses']['200']['content']['application/json']['schema']['properties']['data']['properties']);
|
||||
$this->assertSame('#/components/schemas/PasswordGenerationSuccess', $spec['paths']['/api/v1/passwords/generate']['post']['responses']['200']['content']['application/json']['schema']['$ref']);
|
||||
|
||||
$passwordResponseData = $spec['components']['schemas']['PasswordGenerationSuccess']['properties']['data']['properties'];
|
||||
$this->assertArrayHasKey('security_assessment', $passwordResponseData);
|
||||
$this->assertArrayHasKey('raw_technical_details', $passwordResponseData);
|
||||
$this->assertArrayHasKey('entropy_breakdown', $passwordResponseData['raw_technical_details']['properties']);
|
||||
|
||||
$localeEnum = $spec['paths']['/api/v1/locales/{lang}']['get']['parameters'][0]['schema']['enum'];
|
||||
$this->assertContains('ja', $localeEnum);
|
||||
$this->assertContains('de_CH', $localeEnum);
|
||||
$this->assertNotContains('js', $localeEnum);
|
||||
$this->assertNotContains('ch', $localeEnum);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Utils\SecurityConfig;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ProductionSecurityConfigTest extends TestCase
|
||||
{
|
||||
public function testDevelopmentDefaultsKeepLocalhostCorsAndDebugLogging(): void
|
||||
{
|
||||
$this->assertSame('http://localhost:8080', SecurityConfig::resolveCorsOrigin('development'));
|
||||
$this->assertSame(100, SecurityConfig::resolveRateLimit('development', 100));
|
||||
$this->assertSame(100, SecurityConfig::resolveLogLevel('debug'));
|
||||
}
|
||||
|
||||
public function testProductionDefaultsRejectWildcardOriginsAndUseRestrictedLogging(): void
|
||||
{
|
||||
$this->assertSame('', SecurityConfig::resolveCorsOrigin('production'));
|
||||
$this->assertSame(120, SecurityConfig::resolveRateLimit('production', 120));
|
||||
$this->assertSame(300, SecurityConfig::resolveLogLevel('warning'));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user