diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 5f91dc1069261..24fe3df409679 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -23,12 +23,14 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\Defaults; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Http\Client\IClientService; use OCP\IAppConfig; use OCP\IConfig; use OCP\IGroup; use OCP\IGroupManager; use OCP\IL10N; +use OCP\Install\Events\InstallationCompletedEvent; use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUserManager; @@ -51,6 +53,7 @@ public function __construct( protected LoggerInterface $logger, protected ISecureRandom $random, protected Installer $installer, + protected IEventDispatcher $eventDispatcher, ) { $this->l10n = $l10nFactory->get('lib'); } @@ -495,6 +498,13 @@ public function install(array $options, ?IOutput $output = null): array { } } + // Dispatch installation completed event + $adminUsername = !$disableAdminUser ? ($options['adminlogin'] ?? null) : null; + $adminEmail = !empty($options['adminemail']) ? $options['adminemail'] : null; + $this->eventDispatcher->dispatchTyped( + new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail) + ); + return $error; } diff --git a/lib/public/Install/Events/InstallationCompletedEvent.php b/lib/public/Install/Events/InstallationCompletedEvent.php new file mode 100644 index 0000000000000..1f1cc492973de --- /dev/null +++ b/lib/public/Install/Events/InstallationCompletedEvent.php @@ -0,0 +1,79 @@ +dataDirectory; + } + + /** + * Get the admin username if an admin user was created + * + * @since 31.0.0 + */ + public function getAdminUsername(): ?string { + return $this->adminUsername; + } + + /** + * Get the admin email if configured + * + * @since 31.0.0 + */ + public function getAdminEmail(): ?string { + return $this->adminEmail; + } + + /** + * Check if an admin user was created during installation + * + * @since 31.0.0 + */ + public function hasAdminUser(): bool { + return $this->adminUsername !== null; + } +} diff --git a/tests/lib/Install/Events/InstallationCompletedEventTest.php b/tests/lib/Install/Events/InstallationCompletedEventTest.php new file mode 100644 index 0000000000000..3a973e008b90b --- /dev/null +++ b/tests/lib/Install/Events/InstallationCompletedEventTest.php @@ -0,0 +1,89 @@ +assertEquals($dataDir, $event->getDataDirectory()); + $this->assertEquals($adminUsername, $event->getAdminUsername()); + $this->assertEquals($adminEmail, $event->getAdminEmail()); + $this->assertTrue($event->hasAdminUser()); + } + + public function testConstructorWithMinimalParameters(): void { + $dataDir = '/path/to/data'; + + $event = new InstallationCompletedEvent($dataDir); + + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertNull($event->getAdminUsername()); + $this->assertNull($event->getAdminEmail()); + $this->assertFalse($event->hasAdminUser()); + } + + public function testConstructorWithUsernameOnly(): void { + $dataDir = '/path/to/data'; + $adminUsername = 'admin'; + + $event = new InstallationCompletedEvent($dataDir, $adminUsername); + + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertEquals($adminUsername, $event->getAdminUsername()); + $this->assertNull($event->getAdminEmail()); + $this->assertTrue($event->hasAdminUser()); + } + + public function testConstructorWithUsernameAndEmail(): void { + $dataDir = '/path/to/data'; + $adminUsername = 'admin'; + $adminEmail = 'admin@example.com'; + + $event = new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail); + + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertEquals($adminUsername, $event->getAdminUsername()); + $this->assertEquals($adminEmail, $event->getAdminEmail()); + $this->assertTrue($event->hasAdminUser()); + } + + public function testHasAdminUserReturnsFalseWhenUsernameIsNull(): void { + $event = new InstallationCompletedEvent('/path/to/data', null, 'admin@example.com'); + + $this->assertFalse($event->hasAdminUser()); + $this->assertNull($event->getAdminUsername()); + $this->assertEquals('admin@example.com', $event->getAdminEmail()); + } + + public function testDataDirectoryCanBeAnyString(): void { + $customPath = '/custom/data/directory'; + $event = new InstallationCompletedEvent($customPath); + + $this->assertEquals($customPath, $event->getDataDirectory()); + } + + public function testEmailCanBeSetWithoutUsername(): void { + $dataDir = '/path/to/data'; + $email = 'admin@example.com'; + + $event = new InstallationCompletedEvent($dataDir, null, $email); + + $this->assertNull($event->getAdminUsername()); + $this->assertEquals($email, $event->getAdminEmail()); + $this->assertFalse($event->hasAdminUser()); + } +} diff --git a/tests/lib/SetupTest.php b/tests/lib/SetupTest.php index 0be7eab36f6b2..07d3f59d30c6c 100644 --- a/tests/lib/SetupTest.php +++ b/tests/lib/SetupTest.php @@ -13,7 +13,9 @@ use OC\Setup; use OC\SystemConfig; use OCP\Defaults; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IL10N; +use OCP\Install\Events\InstallationCompletedEvent; use OCP\L10N\IFactory as IL10NFactory; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; @@ -28,6 +30,7 @@ class SetupTest extends \Test\TestCase { protected LoggerInterface $logger; protected ISecureRandom $random; protected Installer $installer; + protected IEventDispatcher $eventDispatcher; protected function setUp(): void { parent::setUp(); @@ -42,9 +45,10 @@ protected function setUp(): void { $this->logger = $this->createMock(LoggerInterface::class); $this->random = $this->createMock(ISecureRandom::class); $this->installer = $this->createMock(Installer::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->setupClass = $this->getMockBuilder(Setup::class) ->onlyMethods(['class_exists', 'is_callable', 'getAvailableDbDriversForPdo']) - ->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10nFactory, $this->defaults, $this->logger, $this->random, $this->installer]) + ->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10nFactory, $this->defaults, $this->logger, $this->random, $this->installer, $this->eventDispatcher]) ->getMock(); } @@ -170,4 +174,87 @@ public static function findWebRootProvider(): array { 'empty' => ['', false], ]; } + + /** + * Test that Setup class has eventDispatcher injected + */ + public function testSetupHasEventDispatcher(): void { + $reflectionClass = new \ReflectionClass($this->setupClass); + $property = $reflectionClass->getProperty('eventDispatcher'); + $property->setAccessible(true); + + $eventDispatcher = $property->getValue($this->setupClass); + + $this->assertInstanceOf(IEventDispatcher::class, $eventDispatcher); + } + + /** + * Helper method to extract event parameters from install options + * This mirrors the logic in Setup::install() for extracting dataDir and admin parameters + * + * Note: This assumes 'directory' key is present in options. Setup::install() has a fallback + * that sets a default directory if empty, but our tests always provide this key. + */ + private function extractInstallationEventParameters(array $options): array { + $dataDir = htmlspecialchars_decode($options['directory']); + $disableAdminUser = (bool)($options['admindisable'] ?? false); + $adminUsername = !$disableAdminUser ? ($options['adminlogin'] ?? null) : null; + $adminEmail = !empty($options['adminemail']) ? $options['adminemail'] : null; + + return [$dataDir, $adminUsername, $adminEmail]; + } + + /** + * Test that InstallationCompletedEvent can be created with parameters from install options + * + * This test verifies that the InstallationCompletedEvent can be properly constructed with + * the parameters that Setup::install() extracts from the options array for dataDir and admin parameters. + * + * Note: Testing that Setup::install() actually dispatches this event requires a full integration + * test with database setup, file system operations, and app installation, which is beyond the + * scope of a unit test. The event class itself is thoroughly tested in InstallationCompletedEventTest.php. + */ + public function testInstallationCompletedEventParametersFromInstallOptions(): void { + // Simulate the options array as passed to Setup::install() + $options = [ + 'directory' => '/path/to/data', + 'adminlogin' => 'admin', + 'adminemail' => 'admin@example.com', + ]; + + // Extract parameters the same way Setup::install() does + [$dataDir, $adminUsername, $adminEmail] = $this->extractInstallationEventParameters($options); + + // Create the event as Setup::install() does after successful installation + $event = new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail); + + // Verify the event contains the expected values + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertEquals($adminUsername, $event->getAdminUsername()); + $this->assertEquals($adminEmail, $event->getAdminEmail()); + $this->assertTrue($event->hasAdminUser()); + } + + /** + * Test that event parameters handle disabled admin user correctly + * + * This tests the scenario where Setup::install() is called with admindisable=true, + * resulting in a null adminUsername in the event. + */ + public function testInstallationCompletedEventWithDisabledAdminUser(): void { + $options = [ + 'directory' => '/path/to/data', + 'admindisable' => true, + ]; + + // Extract parameters as Setup::install() does + [$dataDir, $adminUsername, $adminEmail] = $this->extractInstallationEventParameters($options); + + $event = new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail); + + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertNull($event->getAdminUsername()); + $this->assertNull($event->getAdminEmail()); + $this->assertFalse($event->hasAdminUser()); + } }