diff --git a/tests/Integration/Infrastructure/Adapter/In/Cli/Commands/BackupCommandTest.php b/tests/Integration/Infrastructure/Adapter/In/Cli/Commands/BackupCommandTest.php index 2046f302c..fbc1df4aa 100644 --- a/tests/Integration/Infrastructure/Adapter/In/Cli/Commands/BackupCommandTest.php +++ b/tests/Integration/Infrastructure/Adapter/In/Cli/Commands/BackupCommandTest.php @@ -145,6 +145,29 @@ public function testBackupHonorsCustomPath(): void $this->assertEmpty($default, 'Backup was written to the default dir instead of --path'); } + /** + * getPath() only asks interactively when neither the env var nor --path supplied a + * value. CommandTester runs with interactive=false (see executeCommandTest()), and + * Symfony's QuestionHelper answers a non-interactive ask() with the question's default + * without touching any input stream -- so this exercises the prompt branch without + * risking the "blocks forever" trap the other CLI tests warn about, and still ends up + * backing up to the same default path as testBackupIsSuccessful(). + * + * @throws DependencyException + * @throws NotFoundException + */ + public function testBackupAsksForPathWhenNeitherEnvVarNorOptionIsSet(): void + { + $this->setupDatabase(); + + $commandTester = $this->executeCommandTest(BackupCommand::class, ['--path' => '']); + + $output = $commandTester->getDisplay(); + $this->assertStringContainsString('Application and database backup completed successfully', $output); + + $this->checkBackupFilesAreCreated(); + } + /** * Without a reachable database the dump must fail * diff --git a/tests/Unit/Infrastructure/Adapter/In/Cli/CliCommandHelperTest.php b/tests/Unit/Infrastructure/Adapter/In/Cli/CliCommandHelperTest.php new file mode 100644 index 000000000..e75fce51e --- /dev/null +++ b/tests/Unit/Infrastructure/Adapter/In/Cli/CliCommandHelperTest.php @@ -0,0 +1,68 @@ +. + */ + +namespace SP\Tests\Unit\Infrastructure\Adapter\In\Cli; + +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use SP\Infrastructure\Adapter\In\Cli\Commands\BackupCommand; +use SP\Infrastructure\Adapter\In\Cli\Commands\Crypt\UpdateMasterPasswordCommand; +use SP\Infrastructure\Adapter\In\Cli\Commands\InstallCommand; +use SP\Infrastructure\Adapter\In\Cli\CliCommandHelper; + +/** + * This is what bin/cli.php registers on the Symfony Application: whatever it hands back is the + * full set of `sp:*` commands an operator can run. A command dropped here silently disappears + * from the CLI without any error -- there is nowhere else that would notice. + * + * InstallCommand, BackupCommand and UpdateMasterPasswordCommand are all `final`, so they cannot + * be doubled with createMock(); their constructors also pull in the real DI graph (installer, + * master password, account services...) that has nothing to do with what this class does. + * newInstanceWithoutConstructor() gives real, distinguishable instances of the exact types + * CliCommandHelper is wired against without any of that, which is all identity-based assertions + * below need. + */ +#[Group('unitary')] +class CliCommandHelperTest extends TestCase +{ + #[Test] + public function everyRegisteredCommandIsReturnedInTheOrderItWasWired(): void + { + $installCommand = (new ReflectionClass(InstallCommand::class))->newInstanceWithoutConstructor(); + $backupCommand = (new ReflectionClass(BackupCommand::class))->newInstanceWithoutConstructor(); + $updateMasterPasswordCommand = (new ReflectionClass(UpdateMasterPasswordCommand::class)) + ->newInstanceWithoutConstructor(); + + $helper = new CliCommandHelper($installCommand, $backupCommand, $updateMasterPasswordCommand); + + self::assertSame( + [$installCommand, $backupCommand, $updateMasterPasswordCommand], + $helper->getCommands() + ); + } +} diff --git a/tests/Unit/Infrastructure/Adapter/In/Web/DataGrid/DataGridTest.php b/tests/Unit/Infrastructure/Adapter/In/Web/DataGrid/DataGridTest.php index d801d99d7..d5773dd8a 100644 --- a/tests/Unit/Infrastructure/Adapter/In/Web/DataGrid/DataGridTest.php +++ b/tests/Unit/Infrastructure/Adapter/In/Web/DataGrid/DataGridTest.php @@ -126,6 +126,28 @@ public function updatingThePagerTakesTheRowCountFromTheData() self::assertSame(2, $grid->getPager()->getTotalRows()); } + /** + * A grid built without a pager (e.g. a listing too short to page) must not blow up when + * asked to update one -- updatePager() is called unconditionally by the controllers that + * build a grid, whether or not that grid ever got a pager attached. + * + * @throws Exception + */ + #[Test] + public function updatingThePagerIsANoOpWhenNoPagerWasSet() + { + $data = new DataGridData(); + $data->addDataRowSource('name'); + $data->setData(QueryResult::withTotalNumRows([], 0)); + + $grid = $this->buildGrid(); + $grid->setData($data); + + self::assertNull($grid->getPager()); + self::assertSame($grid, $grid->updatePager()); + self::assertNull($grid->getPager()); + } + /** * Every action counts towards the listing's own total, which the template uses to size the * actions column, and the menu keeps its own count. @@ -191,6 +213,36 @@ public function aMissingTemplateIsNotSet() self::assertNull($grid->getDataActionsTemplate()); } + /** + * A template that does exist is resolved to its full path and kept, so the screen renders + * that section instead of silently skipping it. Covers both branches of the template path + * (with and without a base subdirectory) that a missing-template test cannot reach, since + * that one never gets past the is_readable() check. + * + * @throws Exception + */ + #[Test] + public function aReadableTemplateIsResolvedToItsFullPathAndKept() + { + // Keyed on something unique to this test: TMP_PATH's vfs filesystem is shared by + // every test in the process. + $viewsPath = TMP_PATH . '/datagrid_template_test_' . uniqid(); + mkdir($viewsPath . '/rows', 0755, true); + file_put_contents($viewsPath . '/header.inc', 'header content'); + file_put_contents($viewsPath . '/rows/row.inc', 'row content'); + + $theme = $this->createStub(ThemeInterface::class); + $theme->method('getViewsPath')->willReturn($viewsPath); + + $grid = new DataGrid($theme); + + $grid->setDataHeaderTemplate('header'); + $grid->setDataRowTemplate('row', 'rows'); + + self::assertSame($viewsPath . '/header.inc', $grid->getDataHeaderTemplate()); + self::assertSame($viewsPath . '/rows/row.inc', $grid->getDataRowTemplate()); + } + /** * What the listing does when it is closed — the id of the action to return to — is carried on * the grid.