aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Search/SearchComposerTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Search/SearchComposerTest.php')
-rw-r--r--tests/lib/Search/SearchComposerTest.php290
1 files changed, 290 insertions, 0 deletions
diff --git a/tests/lib/Search/SearchComposerTest.php b/tests/lib/Search/SearchComposerTest.php
new file mode 100644
index 00000000000..f8cf1410fad
--- /dev/null
+++ b/tests/lib/Search/SearchComposerTest.php
@@ -0,0 +1,290 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Search;
+
+use InvalidArgumentException;
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\AppFramework\Bootstrap\RegistrationContext;
+use OC\AppFramework\Bootstrap\ServiceRegistration;
+use OC\Search\SearchComposer;
+use OCP\IAppConfig;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\Search\IInAppSearch;
+use OCP\Search\IProvider;
+use OCP\Search\ISearchQuery;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class SearchComposerTest extends TestCase {
+ private Coordinator&MockObject $bootstrapCoordinator;
+ private ContainerInterface&MockObject $container;
+ private IURLGenerator&MockObject $urlGenerator;
+ private LoggerInterface&MockObject $logger;
+ private IAppConfig&MockObject $appConfig;
+ private SearchComposer $searchComposer;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->bootstrapCoordinator = $this->createMock(Coordinator::class);
+ $this->container = $this->createMock(ContainerInterface::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->appConfig = $this->createMock(IAppConfig::class);
+
+ $this->searchComposer = new SearchComposer(
+ $this->bootstrapCoordinator,
+ $this->container,
+ $this->urlGenerator,
+ $this->logger,
+ $this->appConfig
+ );
+
+ $this->setupUrlGenerator();
+ }
+
+ private function setupUrlGenerator(): void {
+ $this->urlGenerator->method('imagePath')
+ ->willReturnCallback(function ($appId, $imageName) {
+ return "/apps/$appId/img/$imageName";
+ });
+ }
+
+ private function setupEmptyRegistrationContext(): void {
+ $this->bootstrapCoordinator->expects($this->once())
+ ->method('getRegistrationContext')
+ ->willReturn(null);
+ }
+
+ private function setupAppConfigForAllowedProviders(array $allowedProviders = []): void {
+ $this->appConfig->method('getValueArray')
+ ->with('core', 'unified_search.providers_allowed')
+ ->willReturn($allowedProviders);
+ }
+
+ /**
+ * @param array<string, array{service: string, appId: string, order: int, isInApp?: bool}> $providerConfigs
+ * @return array{registrations: ServiceRegistration[], providers: IProvider[]}
+ */
+ private function createMockProvidersAndRegistrations(array $providerConfigs): array {
+ $registrations = [];
+ $providers = [];
+ $containerMap = [];
+
+ foreach ($providerConfigs as $providerId => $config) {
+ // Create registration mock
+ $registration = $this->createMock(ServiceRegistration::class);
+ $registration->method('getService')->willReturn($config['service']);
+ $registration->method('getAppId')->willReturn($config['appId']);
+ $registrations[] = $registration;
+
+ // Create provider mock
+ $providerClass = $config['isInApp'] ?? false ? IInAppSearch::class : IProvider::class;
+ $provider = $this->createMock($providerClass);
+ $provider->method('getId')->willReturn($providerId);
+ $provider->method('getName')->willReturn("Provider $providerId");
+ $provider->method('getOrder')->willReturn($config['order']);
+
+ $providers[$providerId] = $provider;
+ $containerMap[] = [$config['service'], $provider];
+ }
+
+ $this->container->expects($this->exactly(count($providerConfigs)))
+ ->method('get')
+ ->willReturnMap($containerMap);
+
+ return ['registrations' => $registrations, 'providers' => $providers];
+ }
+
+ private function setupRegistrationContextWithProviders(array $registrations): void {
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $registrationContext->method('getSearchProviders')->willReturn($registrations);
+
+ $this->bootstrapCoordinator->expects($this->once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ }
+
+ public function testGetProvidersWithNoRegisteredProviders(): void {
+ $this->setupEmptyRegistrationContext();
+
+ $providers = $this->searchComposer->getProviders('/test/route', []);
+
+ $this->assertIsArray($providers);
+ $this->assertEmpty($providers);
+ }
+
+ public function testSearchWithUnknownProvider(): void {
+ $this->setupEmptyRegistrationContext();
+
+ $user = $this->createMock(IUser::class);
+ $query = $this->createMock(ISearchQuery::class);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Provider unknown_provider is unknown');
+
+ $this->searchComposer->search($user, 'unknown_provider', $query);
+ }
+
+ public function testGetProvidersWithMultipleProviders(): void {
+ $providerConfigs = [
+ 'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
+ 'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
+ 'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 15, 'isInApp' => true],
+ ];
+
+ $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
+ $this->setupRegistrationContextWithProviders($mockData['registrations']);
+ $this->setupAppConfigForAllowedProviders();
+
+ $providers = $this->searchComposer->getProviders('/test/route', []);
+
+ $this->assertProvidersStructureAndSorting($providers, [
+ ['id' => 'provider2', 'name' => 'Provider provider2', 'appId' => 'app2', 'order' => 5, 'inAppSearch' => false],
+ ['id' => 'provider1', 'name' => 'Provider provider1', 'appId' => 'app1', 'order' => 10, 'inAppSearch' => false],
+ ['id' => 'provider3', 'name' => 'Provider provider3', 'appId' => 'app3', 'order' => 15, 'inAppSearch' => true],
+ ]);
+ }
+
+ public function testGetProvidersWithEmptyAllowedProvidersConfiguration(): void {
+ $providerConfigs = [
+ 'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
+ 'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
+ ];
+
+ $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
+ $this->setupRegistrationContextWithProviders($mockData['registrations']);
+ $this->setupAppConfigForAllowedProviders();
+
+ $providers = $this->searchComposer->getProviders('/test/route', []);
+
+ $this->assertCount(2, $providers);
+ $this->assertProvidersAreSortedByOrder($providers);
+ $this->assertEquals('provider2', $providers[0]['id']);
+ $this->assertEquals('provider1', $providers[1]['id']);
+ }
+
+ public function testGetProvidersWithAllowedProvidersRestriction(): void {
+ $providerConfigs = [
+ 'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
+ 'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
+ 'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 15],
+ 'provider4' => ['service' => 'provider4_service', 'appId' => 'app4', 'order' => 8],
+ ];
+
+ $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
+ $this->setupRegistrationContextWithProviders($mockData['registrations']);
+ $this->setupAppConfigForAllowedProviders(['provider1', 'provider3']);
+
+ $providers = $this->searchComposer->getProviders('/test/route', []);
+
+ $this->assertProvidersStructureAndSorting($providers, [
+ ['id' => 'provider1', 'name' => 'Provider provider1', 'appId' => 'app1', 'order' => 10, 'inAppSearch' => false],
+ ['id' => 'provider3', 'name' => 'Provider provider3', 'appId' => 'app3', 'order' => 15, 'inAppSearch' => false],
+ ]);
+
+ // Verify excluded providers are not present
+ $providerIds = array_column($providers, 'id');
+ $this->assertNotContains('provider2', $providerIds);
+ $this->assertNotContains('provider4', $providerIds);
+ }
+
+ public function testGetProvidersFiltersByAllowedProvidersCompletely(): void {
+ $providerConfigs = [
+ 'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
+ 'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
+ ];
+
+ $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
+ $this->setupRegistrationContextWithProviders($mockData['registrations']);
+ $this->setupAppConfigForAllowedProviders(['provider_not_exists']);
+
+ $providers = $this->searchComposer->getProviders('/test/route', []);
+
+ $this->assertIsArray($providers);
+ $this->assertEmpty($providers);
+ }
+
+ public function testGetProvidersWithMixedOrderValues(): void {
+ $providerConfigs = [
+ 'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 100],
+ 'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 1],
+ 'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 50],
+ ];
+
+ $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
+ $this->setupRegistrationContextWithProviders($mockData['registrations']);
+ $this->setupAppConfigForAllowedProviders();
+
+ $providers = $this->searchComposer->getProviders('/test/route', []);
+
+ $this->assertCount(3, $providers);
+ $this->assertProvidersAreSortedByOrder($providers);
+
+ // Verify correct ordering: provider2 (1), provider3 (50), provider1 (100)
+ $this->assertEquals('provider2', $providers[0]['id']);
+ $this->assertEquals('provider3', $providers[1]['id']);
+ $this->assertEquals('provider1', $providers[2]['id']);
+ }
+
+ public function testProviderIconGeneration(): void {
+ $providerConfigs = [
+ 'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
+ ];
+
+ $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
+ $this->setupRegistrationContextWithProviders($mockData['registrations']);
+ $this->setupAppConfigForAllowedProviders();
+
+ $providers = $this->searchComposer->getProviders('/test/route', []);
+
+ $this->assertCount(1, $providers);
+ $this->assertArrayHasKey('icon', $providers[0]);
+ $this->assertStringContainsString('/apps/provider1/img/provider1.svg', $providers[0]['icon']);
+ }
+
+ /**
+ * Assert providers array structure and expected sorting
+ */
+ private function assertProvidersStructureAndSorting(array $actualProviders, array $expectedProviders): void {
+ $this->assertIsArray($actualProviders);
+ $this->assertCount(count($expectedProviders), $actualProviders);
+
+ foreach ($actualProviders as $index => $provider) {
+ $this->assertProviderHasRequiredFields($provider);
+
+ $expected = $expectedProviders[$index];
+ $this->assertEquals($expected['id'], $provider['id']);
+ $this->assertEquals($expected['name'], $provider['name']);
+ $this->assertEquals($expected['appId'], $provider['appId']);
+ $this->assertEquals($expected['order'], $provider['order']);
+ $this->assertEquals($expected['inAppSearch'], $provider['inAppSearch']);
+ }
+
+ $this->assertProvidersAreSortedByOrder($actualProviders);
+ }
+
+ private function assertProviderHasRequiredFields(array $provider): void {
+ $requiredFields = ['id', 'appId', 'name', 'icon', 'order', 'triggers', 'filters', 'inAppSearch'];
+ foreach ($requiredFields as $field) {
+ $this->assertArrayHasKey($field, $provider, "Provider must have '$field' field");
+ }
+ }
+
+ private function assertProvidersAreSortedByOrder(array $providers): void {
+ $orders = array_column($providers, 'order');
+ $sortedOrders = $orders;
+ sort($sortedOrders);
+ $this->assertEquals($sortedOrders, $orders, 'Providers should be sorted by order');
+ }
+}