@@ -397,6 +397,125 @@ public function testRotateRejectsNonNumericKeepValue(): void
397397 $ this ->assertSame (self ::SEED_KEY , env ('encryption.key ' ));
398398 }
399399
400+ public function testRotateRejectsNegativeLengthValue (): void
401+ {
402+ $ this ->seedEnv (self ::SEED_KEY );
403+ $ envContentsBefore = (string ) file_get_contents ($ this ->envPath );
404+
405+ command ('key:rotate --force --length=-1 ' );
406+
407+ $ this ->assertSame (
408+ <<<'EOT'
409+
410+ The --length option must be a positive integer.
411+
412+ EOT,
413+ $ this ->getUndecoratedBuffer (),
414+ );
415+ $ this ->assertSame (self ::SEED_KEY , env ('encryption.key ' ));
416+ $ this ->assertSame (
417+ $ envContentsBefore ,
418+ (string ) file_get_contents ($ this ->envPath ),
419+ 'Validation must reject the run before any .env mutation. ' ,
420+ );
421+ }
422+
423+ public function testRotateRejectsZeroLengthValue (): void
424+ {
425+ $ this ->seedEnv (self ::SEED_KEY );
426+ $ envContentsBefore = (string ) file_get_contents ($ this ->envPath );
427+
428+ command ('key:rotate --force --length=0 ' );
429+
430+ $ this ->assertSame (
431+ <<<'EOT'
432+
433+ The --length option must be a positive integer.
434+
435+ EOT,
436+ $ this ->getUndecoratedBuffer (),
437+ );
438+ $ this ->assertSame (self ::SEED_KEY , env ('encryption.key ' ));
439+ $ this ->assertSame ($ envContentsBefore , (string ) file_get_contents ($ this ->envPath ));
440+ }
441+
442+ public function testRotateRejectsNonNumericLengthValue (): void
443+ {
444+ $ this ->seedEnv (self ::SEED_KEY );
445+ $ envContentsBefore = (string ) file_get_contents ($ this ->envPath );
446+
447+ command ('key:rotate --force --length=abc ' );
448+
449+ $ this ->assertSame (
450+ <<<'EOT'
451+
452+ The --length option must be a positive integer.
453+
454+ EOT,
455+ $ this ->getUndecoratedBuffer (),
456+ );
457+ $ this ->assertSame (self ::SEED_KEY , env ('encryption.key ' ));
458+ $ this ->assertSame ($ envContentsBefore , (string ) file_get_contents ($ this ->envPath ));
459+ }
460+
461+ public function testRotateIgnoresCommentMentioningPreviousKeysWhenInserting (): void
462+ {
463+ $ envContents = "# encryption.previousKeys is for decryption fallback \nencryption.key = " . self ::SEED_KEY . "\n" ;
464+ file_put_contents ($ this ->envPath , $ envContents );
465+ $ this ->resetEnvironment ();
466+ (new DotEnv (ROOTPATH ))->load ();
467+
468+ command ('key:rotate --force ' );
469+
470+ $ contents = (string ) file_get_contents ($ this ->envPath );
471+ $ this ->assertMatchesRegularExpression (
472+ '/^encryption\.previousKeys = ' . preg_quote (self ::SEED_KEY , '/ ' ) . '$/m ' ,
473+ $ contents ,
474+ 'A real `encryption.previousKeys` setting must be written even when a comment mentions the name. ' ,
475+ );
476+ $ this ->assertSame (self ::SEED_KEY , env ('encryption.previousKeys ' ));
477+ }
478+
479+ public function testRotateReplacesPreviousKeysLineWithExportPrefix (): void
480+ {
481+ $ existing = 'hex2bin: ' . str_repeat ('a ' , 64 );
482+ $ envContents = 'encryption.key = ' . self ::SEED_KEY . "\nexport encryption.previousKeys = {$ existing }\n" ;
483+ file_put_contents ($ this ->envPath , $ envContents );
484+ $ this ->resetEnvironment ();
485+ (new DotEnv (ROOTPATH ))->load ();
486+
487+ command ('key:rotate --force ' );
488+
489+ $ contents = (string ) file_get_contents ($ this ->envPath );
490+ $ this ->assertMatchesRegularExpression (
491+ '/^export encryption\.previousKeys = ' . preg_quote (self ::SEED_KEY . ', ' . $ existing , '/ ' ) . '$/m ' ,
492+ $ contents ,
493+ 'The existing `export` prefix should be preserved and the value rewritten. ' ,
494+ );
495+ $ this ->assertSame (
496+ self ::SEED_KEY . ', ' . $ existing ,
497+ env ('encryption.previousKeys ' ),
498+ );
499+ }
500+
501+ public function testRotateInsertsAfterExportPrefixedEncryptionKey (): void
502+ {
503+ $ envContents = 'export encryption.key = ' . self ::SEED_KEY . "\n" ;
504+ file_put_contents ($ this ->envPath , $ envContents );
505+ $ this ->resetEnvironment ();
506+ (new DotEnv (ROOTPATH ))->load ();
507+
508+ command ('key:rotate --force ' );
509+
510+ $ contents = (string ) file_get_contents ($ this ->envPath );
511+ $ this ->assertMatchesRegularExpression (
512+ '/^export encryption\.key = hex2bin:[a-f0-9]{64}\nencryption\.previousKeys = ' . preg_quote (self ::SEED_KEY , '/ ' ) . '$/m ' ,
513+ $ contents ,
514+ '`encryption.previousKeys` should be inserted on the line directly after an `export`-prefixed `encryption.key`. ' ,
515+ );
516+ $ this ->assertSame (self ::SEED_KEY , env ('encryption.previousKeys ' ));
517+ }
518+
400519 public function testRotateErrorsWhenEnvFileIsNotWritable (): void
401520 {
402521 $ this ->seedEnv (self ::SEED_KEY );
0 commit comments