Completed
Push — master ( 50c9c7...7fe5c8 )
by
unknown
21:00 queued 15s
created
lib/private/Search/SearchComposer.php 2 patches
Indentation   +302 added lines, -302 removed lines patch added patch discarded remove patch
@@ -51,306 +51,306 @@
 block discarded – undo
51 51
  * @psalm-import-type CoreUnifiedSearchProvider from ResponseDefinitions
52 52
  */
53 53
 class SearchComposer {
54
-	/**
55
-	 * @var array<string, array{appId: string, provider: IProvider}>
56
-	 */
57
-	private array $providers = [];
58
-
59
-	private array $commonFilters;
60
-	private array $customFilters = [];
61
-
62
-	private array $handlers = [];
63
-
64
-	public function __construct(
65
-		private Coordinator $bootstrapCoordinator,
66
-		private ContainerInterface $container,
67
-		private IURLGenerator $urlGenerator,
68
-		private LoggerInterface $logger,
69
-		private IAppConfig $appConfig,
70
-	) {
71
-		$this->commonFilters = [
72
-			IFilter::BUILTIN_TERM => new FilterDefinition(IFilter::BUILTIN_TERM, FilterDefinition::TYPE_STRING),
73
-			IFilter::BUILTIN_SINCE => new FilterDefinition(IFilter::BUILTIN_SINCE, FilterDefinition::TYPE_DATETIME),
74
-			IFilter::BUILTIN_UNTIL => new FilterDefinition(IFilter::BUILTIN_UNTIL, FilterDefinition::TYPE_DATETIME),
75
-			IFilter::BUILTIN_TITLE_ONLY => new FilterDefinition(IFilter::BUILTIN_TITLE_ONLY, FilterDefinition::TYPE_BOOL, false),
76
-			IFilter::BUILTIN_PERSON => new FilterDefinition(IFilter::BUILTIN_PERSON, FilterDefinition::TYPE_PERSON),
77
-			IFilter::BUILTIN_PLACES => new FilterDefinition(IFilter::BUILTIN_PLACES, FilterDefinition::TYPE_STRINGS, false),
78
-			IFilter::BUILTIN_PROVIDER => new FilterDefinition(IFilter::BUILTIN_PROVIDER, FilterDefinition::TYPE_STRING, false),
79
-		];
80
-	}
81
-
82
-	/**
83
-	 * Load all providers dynamically that were registered through `registerProvider`
84
-	 *
85
-	 * If $targetProviderId is provided, only this provider is loaded
86
-	 * If a provider can't be loaded we log it but the operation continues nevertheless
87
-	 */
88
-	private function loadLazyProviders(?string $targetProviderId = null): void {
89
-		$context = $this->bootstrapCoordinator->getRegistrationContext();
90
-		if ($context === null) {
91
-			// Too early, nothing registered yet
92
-			return;
93
-		}
94
-
95
-		$registrations = $context->getSearchProviders();
96
-		foreach ($registrations as $registration) {
97
-			try {
98
-				/** @var IProvider $provider */
99
-				$provider = $this->container->get($registration->getService());
100
-				$providerId = $provider->getId();
101
-				if ($targetProviderId !== null && $targetProviderId !== $providerId) {
102
-					continue;
103
-				}
104
-				$this->providers[$providerId] = [
105
-					'appId' => $registration->getAppId(),
106
-					'provider' => $provider,
107
-				];
108
-				$this->handlers[$providerId] = [$providerId];
109
-				if ($targetProviderId !== null) {
110
-					break;
111
-				}
112
-			} catch (ContainerExceptionInterface $e) {
113
-				// Log an continue. We can be fault tolerant here.
114
-				$this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [
115
-					'exception' => $e,
116
-					'app' => $registration->getAppId(),
117
-				]);
118
-			}
119
-		}
120
-
121
-		$this->filterProviders();
122
-
123
-		$this->loadFilters();
124
-	}
125
-
126
-	private function loadFilters(): void {
127
-		foreach ($this->providers as $providerId => $providerData) {
128
-			$appId = $providerData['appId'];
129
-			$provider = $providerData['provider'];
130
-			if (!$provider instanceof IFilteringProvider) {
131
-				continue;
132
-			}
133
-
134
-			foreach ($provider->getCustomFilters() as $filter) {
135
-				$this->registerCustomFilter($filter, $providerId);
136
-			}
137
-			foreach ($provider->getAlternateIds() as $alternateId) {
138
-				$this->handlers[$alternateId][] = $providerId;
139
-			}
140
-			foreach ($provider->getSupportedFilters() as $filterName) {
141
-				if ($this->getFilterDefinition($filterName, $providerId) === null) {
142
-					throw new InvalidArgumentException('Invalid filter ' . $filterName);
143
-				}
144
-			}
145
-		}
146
-	}
147
-
148
-	private function registerCustomFilter(FilterDefinition $filter, string $providerId): void {
149
-		$name = $filter->name();
150
-		if (isset($this->commonFilters[$name])) {
151
-			throw new InvalidArgumentException('Filter name is already used');
152
-		}
153
-
154
-		if (isset($this->customFilters[$providerId])) {
155
-			$this->customFilters[$providerId][$name] = $filter;
156
-		} else {
157
-			$this->customFilters[$providerId] = [$name => $filter];
158
-		}
159
-	}
160
-
161
-	/**
162
-	 * Get a list of all provider IDs & Names for the consecutive calls to `search`
163
-	 * Sort the list by the order property
164
-	 *
165
-	 * @param string $route the route the user is currently at
166
-	 * @param array $routeParameters the parameters of the route the user is currently at
167
-	 *
168
-	 * @return list<CoreUnifiedSearchProvider>
169
-	 */
170
-	public function getProviders(string $route, array $routeParameters): array {
171
-		$this->loadLazyProviders();
172
-
173
-		$providers = array_map(
174
-			function (array $providerData) use ($route, $routeParameters) {
175
-				$appId = $providerData['appId'];
176
-				$provider = $providerData['provider'];
177
-				$order = $provider->getOrder($route, $routeParameters);
178
-				if ($order === null) {
179
-					return;
180
-				}
181
-				$triggers = [$provider->getId()];
182
-				if ($provider instanceof IFilteringProvider) {
183
-					$triggers += $provider->getAlternateIds();
184
-					$filters = $provider->getSupportedFilters();
185
-				} else {
186
-					$filters = [IFilter::BUILTIN_TERM];
187
-				}
188
-
189
-				return [
190
-					'id' => $provider->getId(),
191
-					'appId' => $appId,
192
-					'name' => $provider->getName(),
193
-					'icon' => $this->fetchIcon($appId, $provider->getId()),
194
-					'order' => $order,
195
-					'triggers' => array_values($triggers),
196
-					'filters' => $this->getFiltersType($filters, $provider->getId()),
197
-					'inAppSearch' => $provider instanceof IInAppSearch,
198
-				];
199
-			},
200
-			$this->providers,
201
-		);
202
-		$providers = array_filter($providers);
203
-
204
-		// Sort providers by order and strip associative keys
205
-		usort($providers, function ($provider1, $provider2) {
206
-			return $provider1['order'] <=> $provider2['order'];
207
-		});
208
-
209
-		return $providers;
210
-	}
211
-
212
-	/**
213
-	 * Filter providers based on 'unified_search.providers_allowed' core app config array
214
-	 * Will remove providers that are not in the allowed list
215
-	 */
216
-	private function filterProviders(): void {
217
-		$allowedProviders = $this->appConfig->getValueArray('core', 'unified_search.providers_allowed');
218
-
219
-		if (empty($allowedProviders)) {
220
-			return;
221
-		}
222
-
223
-		foreach (array_keys($this->providers) as $providerId) {
224
-			if (!in_array($providerId, $allowedProviders, true)) {
225
-				unset($this->providers[$providerId]);
226
-				unset($this->handlers[$providerId]);
227
-			}
228
-		}
229
-	}
230
-
231
-	private function fetchIcon(string $appId, string $providerId): string {
232
-		$icons = [
233
-			[$providerId, $providerId . '.svg'],
234
-			[$providerId, 'app.svg'],
235
-			[$appId, $providerId . '.svg'],
236
-			[$appId, $appId . '.svg'],
237
-			[$appId, 'app.svg'],
238
-			['core', 'places/default-app-icon.svg'],
239
-		];
240
-		if ($appId === 'settings' && $providerId === 'users') {
241
-			// Conflict:
242
-			// the file /apps/settings/users.svg is already used in black version by top right user menu
243
-			// Override icon name here
244
-			$icons = [['settings', 'users-white.svg']];
245
-		}
246
-		foreach ($icons as $i => $icon) {
247
-			try {
248
-				return $this->urlGenerator->imagePath(... $icon);
249
-			} catch (RuntimeException $e) {
250
-				// Ignore error
251
-			}
252
-		}
253
-
254
-		return '';
255
-	}
256
-
257
-	/**
258
-	 * @param $filters string[]
259
-	 * @return array<string, string>
260
-	 */
261
-	private function getFiltersType(array $filters, string $providerId): array {
262
-		$filterList = [];
263
-		foreach ($filters as $filter) {
264
-			$filterList[$filter] = $this->getFilterDefinition($filter, $providerId)->type();
265
-		}
266
-
267
-		return $filterList;
268
-	}
269
-
270
-	private function getFilterDefinition(string $name, string $providerId): ?FilterDefinition {
271
-		if (isset($this->commonFilters[$name])) {
272
-			return $this->commonFilters[$name];
273
-		}
274
-		if (isset($this->customFilters[$providerId][$name])) {
275
-			return $this->customFilters[$providerId][$name];
276
-		}
277
-
278
-		return null;
279
-	}
280
-
281
-	/**
282
-	 * @param array<string, string> $parameters
283
-	 */
284
-	public function buildFilterList(string $providerId, array $parameters): FilterCollection {
285
-		$this->loadLazyProviders($providerId);
286
-
287
-		$list = [];
288
-		foreach ($parameters as $name => $value) {
289
-			$filter = $this->buildFilter($name, $value, $providerId);
290
-			if ($filter === null) {
291
-				continue;
292
-			}
293
-			$list[$name] = $filter;
294
-		}
295
-
296
-		return new FilterCollection(... $list);
297
-	}
298
-
299
-	private function buildFilter(string $name, string $value, string $providerId): ?IFilter {
300
-		$filterDefinition = $this->getFilterDefinition($name, $providerId);
301
-		if ($filterDefinition === null) {
302
-			$this->logger->debug('Unable to find {name} definition', [
303
-				'name' => $name,
304
-				'value' => $value,
305
-			]);
306
-
307
-			return null;
308
-		}
309
-
310
-		if (!$this->filterSupportedByProvider($filterDefinition, $providerId)) {
311
-			// FIXME Use dedicated exception and handle it
312
-			throw new UnsupportedFilter($name, $providerId);
313
-		}
314
-
315
-		return FilterFactory::get($filterDefinition->type(), $value);
316
-	}
317
-
318
-	private function filterSupportedByProvider(FilterDefinition $filterDefinition, string $providerId): bool {
319
-		// Non exclusive filters can be ommited by apps
320
-		if (!$filterDefinition->exclusive()) {
321
-			return true;
322
-		}
323
-
324
-		$provider = $this->providers[$providerId]['provider'];
325
-		$supportedFilters = $provider instanceof IFilteringProvider
326
-			? $provider->getSupportedFilters()
327
-			: [IFilter::BUILTIN_TERM];
328
-
329
-		return in_array($filterDefinition->name(), $supportedFilters, true);
330
-	}
331
-
332
-	/**
333
-	 * Query an individual search provider for results
334
-	 *
335
-	 * @param IUser $user
336
-	 * @param string $providerId one of the IDs received by `getProviders`
337
-	 * @param ISearchQuery $query
338
-	 *
339
-	 * @return SearchResult
340
-	 * @throws InvalidArgumentException when the $providerId does not correspond to a registered provider
341
-	 */
342
-	public function search(
343
-		IUser $user,
344
-		string $providerId,
345
-		ISearchQuery $query,
346
-	): SearchResult {
347
-		$this->loadLazyProviders($providerId);
348
-
349
-		$provider = $this->providers[$providerId]['provider'] ?? null;
350
-		if ($provider === null) {
351
-			throw new InvalidArgumentException("Provider $providerId is unknown");
352
-		}
353
-
354
-		return $provider->search($user, $query);
355
-	}
54
+    /**
55
+     * @var array<string, array{appId: string, provider: IProvider}>
56
+     */
57
+    private array $providers = [];
58
+
59
+    private array $commonFilters;
60
+    private array $customFilters = [];
61
+
62
+    private array $handlers = [];
63
+
64
+    public function __construct(
65
+        private Coordinator $bootstrapCoordinator,
66
+        private ContainerInterface $container,
67
+        private IURLGenerator $urlGenerator,
68
+        private LoggerInterface $logger,
69
+        private IAppConfig $appConfig,
70
+    ) {
71
+        $this->commonFilters = [
72
+            IFilter::BUILTIN_TERM => new FilterDefinition(IFilter::BUILTIN_TERM, FilterDefinition::TYPE_STRING),
73
+            IFilter::BUILTIN_SINCE => new FilterDefinition(IFilter::BUILTIN_SINCE, FilterDefinition::TYPE_DATETIME),
74
+            IFilter::BUILTIN_UNTIL => new FilterDefinition(IFilter::BUILTIN_UNTIL, FilterDefinition::TYPE_DATETIME),
75
+            IFilter::BUILTIN_TITLE_ONLY => new FilterDefinition(IFilter::BUILTIN_TITLE_ONLY, FilterDefinition::TYPE_BOOL, false),
76
+            IFilter::BUILTIN_PERSON => new FilterDefinition(IFilter::BUILTIN_PERSON, FilterDefinition::TYPE_PERSON),
77
+            IFilter::BUILTIN_PLACES => new FilterDefinition(IFilter::BUILTIN_PLACES, FilterDefinition::TYPE_STRINGS, false),
78
+            IFilter::BUILTIN_PROVIDER => new FilterDefinition(IFilter::BUILTIN_PROVIDER, FilterDefinition::TYPE_STRING, false),
79
+        ];
80
+    }
81
+
82
+    /**
83
+     * Load all providers dynamically that were registered through `registerProvider`
84
+     *
85
+     * If $targetProviderId is provided, only this provider is loaded
86
+     * If a provider can't be loaded we log it but the operation continues nevertheless
87
+     */
88
+    private function loadLazyProviders(?string $targetProviderId = null): void {
89
+        $context = $this->bootstrapCoordinator->getRegistrationContext();
90
+        if ($context === null) {
91
+            // Too early, nothing registered yet
92
+            return;
93
+        }
94
+
95
+        $registrations = $context->getSearchProviders();
96
+        foreach ($registrations as $registration) {
97
+            try {
98
+                /** @var IProvider $provider */
99
+                $provider = $this->container->get($registration->getService());
100
+                $providerId = $provider->getId();
101
+                if ($targetProviderId !== null && $targetProviderId !== $providerId) {
102
+                    continue;
103
+                }
104
+                $this->providers[$providerId] = [
105
+                    'appId' => $registration->getAppId(),
106
+                    'provider' => $provider,
107
+                ];
108
+                $this->handlers[$providerId] = [$providerId];
109
+                if ($targetProviderId !== null) {
110
+                    break;
111
+                }
112
+            } catch (ContainerExceptionInterface $e) {
113
+                // Log an continue. We can be fault tolerant here.
114
+                $this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [
115
+                    'exception' => $e,
116
+                    'app' => $registration->getAppId(),
117
+                ]);
118
+            }
119
+        }
120
+
121
+        $this->filterProviders();
122
+
123
+        $this->loadFilters();
124
+    }
125
+
126
+    private function loadFilters(): void {
127
+        foreach ($this->providers as $providerId => $providerData) {
128
+            $appId = $providerData['appId'];
129
+            $provider = $providerData['provider'];
130
+            if (!$provider instanceof IFilteringProvider) {
131
+                continue;
132
+            }
133
+
134
+            foreach ($provider->getCustomFilters() as $filter) {
135
+                $this->registerCustomFilter($filter, $providerId);
136
+            }
137
+            foreach ($provider->getAlternateIds() as $alternateId) {
138
+                $this->handlers[$alternateId][] = $providerId;
139
+            }
140
+            foreach ($provider->getSupportedFilters() as $filterName) {
141
+                if ($this->getFilterDefinition($filterName, $providerId) === null) {
142
+                    throw new InvalidArgumentException('Invalid filter ' . $filterName);
143
+                }
144
+            }
145
+        }
146
+    }
147
+
148
+    private function registerCustomFilter(FilterDefinition $filter, string $providerId): void {
149
+        $name = $filter->name();
150
+        if (isset($this->commonFilters[$name])) {
151
+            throw new InvalidArgumentException('Filter name is already used');
152
+        }
153
+
154
+        if (isset($this->customFilters[$providerId])) {
155
+            $this->customFilters[$providerId][$name] = $filter;
156
+        } else {
157
+            $this->customFilters[$providerId] = [$name => $filter];
158
+        }
159
+    }
160
+
161
+    /**
162
+     * Get a list of all provider IDs & Names for the consecutive calls to `search`
163
+     * Sort the list by the order property
164
+     *
165
+     * @param string $route the route the user is currently at
166
+     * @param array $routeParameters the parameters of the route the user is currently at
167
+     *
168
+     * @return list<CoreUnifiedSearchProvider>
169
+     */
170
+    public function getProviders(string $route, array $routeParameters): array {
171
+        $this->loadLazyProviders();
172
+
173
+        $providers = array_map(
174
+            function (array $providerData) use ($route, $routeParameters) {
175
+                $appId = $providerData['appId'];
176
+                $provider = $providerData['provider'];
177
+                $order = $provider->getOrder($route, $routeParameters);
178
+                if ($order === null) {
179
+                    return;
180
+                }
181
+                $triggers = [$provider->getId()];
182
+                if ($provider instanceof IFilteringProvider) {
183
+                    $triggers += $provider->getAlternateIds();
184
+                    $filters = $provider->getSupportedFilters();
185
+                } else {
186
+                    $filters = [IFilter::BUILTIN_TERM];
187
+                }
188
+
189
+                return [
190
+                    'id' => $provider->getId(),
191
+                    'appId' => $appId,
192
+                    'name' => $provider->getName(),
193
+                    'icon' => $this->fetchIcon($appId, $provider->getId()),
194
+                    'order' => $order,
195
+                    'triggers' => array_values($triggers),
196
+                    'filters' => $this->getFiltersType($filters, $provider->getId()),
197
+                    'inAppSearch' => $provider instanceof IInAppSearch,
198
+                ];
199
+            },
200
+            $this->providers,
201
+        );
202
+        $providers = array_filter($providers);
203
+
204
+        // Sort providers by order and strip associative keys
205
+        usort($providers, function ($provider1, $provider2) {
206
+            return $provider1['order'] <=> $provider2['order'];
207
+        });
208
+
209
+        return $providers;
210
+    }
211
+
212
+    /**
213
+     * Filter providers based on 'unified_search.providers_allowed' core app config array
214
+     * Will remove providers that are not in the allowed list
215
+     */
216
+    private function filterProviders(): void {
217
+        $allowedProviders = $this->appConfig->getValueArray('core', 'unified_search.providers_allowed');
218
+
219
+        if (empty($allowedProviders)) {
220
+            return;
221
+        }
222
+
223
+        foreach (array_keys($this->providers) as $providerId) {
224
+            if (!in_array($providerId, $allowedProviders, true)) {
225
+                unset($this->providers[$providerId]);
226
+                unset($this->handlers[$providerId]);
227
+            }
228
+        }
229
+    }
230
+
231
+    private function fetchIcon(string $appId, string $providerId): string {
232
+        $icons = [
233
+            [$providerId, $providerId . '.svg'],
234
+            [$providerId, 'app.svg'],
235
+            [$appId, $providerId . '.svg'],
236
+            [$appId, $appId . '.svg'],
237
+            [$appId, 'app.svg'],
238
+            ['core', 'places/default-app-icon.svg'],
239
+        ];
240
+        if ($appId === 'settings' && $providerId === 'users') {
241
+            // Conflict:
242
+            // the file /apps/settings/users.svg is already used in black version by top right user menu
243
+            // Override icon name here
244
+            $icons = [['settings', 'users-white.svg']];
245
+        }
246
+        foreach ($icons as $i => $icon) {
247
+            try {
248
+                return $this->urlGenerator->imagePath(... $icon);
249
+            } catch (RuntimeException $e) {
250
+                // Ignore error
251
+            }
252
+        }
253
+
254
+        return '';
255
+    }
256
+
257
+    /**
258
+     * @param $filters string[]
259
+     * @return array<string, string>
260
+     */
261
+    private function getFiltersType(array $filters, string $providerId): array {
262
+        $filterList = [];
263
+        foreach ($filters as $filter) {
264
+            $filterList[$filter] = $this->getFilterDefinition($filter, $providerId)->type();
265
+        }
266
+
267
+        return $filterList;
268
+    }
269
+
270
+    private function getFilterDefinition(string $name, string $providerId): ?FilterDefinition {
271
+        if (isset($this->commonFilters[$name])) {
272
+            return $this->commonFilters[$name];
273
+        }
274
+        if (isset($this->customFilters[$providerId][$name])) {
275
+            return $this->customFilters[$providerId][$name];
276
+        }
277
+
278
+        return null;
279
+    }
280
+
281
+    /**
282
+     * @param array<string, string> $parameters
283
+     */
284
+    public function buildFilterList(string $providerId, array $parameters): FilterCollection {
285
+        $this->loadLazyProviders($providerId);
286
+
287
+        $list = [];
288
+        foreach ($parameters as $name => $value) {
289
+            $filter = $this->buildFilter($name, $value, $providerId);
290
+            if ($filter === null) {
291
+                continue;
292
+            }
293
+            $list[$name] = $filter;
294
+        }
295
+
296
+        return new FilterCollection(... $list);
297
+    }
298
+
299
+    private function buildFilter(string $name, string $value, string $providerId): ?IFilter {
300
+        $filterDefinition = $this->getFilterDefinition($name, $providerId);
301
+        if ($filterDefinition === null) {
302
+            $this->logger->debug('Unable to find {name} definition', [
303
+                'name' => $name,
304
+                'value' => $value,
305
+            ]);
306
+
307
+            return null;
308
+        }
309
+
310
+        if (!$this->filterSupportedByProvider($filterDefinition, $providerId)) {
311
+            // FIXME Use dedicated exception and handle it
312
+            throw new UnsupportedFilter($name, $providerId);
313
+        }
314
+
315
+        return FilterFactory::get($filterDefinition->type(), $value);
316
+    }
317
+
318
+    private function filterSupportedByProvider(FilterDefinition $filterDefinition, string $providerId): bool {
319
+        // Non exclusive filters can be ommited by apps
320
+        if (!$filterDefinition->exclusive()) {
321
+            return true;
322
+        }
323
+
324
+        $provider = $this->providers[$providerId]['provider'];
325
+        $supportedFilters = $provider instanceof IFilteringProvider
326
+            ? $provider->getSupportedFilters()
327
+            : [IFilter::BUILTIN_TERM];
328
+
329
+        return in_array($filterDefinition->name(), $supportedFilters, true);
330
+    }
331
+
332
+    /**
333
+     * Query an individual search provider for results
334
+     *
335
+     * @param IUser $user
336
+     * @param string $providerId one of the IDs received by `getProviders`
337
+     * @param ISearchQuery $query
338
+     *
339
+     * @return SearchResult
340
+     * @throws InvalidArgumentException when the $providerId does not correspond to a registered provider
341
+     */
342
+    public function search(
343
+        IUser $user,
344
+        string $providerId,
345
+        ISearchQuery $query,
346
+    ): SearchResult {
347
+        $this->loadLazyProviders($providerId);
348
+
349
+        $provider = $this->providers[$providerId]['provider'] ?? null;
350
+        if ($provider === null) {
351
+            throw new InvalidArgumentException("Provider $providerId is unknown");
352
+        }
353
+
354
+        return $provider->search($user, $query);
355
+    }
356 356
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 				}
112 112
 			} catch (ContainerExceptionInterface $e) {
113 113
 				// Log an continue. We can be fault tolerant here.
114
-				$this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [
114
+				$this->logger->error('Could not load search provider dynamically: '.$e->getMessage(), [
115 115
 					'exception' => $e,
116 116
 					'app' => $registration->getAppId(),
117 117
 				]);
@@ -139,7 +139,7 @@  discard block
 block discarded – undo
139 139
 			}
140 140
 			foreach ($provider->getSupportedFilters() as $filterName) {
141 141
 				if ($this->getFilterDefinition($filterName, $providerId) === null) {
142
-					throw new InvalidArgumentException('Invalid filter ' . $filterName);
142
+					throw new InvalidArgumentException('Invalid filter '.$filterName);
143 143
 				}
144 144
 			}
145 145
 		}
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 		$this->loadLazyProviders();
172 172
 
173 173
 		$providers = array_map(
174
-			function (array $providerData) use ($route, $routeParameters) {
174
+			function(array $providerData) use ($route, $routeParameters) {
175 175
 				$appId = $providerData['appId'];
176 176
 				$provider = $providerData['provider'];
177 177
 				$order = $provider->getOrder($route, $routeParameters);
@@ -202,7 +202,7 @@  discard block
 block discarded – undo
202 202
 		$providers = array_filter($providers);
203 203
 
204 204
 		// Sort providers by order and strip associative keys
205
-		usort($providers, function ($provider1, $provider2) {
205
+		usort($providers, function($provider1, $provider2) {
206 206
 			return $provider1['order'] <=> $provider2['order'];
207 207
 		});
208 208
 
@@ -230,10 +230,10 @@  discard block
 block discarded – undo
230 230
 
231 231
 	private function fetchIcon(string $appId, string $providerId): string {
232 232
 		$icons = [
233
-			[$providerId, $providerId . '.svg'],
233
+			[$providerId, $providerId.'.svg'],
234 234
 			[$providerId, 'app.svg'],
235
-			[$appId, $providerId . '.svg'],
236
-			[$appId, $appId . '.svg'],
235
+			[$appId, $providerId.'.svg'],
236
+			[$appId, $appId.'.svg'],
237 237
 			[$appId, 'app.svg'],
238 238
 			['core', 'places/default-app-icon.svg'],
239 239
 		];
Please login to merge, or discard this patch.
tests/lib/Search/SearchComposerTest.php 2 patches
Indentation   +261 added lines, -261 removed lines patch added patch discarded remove patch
@@ -26,265 +26,265 @@
 block discarded – undo
26 26
 use Test\TestCase;
27 27
 
28 28
 class SearchComposerTest extends TestCase {
29
-	private Coordinator&MockObject $bootstrapCoordinator;
30
-	private ContainerInterface&MockObject $container;
31
-	private IURLGenerator&MockObject $urlGenerator;
32
-	private LoggerInterface&MockObject $logger;
33
-	private IAppConfig&MockObject $appConfig;
34
-	private SearchComposer $searchComposer;
35
-
36
-	protected function setUp(): void {
37
-		parent::setUp();
38
-
39
-		$this->bootstrapCoordinator = $this->createMock(Coordinator::class);
40
-		$this->container = $this->createMock(ContainerInterface::class);
41
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
42
-		$this->logger = $this->createMock(LoggerInterface::class);
43
-		$this->appConfig = $this->createMock(IAppConfig::class);
44
-
45
-		$this->searchComposer = new SearchComposer(
46
-			$this->bootstrapCoordinator,
47
-			$this->container,
48
-			$this->urlGenerator,
49
-			$this->logger,
50
-			$this->appConfig
51
-		);
52
-
53
-		$this->setupUrlGenerator();
54
-	}
55
-
56
-	private function setupUrlGenerator(): void {
57
-		$this->urlGenerator->method('imagePath')
58
-			->willReturnCallback(function ($appId, $imageName) {
59
-				return "/apps/$appId/img/$imageName";
60
-			});
61
-	}
62
-
63
-	private function setupEmptyRegistrationContext(): void {
64
-		$this->bootstrapCoordinator->expects($this->once())
65
-			->method('getRegistrationContext')
66
-			->willReturn(null);
67
-	}
68
-
69
-	private function setupAppConfigForAllowedProviders(array $allowedProviders = []): void {
70
-		$this->appConfig->method('getValueArray')
71
-			->with('core', 'unified_search.providers_allowed')
72
-			->willReturn($allowedProviders);
73
-	}
74
-
75
-	/**
76
-	 * @param array<string, array{service: string, appId: string, order: int, isInApp?: bool}> $providerConfigs
77
-	 * @return array{registrations: ServiceRegistration[], providers: IProvider[]}
78
-	 */
79
-	private function createMockProvidersAndRegistrations(array $providerConfigs): array {
80
-		$registrations = [];
81
-		$providers = [];
82
-		$containerMap = [];
83
-
84
-		foreach ($providerConfigs as $providerId => $config) {
85
-			// Create registration mock
86
-			$registration = $this->createMock(ServiceRegistration::class);
87
-			$registration->method('getService')->willReturn($config['service']);
88
-			$registration->method('getAppId')->willReturn($config['appId']);
89
-			$registrations[] = $registration;
90
-
91
-			// Create provider mock
92
-			$providerClass = $config['isInApp'] ?? false ? IInAppSearch::class : IProvider::class;
93
-			$provider = $this->createMock($providerClass);
94
-			$provider->method('getId')->willReturn($providerId);
95
-			$provider->method('getName')->willReturn("Provider $providerId");
96
-			$provider->method('getOrder')->willReturn($config['order']);
97
-
98
-			$providers[$providerId] = $provider;
99
-			$containerMap[] = [$config['service'], $provider];
100
-		}
101
-
102
-		$this->container->expects($this->exactly(count($providerConfigs)))
103
-			->method('get')
104
-			->willReturnMap($containerMap);
105
-
106
-		return ['registrations' => $registrations, 'providers' => $providers];
107
-	}
108
-
109
-	private function setupRegistrationContextWithProviders(array $registrations): void {
110
-		$registrationContext = $this->createMock(RegistrationContext::class);
111
-		$registrationContext->method('getSearchProviders')->willReturn($registrations);
112
-
113
-		$this->bootstrapCoordinator->expects($this->once())
114
-			->method('getRegistrationContext')
115
-			->willReturn($registrationContext);
116
-	}
117
-
118
-	public function testGetProvidersWithNoRegisteredProviders(): void {
119
-		$this->setupEmptyRegistrationContext();
120
-
121
-		$providers = $this->searchComposer->getProviders('/test/route', []);
122
-
123
-		$this->assertIsArray($providers);
124
-		$this->assertEmpty($providers);
125
-	}
126
-
127
-	public function testSearchWithUnknownProvider(): void {
128
-		$this->setupEmptyRegistrationContext();
129
-
130
-		$user = $this->createMock(IUser::class);
131
-		$query = $this->createMock(ISearchQuery::class);
132
-
133
-		$this->expectException(InvalidArgumentException::class);
134
-		$this->expectExceptionMessage('Provider unknown_provider is unknown');
135
-
136
-		$this->searchComposer->search($user, 'unknown_provider', $query);
137
-	}
138
-
139
-	public function testGetProvidersWithMultipleProviders(): void {
140
-		$providerConfigs = [
141
-			'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
142
-			'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
143
-			'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 15, 'isInApp' => true],
144
-		];
145
-
146
-		$mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
147
-		$this->setupRegistrationContextWithProviders($mockData['registrations']);
148
-		$this->setupAppConfigForAllowedProviders();
149
-
150
-		$providers = $this->searchComposer->getProviders('/test/route', []);
151
-
152
-		$this->assertProvidersStructureAndSorting($providers, [
153
-			['id' => 'provider2', 'name' => 'Provider provider2', 'appId' => 'app2', 'order' => 5, 'inAppSearch' => false],
154
-			['id' => 'provider1', 'name' => 'Provider provider1', 'appId' => 'app1', 'order' => 10, 'inAppSearch' => false],
155
-			['id' => 'provider3', 'name' => 'Provider provider3', 'appId' => 'app3', 'order' => 15, 'inAppSearch' => true],
156
-		]);
157
-	}
158
-
159
-	public function testGetProvidersWithEmptyAllowedProvidersConfiguration(): void {
160
-		$providerConfigs = [
161
-			'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
162
-			'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
163
-		];
164
-
165
-		$mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
166
-		$this->setupRegistrationContextWithProviders($mockData['registrations']);
167
-		$this->setupAppConfigForAllowedProviders();
168
-
169
-		$providers = $this->searchComposer->getProviders('/test/route', []);
170
-
171
-		$this->assertCount(2, $providers);
172
-		$this->assertProvidersAreSortedByOrder($providers);
173
-		$this->assertEquals('provider2', $providers[0]['id']);
174
-		$this->assertEquals('provider1', $providers[1]['id']);
175
-	}
176
-
177
-	public function testGetProvidersWithAllowedProvidersRestriction(): void {
178
-		$providerConfigs = [
179
-			'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
180
-			'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
181
-			'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 15],
182
-			'provider4' => ['service' => 'provider4_service', 'appId' => 'app4', 'order' => 8],
183
-		];
184
-
185
-		$mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
186
-		$this->setupRegistrationContextWithProviders($mockData['registrations']);
187
-		$this->setupAppConfigForAllowedProviders(['provider1', 'provider3']);
188
-
189
-		$providers = $this->searchComposer->getProviders('/test/route', []);
190
-
191
-		$this->assertProvidersStructureAndSorting($providers, [
192
-			['id' => 'provider1', 'name' => 'Provider provider1', 'appId' => 'app1', 'order' => 10, 'inAppSearch' => false],
193
-			['id' => 'provider3', 'name' => 'Provider provider3', 'appId' => 'app3', 'order' => 15, 'inAppSearch' => false],
194
-		]);
195
-
196
-		// Verify excluded providers are not present
197
-		$providerIds = array_column($providers, 'id');
198
-		$this->assertNotContains('provider2', $providerIds);
199
-		$this->assertNotContains('provider4', $providerIds);
200
-	}
201
-
202
-	public function testGetProvidersFiltersByAllowedProvidersCompletely(): void {
203
-		$providerConfigs = [
204
-			'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
205
-			'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
206
-		];
207
-
208
-		$mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
209
-		$this->setupRegistrationContextWithProviders($mockData['registrations']);
210
-		$this->setupAppConfigForAllowedProviders(['provider_not_exists']);
211
-
212
-		$providers = $this->searchComposer->getProviders('/test/route', []);
213
-
214
-		$this->assertIsArray($providers);
215
-		$this->assertEmpty($providers);
216
-	}
217
-
218
-	public function testGetProvidersWithMixedOrderValues(): void {
219
-		$providerConfigs = [
220
-			'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 100],
221
-			'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 1],
222
-			'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 50],
223
-		];
224
-
225
-		$mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
226
-		$this->setupRegistrationContextWithProviders($mockData['registrations']);
227
-		$this->setupAppConfigForAllowedProviders();
228
-
229
-		$providers = $this->searchComposer->getProviders('/test/route', []);
230
-
231
-		$this->assertCount(3, $providers);
232
-		$this->assertProvidersAreSortedByOrder($providers);
233
-
234
-		// Verify correct ordering: provider2 (1), provider3 (50), provider1 (100)
235
-		$this->assertEquals('provider2', $providers[0]['id']);
236
-		$this->assertEquals('provider3', $providers[1]['id']);
237
-		$this->assertEquals('provider1', $providers[2]['id']);
238
-	}
239
-
240
-	public function testProviderIconGeneration(): void {
241
-		$providerConfigs = [
242
-			'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
243
-		];
244
-
245
-		$mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
246
-		$this->setupRegistrationContextWithProviders($mockData['registrations']);
247
-		$this->setupAppConfigForAllowedProviders();
248
-
249
-		$providers = $this->searchComposer->getProviders('/test/route', []);
250
-
251
-		$this->assertCount(1, $providers);
252
-		$this->assertArrayHasKey('icon', $providers[0]);
253
-		$this->assertStringContainsString('/apps/provider1/img/provider1.svg', $providers[0]['icon']);
254
-	}
255
-
256
-	/**
257
-	 * Assert providers array structure and expected sorting
258
-	 */
259
-	private function assertProvidersStructureAndSorting(array $actualProviders, array $expectedProviders): void {
260
-		$this->assertIsArray($actualProviders);
261
-		$this->assertCount(count($expectedProviders), $actualProviders);
262
-
263
-		foreach ($actualProviders as $index => $provider) {
264
-			$this->assertProviderHasRequiredFields($provider);
265
-
266
-			$expected = $expectedProviders[$index];
267
-			$this->assertEquals($expected['id'], $provider['id']);
268
-			$this->assertEquals($expected['name'], $provider['name']);
269
-			$this->assertEquals($expected['appId'], $provider['appId']);
270
-			$this->assertEquals($expected['order'], $provider['order']);
271
-			$this->assertEquals($expected['inAppSearch'], $provider['inAppSearch']);
272
-		}
273
-
274
-		$this->assertProvidersAreSortedByOrder($actualProviders);
275
-	}
276
-
277
-	private function assertProviderHasRequiredFields(array $provider): void {
278
-		$requiredFields = ['id', 'appId', 'name', 'icon', 'order', 'triggers', 'filters', 'inAppSearch'];
279
-		foreach ($requiredFields as $field) {
280
-			$this->assertArrayHasKey($field, $provider, "Provider must have '$field' field");
281
-		}
282
-	}
283
-
284
-	private function assertProvidersAreSortedByOrder(array $providers): void {
285
-		$orders = array_column($providers, 'order');
286
-		$sortedOrders = $orders;
287
-		sort($sortedOrders);
288
-		$this->assertEquals($sortedOrders, $orders, 'Providers should be sorted by order');
289
-	}
29
+    private Coordinator&MockObject $bootstrapCoordinator;
30
+    private ContainerInterface&MockObject $container;
31
+    private IURLGenerator&MockObject $urlGenerator;
32
+    private LoggerInterface&MockObject $logger;
33
+    private IAppConfig&MockObject $appConfig;
34
+    private SearchComposer $searchComposer;
35
+
36
+    protected function setUp(): void {
37
+        parent::setUp();
38
+
39
+        $this->bootstrapCoordinator = $this->createMock(Coordinator::class);
40
+        $this->container = $this->createMock(ContainerInterface::class);
41
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
42
+        $this->logger = $this->createMock(LoggerInterface::class);
43
+        $this->appConfig = $this->createMock(IAppConfig::class);
44
+
45
+        $this->searchComposer = new SearchComposer(
46
+            $this->bootstrapCoordinator,
47
+            $this->container,
48
+            $this->urlGenerator,
49
+            $this->logger,
50
+            $this->appConfig
51
+        );
52
+
53
+        $this->setupUrlGenerator();
54
+    }
55
+
56
+    private function setupUrlGenerator(): void {
57
+        $this->urlGenerator->method('imagePath')
58
+            ->willReturnCallback(function ($appId, $imageName) {
59
+                return "/apps/$appId/img/$imageName";
60
+            });
61
+    }
62
+
63
+    private function setupEmptyRegistrationContext(): void {
64
+        $this->bootstrapCoordinator->expects($this->once())
65
+            ->method('getRegistrationContext')
66
+            ->willReturn(null);
67
+    }
68
+
69
+    private function setupAppConfigForAllowedProviders(array $allowedProviders = []): void {
70
+        $this->appConfig->method('getValueArray')
71
+            ->with('core', 'unified_search.providers_allowed')
72
+            ->willReturn($allowedProviders);
73
+    }
74
+
75
+    /**
76
+     * @param array<string, array{service: string, appId: string, order: int, isInApp?: bool}> $providerConfigs
77
+     * @return array{registrations: ServiceRegistration[], providers: IProvider[]}
78
+     */
79
+    private function createMockProvidersAndRegistrations(array $providerConfigs): array {
80
+        $registrations = [];
81
+        $providers = [];
82
+        $containerMap = [];
83
+
84
+        foreach ($providerConfigs as $providerId => $config) {
85
+            // Create registration mock
86
+            $registration = $this->createMock(ServiceRegistration::class);
87
+            $registration->method('getService')->willReturn($config['service']);
88
+            $registration->method('getAppId')->willReturn($config['appId']);
89
+            $registrations[] = $registration;
90
+
91
+            // Create provider mock
92
+            $providerClass = $config['isInApp'] ?? false ? IInAppSearch::class : IProvider::class;
93
+            $provider = $this->createMock($providerClass);
94
+            $provider->method('getId')->willReturn($providerId);
95
+            $provider->method('getName')->willReturn("Provider $providerId");
96
+            $provider->method('getOrder')->willReturn($config['order']);
97
+
98
+            $providers[$providerId] = $provider;
99
+            $containerMap[] = [$config['service'], $provider];
100
+        }
101
+
102
+        $this->container->expects($this->exactly(count($providerConfigs)))
103
+            ->method('get')
104
+            ->willReturnMap($containerMap);
105
+
106
+        return ['registrations' => $registrations, 'providers' => $providers];
107
+    }
108
+
109
+    private function setupRegistrationContextWithProviders(array $registrations): void {
110
+        $registrationContext = $this->createMock(RegistrationContext::class);
111
+        $registrationContext->method('getSearchProviders')->willReturn($registrations);
112
+
113
+        $this->bootstrapCoordinator->expects($this->once())
114
+            ->method('getRegistrationContext')
115
+            ->willReturn($registrationContext);
116
+    }
117
+
118
+    public function testGetProvidersWithNoRegisteredProviders(): void {
119
+        $this->setupEmptyRegistrationContext();
120
+
121
+        $providers = $this->searchComposer->getProviders('/test/route', []);
122
+
123
+        $this->assertIsArray($providers);
124
+        $this->assertEmpty($providers);
125
+    }
126
+
127
+    public function testSearchWithUnknownProvider(): void {
128
+        $this->setupEmptyRegistrationContext();
129
+
130
+        $user = $this->createMock(IUser::class);
131
+        $query = $this->createMock(ISearchQuery::class);
132
+
133
+        $this->expectException(InvalidArgumentException::class);
134
+        $this->expectExceptionMessage('Provider unknown_provider is unknown');
135
+
136
+        $this->searchComposer->search($user, 'unknown_provider', $query);
137
+    }
138
+
139
+    public function testGetProvidersWithMultipleProviders(): void {
140
+        $providerConfigs = [
141
+            'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
142
+            'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
143
+            'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 15, 'isInApp' => true],
144
+        ];
145
+
146
+        $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
147
+        $this->setupRegistrationContextWithProviders($mockData['registrations']);
148
+        $this->setupAppConfigForAllowedProviders();
149
+
150
+        $providers = $this->searchComposer->getProviders('/test/route', []);
151
+
152
+        $this->assertProvidersStructureAndSorting($providers, [
153
+            ['id' => 'provider2', 'name' => 'Provider provider2', 'appId' => 'app2', 'order' => 5, 'inAppSearch' => false],
154
+            ['id' => 'provider1', 'name' => 'Provider provider1', 'appId' => 'app1', 'order' => 10, 'inAppSearch' => false],
155
+            ['id' => 'provider3', 'name' => 'Provider provider3', 'appId' => 'app3', 'order' => 15, 'inAppSearch' => true],
156
+        ]);
157
+    }
158
+
159
+    public function testGetProvidersWithEmptyAllowedProvidersConfiguration(): void {
160
+        $providerConfigs = [
161
+            'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
162
+            'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
163
+        ];
164
+
165
+        $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
166
+        $this->setupRegistrationContextWithProviders($mockData['registrations']);
167
+        $this->setupAppConfigForAllowedProviders();
168
+
169
+        $providers = $this->searchComposer->getProviders('/test/route', []);
170
+
171
+        $this->assertCount(2, $providers);
172
+        $this->assertProvidersAreSortedByOrder($providers);
173
+        $this->assertEquals('provider2', $providers[0]['id']);
174
+        $this->assertEquals('provider1', $providers[1]['id']);
175
+    }
176
+
177
+    public function testGetProvidersWithAllowedProvidersRestriction(): void {
178
+        $providerConfigs = [
179
+            'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
180
+            'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
181
+            'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 15],
182
+            'provider4' => ['service' => 'provider4_service', 'appId' => 'app4', 'order' => 8],
183
+        ];
184
+
185
+        $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
186
+        $this->setupRegistrationContextWithProviders($mockData['registrations']);
187
+        $this->setupAppConfigForAllowedProviders(['provider1', 'provider3']);
188
+
189
+        $providers = $this->searchComposer->getProviders('/test/route', []);
190
+
191
+        $this->assertProvidersStructureAndSorting($providers, [
192
+            ['id' => 'provider1', 'name' => 'Provider provider1', 'appId' => 'app1', 'order' => 10, 'inAppSearch' => false],
193
+            ['id' => 'provider3', 'name' => 'Provider provider3', 'appId' => 'app3', 'order' => 15, 'inAppSearch' => false],
194
+        ]);
195
+
196
+        // Verify excluded providers are not present
197
+        $providerIds = array_column($providers, 'id');
198
+        $this->assertNotContains('provider2', $providerIds);
199
+        $this->assertNotContains('provider4', $providerIds);
200
+    }
201
+
202
+    public function testGetProvidersFiltersByAllowedProvidersCompletely(): void {
203
+        $providerConfigs = [
204
+            'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
205
+            'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 5],
206
+        ];
207
+
208
+        $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
209
+        $this->setupRegistrationContextWithProviders($mockData['registrations']);
210
+        $this->setupAppConfigForAllowedProviders(['provider_not_exists']);
211
+
212
+        $providers = $this->searchComposer->getProviders('/test/route', []);
213
+
214
+        $this->assertIsArray($providers);
215
+        $this->assertEmpty($providers);
216
+    }
217
+
218
+    public function testGetProvidersWithMixedOrderValues(): void {
219
+        $providerConfigs = [
220
+            'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 100],
221
+            'provider2' => ['service' => 'provider2_service', 'appId' => 'app2', 'order' => 1],
222
+            'provider3' => ['service' => 'provider3_service', 'appId' => 'app3', 'order' => 50],
223
+        ];
224
+
225
+        $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
226
+        $this->setupRegistrationContextWithProviders($mockData['registrations']);
227
+        $this->setupAppConfigForAllowedProviders();
228
+
229
+        $providers = $this->searchComposer->getProviders('/test/route', []);
230
+
231
+        $this->assertCount(3, $providers);
232
+        $this->assertProvidersAreSortedByOrder($providers);
233
+
234
+        // Verify correct ordering: provider2 (1), provider3 (50), provider1 (100)
235
+        $this->assertEquals('provider2', $providers[0]['id']);
236
+        $this->assertEquals('provider3', $providers[1]['id']);
237
+        $this->assertEquals('provider1', $providers[2]['id']);
238
+    }
239
+
240
+    public function testProviderIconGeneration(): void {
241
+        $providerConfigs = [
242
+            'provider1' => ['service' => 'provider1_service', 'appId' => 'app1', 'order' => 10],
243
+        ];
244
+
245
+        $mockData = $this->createMockProvidersAndRegistrations($providerConfigs);
246
+        $this->setupRegistrationContextWithProviders($mockData['registrations']);
247
+        $this->setupAppConfigForAllowedProviders();
248
+
249
+        $providers = $this->searchComposer->getProviders('/test/route', []);
250
+
251
+        $this->assertCount(1, $providers);
252
+        $this->assertArrayHasKey('icon', $providers[0]);
253
+        $this->assertStringContainsString('/apps/provider1/img/provider1.svg', $providers[0]['icon']);
254
+    }
255
+
256
+    /**
257
+     * Assert providers array structure and expected sorting
258
+     */
259
+    private function assertProvidersStructureAndSorting(array $actualProviders, array $expectedProviders): void {
260
+        $this->assertIsArray($actualProviders);
261
+        $this->assertCount(count($expectedProviders), $actualProviders);
262
+
263
+        foreach ($actualProviders as $index => $provider) {
264
+            $this->assertProviderHasRequiredFields($provider);
265
+
266
+            $expected = $expectedProviders[$index];
267
+            $this->assertEquals($expected['id'], $provider['id']);
268
+            $this->assertEquals($expected['name'], $provider['name']);
269
+            $this->assertEquals($expected['appId'], $provider['appId']);
270
+            $this->assertEquals($expected['order'], $provider['order']);
271
+            $this->assertEquals($expected['inAppSearch'], $provider['inAppSearch']);
272
+        }
273
+
274
+        $this->assertProvidersAreSortedByOrder($actualProviders);
275
+    }
276
+
277
+    private function assertProviderHasRequiredFields(array $provider): void {
278
+        $requiredFields = ['id', 'appId', 'name', 'icon', 'order', 'triggers', 'filters', 'inAppSearch'];
279
+        foreach ($requiredFields as $field) {
280
+            $this->assertArrayHasKey($field, $provider, "Provider must have '$field' field");
281
+        }
282
+    }
283
+
284
+    private function assertProvidersAreSortedByOrder(array $providers): void {
285
+        $orders = array_column($providers, 'order');
286
+        $sortedOrders = $orders;
287
+        sort($sortedOrders);
288
+        $this->assertEquals($sortedOrders, $orders, 'Providers should be sorted by order');
289
+    }
290 290
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -55,7 +55,7 @@
 block discarded – undo
55 55
 
56 56
 	private function setupUrlGenerator(): void {
57 57
 		$this->urlGenerator->method('imagePath')
58
-			->willReturnCallback(function ($appId, $imageName) {
58
+			->willReturnCallback(function($appId, $imageName) {
59 59
 				return "/apps/$appId/img/$imageName";
60 60
 			});
61 61
 	}
Please login to merge, or discard this patch.