19declare(strict_types=1);
27use PHPUnit\Framework\Attributes\DataProvider;
45 return [
'language_keys' =>
'de'];
55 $lng_objects_list = [];
56 $fakes_by_obj_id = [];
59 foreach ($language_objects as $language_key => $flags) {
60 $lng_objects_list[] = [
'obj_id' => $obj_id,
'title' => $language_key];
62 is_installed: $flags[
'installed'] ??
true,
63 remove_local_changes_return_value: $flags[
'remove_local_changes_return'] ??
true,
64 modules_with_unwritten_overlay: $flags[
'unwritten_overlay'] ?? [],
69 $lng_objects =
static fn(): array => $lng_objects_list;
72 return [$fakes_by_obj_id, $lng_objects, $obj_language_factory];
77 $rbac = $this->createMock(\ilRbacSystem::class);
78 $rbac->expects($this->once())
79 ->method(
'checkAccessOfUser')
80 ->with(6,
'write', 42)
84 static fn(): array => [],
85 static fn(
int $id) =>
null,
90 $this->assertTrue($activity->isAllowedToPerform(6, [
'language_keys' =>
'de']));
95 $rbac = $this->createStub(\ilRbacSystem::class);
96 $rbac->method(
'checkAccessOfUser')->willReturn(
false);
99 static fn(): array => [],
100 static fn(
int $id) =>
null,
104 $this->assertFalse($activity->isAllowedToPerform(6, [
'language_keys' =>
'de']));
110 $lng_objects =
static function (): array {
111 throw new \LogicException(
'lng_objects must never be called by isAllowedToPerform()');
113 $obj_language_factory =
static function (
int $id) {
114 throw new \LogicException(
'obj_language_factory must never be called by isAllowedToPerform()');
117 $rbac = $this->createStub(\ilRbacSystem::class);
118 $rbac->method(
'checkAccessOfUser')->willReturn(
true);
120 $activity = $this->
createActivity($lng_objects, $obj_language_factory, $rbac);
122 $this->assertTrue($activity->isAllowedToPerform(6, [
'language_keys' =>
'de']));
131 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
132 'language_keys' =>
'de',
135 $this->assertSame([
'de'], $result[
'removed_local_changes_language_keys']);
136 $this->assertSame([], $result[
'invalid_language_file_keys']);
137 $this->assertSame([], $result[
'not_installed_language_keys']);
138 $this->assertSame(1, $fakes[1]->removeLocalChangesCallCount());
148 'de' => [
'unwritten_overlay' => [
'pilot']],
150 'fr' => [
'remove_local_changes_return' =>
false,
'unwritten_overlay' => [
'pilot']],
153 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
154 'language_keys' => [
'de',
'en',
'fr'],
157 $this->assertSame([
'de',
'en'], $result[
'removed_local_changes_language_keys']);
158 $this->assertSame([
'fr'], $result[
'invalid_language_file_keys']);
159 $this->assertSame([
'de'], $result[
'overlay_write_failed_language_keys']);
168 'fr' => [
'remove_local_changes_return' =>
false],
169 'it' => [
'installed' =>
false],
171 $repository = $this->languageObjectProviderMock($lng_objects, $obj_language_factory);
172 $repository->expects($this->once())->method(
'refreshPlugins')->with([
'de',
'en']);
175 ->perform([
'language_keys' => [
'de',
'en',
'fr',
'it']]);
182 'de' => [
'remove_local_changes_return' =>
false],
183 'en' => [
'installed' =>
false],
185 $repository = $this->languageObjectProviderMock($lng_objects, $obj_language_factory);
186 $repository->expects($this->never())->method(
'refreshPlugins');
189 ->perform([
'language_keys' => [
'de',
'en']]);
198 $lng_objects =
static fn(): array => [[
'obj_id' => 1,
'title' =>
'de']];
199 $repository = $this->createMock(RepositoryProvider::class);
200 $repository->method(
'listLanguageObjects')->willReturnCallback($lng_objects);
201 $repository->method(
'isInstalled')->willReturn(
true);
202 $repository->method(
'removeLocalChanges')->willThrowException(
new \RuntimeException(
'db gone'));
203 $repository->expects($this->once())->method(
'logWarning')->with($this->stringContains(
'setup.php update'));
204 $repository->expects($this->never())->method(
'refreshPlugins');
206 $rbac = $this->createStub(\ilRbacSystem::class);
207 $rbac->method(
'checkAccessOfUser')->willReturn(
true);
210 ->maybePerformAs($this->createRealFieldsUiFactory()->input(), 6, [
'language_keys' =>
'de']);
212 $this->assertTrue($result->isError());
213 $this->assertSame(500, $result->error()->getCode());
214 $this->assertSame(
'db gone', $result->error()->getPrevious()?->getMessage());
220 'de' => [
'installed' =>
false],
223 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
224 'language_keys' =>
'de',
227 $this->assertSame([], $result[
'removed_local_changes_language_keys']);
228 $this->assertSame([], $result[
'invalid_language_file_keys']);
229 $this->assertSame([
'de'], $result[
'not_installed_language_keys']);
230 $this->assertSame(0, $fakes[1]->removeLocalChangesCallCount());
237 'de' => [
'remove_local_changes_return' =>
false],
240 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
241 'language_keys' =>
'de',
244 $this->assertSame([], $result[
'removed_local_changes_language_keys']);
245 $this->assertSame([
'de'], $result[
'invalid_language_file_keys']);
246 $this->assertSame([], $result[
'not_installed_language_keys']);
247 $this->assertSame(1, $fakes[1]->removeLocalChangesCallCount());
253 $lng_objects =
static fn(): array => [];
255 $obj_language_factory =
static function (
int $id) use (&$factory_calls) {
256 $factory_calls[] =
$id;
257 throw new \LogicException(
'must never be called for an unknown language key');
260 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
261 'language_keys' =>
'xx',
264 $this->assertSame([], $result[
'removed_local_changes_language_keys']);
265 $this->assertSame([], $result[
'invalid_language_file_keys']);
266 $this->assertSame([
'xx'], $result[
'not_installed_language_keys']);
267 $this->assertSame([], $factory_calls);
275 'fr' => [
'remove_local_changes_return' =>
false],
276 'it' => [
'installed' =>
false],
279 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
280 'language_keys' => [
'de',
'fr',
'it',
'xx'],
283 $this->assertSame([
'de'], $result[
'removed_local_changes_language_keys']);
284 $this->assertSame([
'fr'], $result[
'invalid_language_file_keys']);
285 $this->assertSame([
'it',
'xx'], $result[
'not_installed_language_keys']);
287 $this->assertSame(1, $fakes[1]->removeLocalChangesCallCount(),
'de must have its local changes removed');
288 $this->assertSame(1, $fakes[2]->removeLocalChangesCallCount(),
'fr is installed, so must still be attempted');
289 $this->assertSame(0, $fakes[3]->removeLocalChangesCallCount(),
'not-installed language must never be attempted');
298 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
299 'language_keys' => [
' de, de ',
'de'],
302 $this->assertSame([
'de'], $result[
'removed_local_changes_language_keys']);
303 $this->assertSame(1, $fakes[1]->removeLocalChangesCallCount());
314 $result = $this->
createActivity($lng_objects, $obj_language_factory)->perform([
315 'language_keys' =>
' de, fr ,it',
318 $this->assertSame([
'de',
'fr',
'it'], $result[
'removed_local_changes_language_keys']);
324 'missing language_keys key' => [[]],
325 'empty (only whitespace/commas)' => [[
'language_keys' =>
' , ']],
326 'nested array value' => [[
'language_keys' => [
'de', [
'fr']]]],
327 'non-array, non-string value' => [[
'language_keys' => 42]],
328 'three letters' => [[
'language_keys' =>
'deu']],
329 'one letter' => [[
'language_keys' =>
'd']],
330 'uppercase' => [[
'language_keys' =>
'DE']],
331 'contains a digit' => [[
'language_keys' =>
'de1']],
332 'contains a hyphen' => [[
'language_keys' =>
'de-at']],
336 #[DataProvider('invalidLanguageKeysProvider')]
339 $this->expectException(InvalidInputException::class);
347 $this->expectException(InvalidInputException::class);
349 $this->
createActivity(
static fn(): array => [],
static fn(
int $id) =>
null)->perform(
'not-an-array');
355 $rbac = $this->createMock(\ilRbacSystem::class);
356 $rbac->expects($this->once())
357 ->method(
'checkAccessOfUser')
358 ->with(6,
'write', $this->anything())
366 $obj_language_factory,
368 )->maybePerformAs($this->createRealFieldsUiFactory()->input(), 6, [
'language_keys' =>
'de']);
370 $this->assertTrue($result->isError());
371 $this->assertSame(0, $fakes[1]->removeLocalChangesCallCount());
376 $rbac = $this->createStub(\ilRbacSystem::class);
377 $rbac->method(
'checkAccessOfUser')->willReturn(
true);
385 $obj_language_factory,
387 )->maybePerformAs($this->createRealFieldsUiFactory()->input(), 6, [
'language_keys' =>
'de']);
389 $this->assertFalse($result->isError());
390 $this->assertSame([
'de'], $result->value()[
'removed_local_changes_language_keys']);
391 $this->assertSame(1, $fakes[1]->removeLocalChangesCallCount());
397 $rbac = $this->createStub(\ilRbacSystem::class);
398 $rbac->method(
'checkAccessOfUser')->willReturn(
true);
405 $result = $this->
createActivity($lng_objects, $obj_language_factory, $rbac)
406 ->maybePerformAs($this->createRealFieldsUiFactory()->input(), 6, [
'language_keys' => [
'de',
'fr']]);
408 $this->assertFalse($result->isError());
409 $this->assertSame([
'de',
'fr'], $result->value()[
'removed_local_changes_language_keys']);
410 $this->assertSame(1, $fakes[1]->removeLocalChangesCallCount());
411 $this->assertSame(1, $fakes[2]->removeLocalChangesCallCount());
417 $rbac = $this->createStub(\ilRbacSystem::class);
418 $rbac->method(
'checkAccessOfUser')->willReturn(
true);
420 $lng_objects =
static fn(): array => [];
421 $obj_language_factory =
static fn(
int $id) =>
null;
426 $obj_language_factory,
428 )->maybePerformAs($this->createRealFieldsUiFactory()->input(), 6, [
'language_keys' =>
' , ']);
430 $this->assertTrue($result->isError());
431 $this->assertInstanceOf(InvalidInputException::class, $result->error());
432 $this->assertSame(400, $result->error()->getCode());
438 $rbac = $this->createMock(\ilRbacSystem::class);
439 $rbac->expects($this->never())->method(
'checkAccessOfUser');
442 static fn(): array => [],
443 static fn(
int $id) =>
null,
445 )->maybePerformAs($this->createRealFieldsUiFactory()->input(), 6, []);
447 $this->assertTrue($result->isError());
448 $this->assertInstanceOf(InvalidInputException::class, $result->error());
449 $this->assertNotSame(
'', $result->error()->getMessage());
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Every legacy and repository access of the runtime language activities.
Stand-in for \ilObjLanguage, which needs the global $DIC; injected via RemoveLocalLanguageChanges's $...
\ilObjLanguage cannot be constructed in a unit test (its constructor needs $DIC), so a fake object st...
testRefreshPluginsIsNeverCalledWhenNoLanguagesLocalChangesWereActuallyRemoved()
Without a single removed language refreshPlugins() is not called at all, not even with an empty list.
testPermissionDeniedBeforePerformNeverCallsObjectFactoryOrRemoveLocalChanges()
Denied permission short-circuits before perform(): no object is touched.
testIsAllowedToPerformReturnsFalseWhenRbacDenies()
testCommaSeparatedStringIsNormalizedIntoMultipleLanguageKeys()
testRemoveLocalChangesThrowingIsLoggedWithSetupUpdateAdviceAndSurfacesAsA500ResultError()
A Throwable from removeLocalChanges() itself (not a null return) is logged as a warning naming "setup...
testExistingButNotInstalledLanguageObjectIsReportedAsNotInstalledWithoutCallingRemoveLocalChanges()
testALanguageWithAnUnwrittenOverlayIsListedAdditionallyToBeingRemoved()
A language whose migrated-module overlay could not be written still counts as removed and is also lis...
testMissingLanguageKeysParameterViaMaybePerformAsIsAResultErrorAndNeverChecksPermission()
A missing 'language_keys' fails the required Text field in grind(), before rbac is touched.
testThrowableFromPerformIsTurnedIntoAResultError()
A Throwable from perform() becomes a Result\Error.
testInstalledLanguageWithValidFileHasItsLocalChangesRemoved()
testIsAllowedToPerformNeverTouchesLngObjectsOrObjectFactory()
isAllowedToPerform() is a pure permission check: no object access, no removeLocalChanges().
testIsAllowedToPerformDelegatesToRbacSystemWithWriteAndTheConfiguredRefId()
testInvalidLanguageKeysAreRejected(array $parameters)
testNonArrayParametersAreRejected()
Pins InvalidInputException, not just its parent \InvalidArgumentException, which would pass silently.
testMaybePerformAsAcceptsAnArrayOfLanguageKeysAndRemovesLocalChangesForEachOne()
ilObjLanguageFolderGUI sends 'language_keys' as array of strings; the real grind() pipeline must join...
testUnknownLanguageKeyIsReportedAsNotInstalledWithoutTouchingTheObjectFactory()
A key without a "lng" object is not installed, and the object factory is never invoked for it.
testDuplicateLanguageKeysAcrossStringAndArrayAreDeduplicated()
testInstalledLanguageWhereRemoveLocalChangesFailsIsReportedAsInvalidLanguageFile()
removeLocalChanges() returning false (invalid language file) is reported as invalid_language_file_key...
testRefreshPluginsIsCalledOnceWithExactlyTheRemovedLanguageKeys()
Plugin language files are re-applied once, only for languages whose local changes were actually remov...
testPermissionGrantedPerformsAndReturnsOkResult()
buildFakeLanguageWorld(array $language_objects)
static invalidLanguageKeysProvider()
testMixedBulkRequestPartitionsEachKeyIntoTheCorrectBucket()
Every state in one request lands in its own bucket; removeLocalChanges() only runs for the two instal...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
createActivity(\Closure $lng_objects, \Closure $obj_language_factory, ?\ilRbacSystem $rbac=null, int $language_folder_ref_id=0, ?RepositoryProvider $repository=null,)
trait RemoveLocalLanguageChangesTestFixture