|
20 | 20 | use CodeIgniter\Test\CIUnitTestCase; |
21 | 21 | use CodeIgniter\Test\Mock\MockInputOutput; |
22 | 22 | use CodeIgniter\Test\StreamFilterTrait; |
| 23 | +use PHPUnit\Framework\Attributes\CoversClass; |
23 | 24 | use PHPUnit\Framework\Attributes\Group; |
| 25 | +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; |
24 | 26 | use PHPUnit\Framework\Attributes\WithoutErrorHandler; |
25 | 27 |
|
26 | 28 | /** |
27 | 29 | * @internal |
28 | 30 | */ |
| 31 | +#[CoversClass(RotateKey::class)] |
29 | 32 | #[Group('Others')] |
30 | 33 | final class RotateKeyTest extends CIUnitTestCase |
31 | 34 | { |
@@ -113,6 +116,7 @@ public function testRotateMovesCurrentKeyToPreviousKeysAndGeneratesNew(): void |
113 | 116 |
|
114 | 117 | $this->assertSame( |
115 | 118 | <<<'EOT' |
| 119 | +
|
116 | 120 | Encryption key rotated. 1 previous key retained for decryption fallback. |
117 | 121 | Re-encrypt existing data with the new key when ready. |
118 | 122 |
|
@@ -140,6 +144,7 @@ public function testRotatePrependsToExistingPreviousKeysList(): void |
140 | 144 |
|
141 | 145 | $this->assertSame( |
142 | 146 | <<<'EOT' |
| 147 | +
|
143 | 148 | Encryption key rotated. 3 previous keys retained for decryption fallback. |
144 | 149 | Re-encrypt existing data with the new key when ready. |
145 | 150 |
|
@@ -396,6 +401,23 @@ public function testRotateRejectsNonNumericKeepValue(): void |
396 | 401 | $this->assertSame(self::SEED_KEY, env('encryption.key')); |
397 | 402 | } |
398 | 403 |
|
| 404 | + public function testRotateRejectsFractionalKeepValue(): void |
| 405 | + { |
| 406 | + $this->seedEnv(self::SEED_KEY); |
| 407 | + |
| 408 | + command('key:rotate --force --keep=3.5'); |
| 409 | + |
| 410 | + $this->assertSame( |
| 411 | + <<<'EOT' |
| 412 | +
|
| 413 | + The --keep option must be a non-negative integer. |
| 414 | + |
| 415 | + EOT, |
| 416 | + $this->getUndecoratedBuffer(), |
| 417 | + ); |
| 418 | + $this->assertSame(self::SEED_KEY, env('encryption.key')); |
| 419 | + } |
| 420 | + |
399 | 421 | public function testRotateRejectsNegativeLengthValue(): void |
400 | 422 | { |
401 | 423 | $this->seedEnv(self::SEED_KEY); |
@@ -457,6 +479,57 @@ public function testRotateRejectsNonNumericLengthValue(): void |
457 | 479 | $this->assertSame($envContentsBefore, (string) file_get_contents($this->envPath)); |
458 | 480 | } |
459 | 481 |
|
| 482 | + public function testRotateRejectsFractionalLengthValue(): void |
| 483 | + { |
| 484 | + $this->seedEnv(self::SEED_KEY); |
| 485 | + $envContentsBefore = (string) file_get_contents($this->envPath); |
| 486 | + |
| 487 | + command('key:rotate --force --length=3.5'); |
| 488 | + |
| 489 | + $this->assertSame( |
| 490 | + <<<'EOT' |
| 491 | +
|
| 492 | + The --length option must be a positive integer. |
| 493 | + |
| 494 | + EOT, |
| 495 | + $this->getUndecoratedBuffer(), |
| 496 | + ); |
| 497 | + $this->assertSame(self::SEED_KEY, env('encryption.key')); |
| 498 | + $this->assertSame($envContentsBefore, (string) file_get_contents($this->envPath)); |
| 499 | + } |
| 500 | + |
| 501 | + public function testRotateErrorsWhenEnvFileIsMissing(): void |
| 502 | + { |
| 503 | + // No seedEnv() call: `.env` is absent. Populate the env var directly so |
| 504 | + // the up-front `encryption.key` existence check passes. |
| 505 | + putenv('encryption.key=' . self::SEED_KEY); |
| 506 | + $_ENV['encryption.key'] = self::SEED_KEY; |
| 507 | + |
| 508 | + command('key:rotate --force'); |
| 509 | + |
| 510 | + $this->assertStringContainsString('Cannot rotate: `.env` file not found at', $this->getUndecoratedBuffer()); |
| 511 | + $this->assertFileDoesNotExist($this->envPath, 'No `.env` file should have been created.'); |
| 512 | + } |
| 513 | + |
| 514 | + #[RequiresOperatingSystem('Linux|Darwin')] |
| 515 | + public function testRotateErrorsWhenEnvFileIsNotWritable(): void |
| 516 | + { |
| 517 | + $this->seedEnv(self::SEED_KEY); |
| 518 | + $envContentsBefore = (string) file_get_contents($this->envPath); |
| 519 | + chmod($this->envPath, 0o444); |
| 520 | + |
| 521 | + try { |
| 522 | + command('key:rotate --force'); |
| 523 | + |
| 524 | + $output = $this->getUndecoratedBuffer(); |
| 525 | + $this->assertStringContainsString('Cannot rotate: `.env` file at', $output); |
| 526 | + $this->assertStringContainsString('is not writable', $output); |
| 527 | + $this->assertSame($envContentsBefore, (string) file_get_contents($this->envPath)); |
| 528 | + } finally { |
| 529 | + chmod($this->envPath, 0o644); |
| 530 | + } |
| 531 | + } |
| 532 | + |
460 | 533 | public function testRotateIgnoresCommentMentioningPreviousKeysWhenInserting(): void |
461 | 534 | { |
462 | 535 | $envContents = "# encryption.previousKeys is for decryption fallback\nencryption.key = " . self::SEED_KEY . "\n"; |
@@ -514,29 +587,4 @@ public function testRotateInsertsAfterExportPrefixedEncryptionKey(): void |
514 | 587 | ); |
515 | 588 | $this->assertSame(self::SEED_KEY, env('encryption.previousKeys')); |
516 | 589 | } |
517 | | - |
518 | | - public function testRotateErrorsWhenEnvFileIsNotWritable(): void |
519 | | - { |
520 | | - $this->seedEnv(self::SEED_KEY); |
521 | | - chmod($this->envPath, 0o444); |
522 | | - |
523 | | - try { |
524 | | - command('key:rotate --force'); |
525 | | - |
526 | | - $this->assertSame( |
527 | | - sprintf( |
528 | | - <<<'EOT' |
529 | | -
|
530 | | - Failed to write `encryption.previousKeys` to %s. |
531 | | - |
532 | | - EOT, |
533 | | - clean_path($this->envPath), |
534 | | - ), |
535 | | - $this->getUndecoratedBuffer(), |
536 | | - ); |
537 | | - $this->assertSame(self::SEED_KEY, env('encryption.key')); |
538 | | - } finally { |
539 | | - chmod($this->envPath, 0o644); |
540 | | - } |
541 | | - } |
542 | 590 | } |
0 commit comments