From 6ee966efd7d45dc69bc8e23b401186129fea871b Mon Sep 17 00:00:00 2001 From: Max Inno Date: Sun, 31 May 2026 16:02:16 +0300 Subject: [PATCH 1/4] feat: add Applications API support --- src/CrowdinApiClient/Api/ApplicationApi.php | 94 +++++++++++++++ src/CrowdinApiClient/Crowdin.php | 3 + .../Model/ApplicationData.php | 13 +++ .../Api/ApplicationApiTest.php | 109 ++++++++++++++++++ .../Model/ApplicationDataTest.php | 39 +++++++ 5 files changed, 258 insertions(+) create mode 100644 src/CrowdinApiClient/Api/ApplicationApi.php create mode 100644 src/CrowdinApiClient/Model/ApplicationData.php create mode 100644 tests/CrowdinApiClient/Api/ApplicationApiTest.php create mode 100644 tests/CrowdinApiClient/Model/ApplicationDataTest.php diff --git a/src/CrowdinApiClient/Api/ApplicationApi.php b/src/CrowdinApiClient/Api/ApplicationApi.php new file mode 100644 index 00000000..0afdca8c --- /dev/null +++ b/src/CrowdinApiClient/Api/ApplicationApi.php @@ -0,0 +1,94 @@ +_get($url, ApplicationData::class, $params); + } + + /** + * Add Application Data + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.api.post API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.api.post API Documentation Enterprise + * + * @param string $applicationIdentifier + * @param string $path + * @param array $data + * @return ApplicationData|null + */ + public function addApplicationData(string $applicationIdentifier, string $path, array $data): ?ApplicationData + { + $url = sprintf('applications/%s/api/%s', $applicationIdentifier, $path); + return $this->_post($url, ApplicationData::class, $data); + } + + /** + * Update or Restore Application Data + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.api.put API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.api.put API Documentation Enterprise + * + * @param string $applicationIdentifier + * @param string $path + * @param array $data + * @return ApplicationData|null + */ + public function updateOrRestoreApplicationData(string $applicationIdentifier, string $path, array $data): ?ApplicationData + { + $url = sprintf('applications/%s/api/%s', $applicationIdentifier, $path); + return $this->_put($url, ApplicationData::class, $data); + } + + /** + * Delete Application Data + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.api.delete API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.api.delete API Documentation Enterprise + * + * @param string $applicationIdentifier + * @param string $path + * @return mixed + */ + public function deleteApplicationData(string $applicationIdentifier, string $path) + { + $url = sprintf('applications/%s/api/%s', $applicationIdentifier, $path); + return $this->_delete($url); + } + + /** + * Edit Application Data + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.api.patch API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.api.patch API Documentation Enterprise + * + * @param string $applicationIdentifier + * @param string $path + * @param array $data + * @return ApplicationData|null + */ + public function editApplicationData(string $applicationIdentifier, string $path, array $data): ?ApplicationData + { + $url = sprintf('applications/%s/api/%s', $applicationIdentifier, $path); + return $this->_patch($url, ApplicationData::class, $data); + } +} diff --git a/src/CrowdinApiClient/Crowdin.php b/src/CrowdinApiClient/Crowdin.php index 7ea7616e..61887b3e 100644 --- a/src/CrowdinApiClient/Crowdin.php +++ b/src/CrowdinApiClient/Crowdin.php @@ -52,6 +52,7 @@ * @property \CrowdinApiClient\Api\AiApi|\CrowdinApiClient\Api\Enterprise\AiApi $ai * @property \CrowdinApiClient\Api\Enterprise\FieldApi $field * @property \CrowdinApiClient\Api\StyleGuideApi $styleGuide + * @property \CrowdinApiClient\Api\ApplicationApi $application */ class Crowdin { @@ -126,6 +127,7 @@ class Crowdin 'securityLog', 'ai', 'styleGuide', + 'application', ]; protected $servicesEnterprise = [ @@ -170,6 +172,7 @@ class Crowdin 'ai', 'field', 'styleGuide', + 'application', ]; public function __construct(array $config) diff --git a/src/CrowdinApiClient/Model/ApplicationData.php b/src/CrowdinApiClient/Model/ApplicationData.php new file mode 100644 index 00000000..9f7f6980 --- /dev/null +++ b/src/CrowdinApiClient/Model/ApplicationData.php @@ -0,0 +1,13 @@ + 'value', + 'count' => 1, + ]; + } + + protected function getResponseJson(): string + { + return json_encode([ + 'data' => $this->getResponseData(), + ]); + } + + public function testGetApplicationData(): void + { + $this->mockRequestGet( + '/applications/my-application/api/settings', + $this->getResponseJson() + ); + + $data = $this->crowdin->application->getApplicationData($this->applicationIdentifier, $this->path); + + $this->assertInstanceOf(ApplicationData::class, $data); + $this->assertEquals($this->getResponseData(), $data->getData()); + $this->assertEquals('value', $data->getDataProperty('key')); + $this->assertEquals(1, $data->getDataProperty('count')); + } + + public function testAddApplicationData(): void + { + $requestBody = ['key' => 'new-value']; + + $this->mockRequest([ + 'path' => '/applications/my-application/api/settings', + 'method' => 'post', + 'body' => json_encode($requestBody), + 'response' => $this->getResponseJson(), + ]); + + $data = $this->crowdin->application->addApplicationData( + $this->applicationIdentifier, + $this->path, + $requestBody + ); + + $this->assertInstanceOf(ApplicationData::class, $data); + $this->assertEquals($this->getResponseData(), $data->getData()); + } + + public function testUpdateOrRestoreApplicationData(): void + { + $requestBody = ['key' => 'updated-value']; + + $this->mockRequestPut( + '/applications/my-application/api/settings', + $this->getResponseJson() + ); + + $data = $this->crowdin->application->updateOrRestoreApplicationData( + $this->applicationIdentifier, + $this->path, + $requestBody + ); + + $this->assertInstanceOf(ApplicationData::class, $data); + $this->assertEquals($this->getResponseData(), $data->getData()); + } + + public function testDeleteApplicationData(): void + { + $this->mockRequestDelete('/applications/my-application/api/settings'); + + $this->crowdin->application->deleteApplicationData($this->applicationIdentifier, $this->path); + } + + public function testEditApplicationData(): void + { + $requestBody = ['key' => 'edited-value']; + + $this->mockRequestPatch( + '/applications/my-application/api/settings', + $this->getResponseJson() + ); + + $data = $this->crowdin->application->editApplicationData( + $this->applicationIdentifier, + $this->path, + $requestBody + ); + + $this->assertInstanceOf(ApplicationData::class, $data); + $this->assertEquals($this->getResponseData(), $data->getData()); + } +} diff --git a/tests/CrowdinApiClient/Model/ApplicationDataTest.php b/tests/CrowdinApiClient/Model/ApplicationDataTest.php new file mode 100644 index 00000000..9ac60da4 --- /dev/null +++ b/tests/CrowdinApiClient/Model/ApplicationDataTest.php @@ -0,0 +1,39 @@ +model = new ApplicationData([ + 'key' => 'value', + 'count' => 42, + 'nested' => ['a' => 1], + ]); + } + + public function testGetData(): void + { + $this->assertEquals([ + 'key' => 'value', + 'count' => 42, + 'nested' => ['a' => 1], + ], $this->model->getData()); + } + + public function testGetDataProperty(): void + { + $this->assertEquals('value', $this->model->getDataProperty('key')); + $this->assertEquals(42, $this->model->getDataProperty('count')); + $this->assertEquals(['a' => 1], $this->model->getDataProperty('nested')); + $this->assertNull($this->model->getDataProperty('missing')); + } +} From 3e2fe23ca407c6dfa075ef1943410e8839f0b8fd Mon Sep 17 00:00:00 2001 From: Max Inno Date: Mon, 1 Jun 2026 10:09:50 +0300 Subject: [PATCH 2/4] feat(applications): add support for new endpoints --- src/CrowdinApiClient/Api/ApplicationApi.php | 77 ++++++++ .../Model/ApplicationInstallation.php | 165 ++++++++++++++++++ .../Api/ApplicationApiTest.php | 105 +++++++++++ .../Model/ApplicationInstallationTest.php | 126 +++++++++++++ 4 files changed, 473 insertions(+) create mode 100644 src/CrowdinApiClient/Model/ApplicationInstallation.php create mode 100644 tests/CrowdinApiClient/Model/ApplicationInstallationTest.php diff --git a/src/CrowdinApiClient/Api/ApplicationApi.php b/src/CrowdinApiClient/Api/ApplicationApi.php index 0afdca8c..bafb4751 100644 --- a/src/CrowdinApiClient/Api/ApplicationApi.php +++ b/src/CrowdinApiClient/Api/ApplicationApi.php @@ -5,6 +5,8 @@ namespace CrowdinApiClient\Api; use CrowdinApiClient\Model\ApplicationData; +use CrowdinApiClient\Model\ApplicationInstallation; +use CrowdinApiClient\ModelCollection; /** * Use API to manage custom application data. @@ -76,6 +78,81 @@ public function deleteApplicationData(string $applicationIdentifier, string $pat return $this->_delete($url); } + /** + * List Application Installations + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.installations.getMany API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.installations.getMany API Documentation Enterprise + * + * @param array $params + * integer $params[limit]
+ * integer $params[offset]
+ * integer $params[installedBy]
+ * string $params[orderBy] + * @return ModelCollection + */ + public function listInstallations(array $params = []): ModelCollection + { + return $this->_list('applications/installations', ApplicationInstallation::class, $params); + } + + /** + * Install Application + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.installations.post API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.installations.post API Documentation Enterprise + * + * @param array $data + * string $data[url] required
+ * array $data[permissions]
+ * array $data[modules]
+ * bool $data[assignAgent] + * @return ApplicationInstallation|null + */ + public function installApplication(array $data): ?ApplicationInstallation + { + return $this->_create('applications/installations', ApplicationInstallation::class, $data); + } + + /** + * Get Application Installation + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.installations.get API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.installations.get API Documentation Enterprise + * + * @param string $identifier + * @return ApplicationInstallation|null + */ + public function getInstallation(string $identifier): ?ApplicationInstallation + { + return $this->_get('applications/installations/' . $identifier, ApplicationInstallation::class); + } + + /** + * Delete Application Installation + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.installations.delete API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.installations.delete API Documentation Enterprise + * + * @param string $identifier + * @param bool $force + * @return mixed + */ + public function deleteInstallation(string $identifier, bool $force = false) + { + $params = $force ? ['force' => true] : []; + return $this->_delete('applications/installations/' . $identifier, $params); + } + + /** + * Edit Application Installation + * @link https://developer.crowdin.com/api/v2/#operation/api.applications.installations.patch API Documentation + * @link https://developer.crowdin.com/enterprise/api/v2/#operation/api.applications.installations.patch API Documentation Enterprise + * + * @param ApplicationInstallation $installation + * @return ApplicationInstallation|null + */ + public function updateInstallation(ApplicationInstallation $installation): ?ApplicationInstallation + { + return $this->_update('applications/installations/' . $installation->getIdentifier(), $installation); + } + /** * Edit Application Data * @link https://developer.crowdin.com/api/v2/#operation/api.applications.api.patch API Documentation diff --git a/src/CrowdinApiClient/Model/ApplicationInstallation.php b/src/CrowdinApiClient/Model/ApplicationInstallation.php new file mode 100644 index 00000000..d091dc9a --- /dev/null +++ b/src/CrowdinApiClient/Model/ApplicationInstallation.php @@ -0,0 +1,165 @@ +identifier = (string)$this->getDataProperty('identifier'); + $this->name = (string)$this->getDataProperty('name'); + $this->installedBy = $this->getDataProperty('installedBy'); + $this->description = $this->getDataProperty('description'); + $this->logo = (string)$this->getDataProperty('logo'); + $this->agent = $this->getDataProperty('agent'); + $this->baseUrl = (string)$this->getDataProperty('baseUrl'); + $this->manifestUrl = (string)$this->getDataProperty('manifestUrl'); + $this->createdAt = $this->getDataProperty('createdAt'); + $this->modules = (array)$this->getDataProperty('modules'); + $this->scopes = (array)$this->getDataProperty('scopes'); + $this->permissions = (array)$this->getDataProperty('permissions'); + $this->defaultPermissions = (array)$this->getDataProperty('defaultPermissions'); + $this->limitReached = (bool)$this->getDataProperty('limitReached'); + $this->stringBasedAvailable = (bool)$this->getDataProperty('stringBasedAvailable'); + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return array|null + */ + public function getInstalledBy(): ?array + { + return $this->installedBy; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function getLogo(): string + { + return $this->logo; + } + + /** + * @return array|null + */ + public function getAgent(): ?array + { + return $this->agent; + } + + public function getBaseUrl(): string + { + return $this->baseUrl; + } + + public function getManifestUrl(): string + { + return $this->manifestUrl; + } + + public function getCreatedAt(): ?string + { + return $this->createdAt; + } + + public function getModules(): array + { + return $this->modules; + } + + public function getScopes(): array + { + return $this->scopes; + } + + public function getPermissions(): array + { + return $this->permissions; + } + + public function getDefaultPermissions(): array + { + return $this->defaultPermissions; + } + + public function isLimitReached(): bool + { + return $this->limitReached; + } + + public function isStringBasedAvailable(): bool + { + return $this->stringBasedAvailable; + } + + public function setPermissions(array $permissions): void + { + $this->permissions = $permissions; + } + + public function setModules(array $modules): void + { + $this->modules = $modules; + } +} diff --git a/tests/CrowdinApiClient/Api/ApplicationApiTest.php b/tests/CrowdinApiClient/Api/ApplicationApiTest.php index a3569016..42bdf687 100644 --- a/tests/CrowdinApiClient/Api/ApplicationApiTest.php +++ b/tests/CrowdinApiClient/Api/ApplicationApiTest.php @@ -5,6 +5,8 @@ namespace CrowdinApiClient\Tests\Api; use CrowdinApiClient\Model\ApplicationData; +use CrowdinApiClient\Model\ApplicationInstallation; +use CrowdinApiClient\ModelCollection; class ApplicationApiTest extends AbstractTestApi { @@ -26,6 +28,109 @@ protected function getResponseJson(): string ]); } + protected function getInstallationData(): array + { + return [ + 'identifier' => 'my-app', + 'name' => 'My Application', + 'installedBy' => ['id' => 1, 'username' => 'admin'], + 'description' => 'App description', + 'logo' => '/resources/images/logo.png', + 'agent' => null, + 'baseUrl' => 'https://localhost.dev', + 'manifestUrl' => 'https://localhost.dev/manifest.json', + 'createdAt' => '2023-11-29T10:00:00+00:00', + 'modules' => [], + 'scopes' => ['application'], + 'permissions' => ['user' => ['value' => 'all']], + 'defaultPermissions' => [], + 'limitReached' => false, + 'stringBasedAvailable' => false, + ]; + } + + protected function getInstallationResponseJson(): string + { + return json_encode(['data' => $this->getInstallationData()]); + } + + public function testListInstallations(): void + { + $this->mockRequest([ + 'path' => '/applications/installations', + 'method' => 'get', + 'response' => json_encode([ + 'data' => [ + ['data' => $this->getInstallationData()], + ], + 'pagination' => [['offset' => 0, 'limit' => 25]], + ]), + ]); + + $list = $this->crowdin->application->listInstallations(); + + $this->assertInstanceOf(ModelCollection::class, $list); + $this->assertCount(1, $list); + $this->assertInstanceOf(ApplicationInstallation::class, $list[0]); + $this->assertEquals('my-app', $list[0]->getIdentifier()); + $this->assertEquals('My Application', $list[0]->getName()); + } + + public function testInstallApplication(): void + { + $this->mockRequest([ + 'path' => '/applications/installations', + 'method' => 'post', + 'body' => json_encode(['url' => 'https://localhost.dev/manifest.json']), + 'response' => $this->getInstallationResponseJson(), + ]); + + $installation = $this->crowdin->application->installApplication([ + 'url' => 'https://localhost.dev/manifest.json', + ]); + + $this->assertInstanceOf(ApplicationInstallation::class, $installation); + $this->assertEquals('my-app', $installation->getIdentifier()); + $this->assertEquals('My Application', $installation->getName()); + } + + public function testGetInstallation(): void + { + $this->mockRequestGet( + '/applications/installations/my-app', + $this->getInstallationResponseJson() + ); + + $installation = $this->crowdin->application->getInstallation('my-app'); + + $this->assertInstanceOf(ApplicationInstallation::class, $installation); + $this->assertEquals('my-app', $installation->getIdentifier()); + $this->assertEquals('My Application', $installation->getName()); + } + + public function testDeleteInstallation(): void + { + $this->mockRequestDelete('/applications/installations/my-app'); + + $this->crowdin->application->deleteInstallation('my-app'); + } + + public function testUpdateInstallation(): void + { + $this->mockRequestPatch( + '/applications/installations/my-app', + $this->getInstallationResponseJson() + ); + + $installation = new ApplicationInstallation($this->getInstallationData()); + $installation->setPermissions(['user' => ['value' => 'managers']]); + + $result = $this->crowdin->application->updateInstallation($installation); + + $this->assertInstanceOf(ApplicationInstallation::class, $result); + $this->assertEquals('my-app', $result->getIdentifier()); + } + public function testGetApplicationData(): void { $this->mockRequestGet( diff --git a/tests/CrowdinApiClient/Model/ApplicationInstallationTest.php b/tests/CrowdinApiClient/Model/ApplicationInstallationTest.php new file mode 100644 index 00000000..30651d07 --- /dev/null +++ b/tests/CrowdinApiClient/Model/ApplicationInstallationTest.php @@ -0,0 +1,126 @@ +model = new ApplicationInstallation([ + 'identifier' => 'my-app', + 'name' => 'My Application', + 'installedBy' => ['id' => 1, 'username' => 'admin'], + 'description' => 'App description', + 'logo' => '/resources/images/logo.png', + 'agent' => null, + 'baseUrl' => 'https://localhost.dev', + 'manifestUrl' => 'https://localhost.dev/manifest.json', + 'createdAt' => '2023-11-29T10:00:00+00:00', + 'modules' => [['key' => 'module-1', 'type' => 'custom']], + 'scopes' => ['application'], + 'permissions' => ['user' => ['value' => 'all'], 'project' => ['value' => 'own']], + 'defaultPermissions' => ['user' => ['value' => 'all']], + 'limitReached' => false, + 'stringBasedAvailable' => true, + ]); + } + + public function testGetIdentifier(): void + { + $this->assertEquals('my-app', $this->model->getIdentifier()); + } + + public function testGetName(): void + { + $this->assertEquals('My Application', $this->model->getName()); + } + + public function testGetInstalledBy(): void + { + $this->assertEquals(['id' => 1, 'username' => 'admin'], $this->model->getInstalledBy()); + } + + public function testGetDescription(): void + { + $this->assertEquals('App description', $this->model->getDescription()); + } + + public function testGetLogo(): void + { + $this->assertEquals('/resources/images/logo.png', $this->model->getLogo()); + } + + public function testGetAgent(): void + { + $this->assertNull($this->model->getAgent()); + } + + public function testGetBaseUrl(): void + { + $this->assertEquals('https://localhost.dev', $this->model->getBaseUrl()); + } + + public function testGetManifestUrl(): void + { + $this->assertEquals('https://localhost.dev/manifest.json', $this->model->getManifestUrl()); + } + + public function testGetCreatedAt(): void + { + $this->assertEquals('2023-11-29T10:00:00+00:00', $this->model->getCreatedAt()); + } + + public function testGetModules(): void + { + $this->assertEquals([['key' => 'module-1', 'type' => 'custom']], $this->model->getModules()); + } + + public function testGetScopes(): void + { + $this->assertEquals(['application'], $this->model->getScopes()); + } + + public function testGetPermissions(): void + { + $this->assertEquals( + ['user' => ['value' => 'all'], 'project' => ['value' => 'own']], + $this->model->getPermissions() + ); + } + + public function testGetDefaultPermissions(): void + { + $this->assertEquals(['user' => ['value' => 'all']], $this->model->getDefaultPermissions()); + } + + public function testIsLimitReached(): void + { + $this->assertFalse($this->model->isLimitReached()); + } + + public function testIsStringBasedAvailable(): void + { + $this->assertTrue($this->model->isStringBasedAvailable()); + } + + public function testSetPermissions(): void + { + $newPermissions = ['user' => ['value' => 'managers']]; + $this->model->setPermissions($newPermissions); + $this->assertEquals($newPermissions, $this->model->getPermissions()); + } + + public function testSetModules(): void + { + $newModules = [['key' => 'module-2', 'type' => 'custom']]; + $this->model->setModules($newModules); + $this->assertEquals($newModules, $this->model->getModules()); + } +} From 6cfb856c76adea05d56b7f9f891f6665ecdfaa94 Mon Sep 17 00:00:00 2001 From: Max Inno Date: Sat, 6 Jun 2026 01:00:24 +0300 Subject: [PATCH 3/4] fix: code review --- src/CrowdinApiClient/Api/ApplicationApi.php | 2 +- .../Api/ApplicationApiTest.php | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/CrowdinApiClient/Api/ApplicationApi.php b/src/CrowdinApiClient/Api/ApplicationApi.php index bafb4751..470ee7bd 100644 --- a/src/CrowdinApiClient/Api/ApplicationApi.php +++ b/src/CrowdinApiClient/Api/ApplicationApi.php @@ -160,7 +160,7 @@ public function updateInstallation(ApplicationInstallation $installation): ?Appl * * @param string $applicationIdentifier * @param string $path - * @param array $data + * @param array $data Application-specific key-value payload (free-form, not RFC 6902 patch operations) * @return ApplicationData|null */ public function editApplicationData(string $applicationIdentifier, string $path, array $data): ?ApplicationData diff --git a/tests/CrowdinApiClient/Api/ApplicationApiTest.php b/tests/CrowdinApiClient/Api/ApplicationApiTest.php index 42bdf687..a2cceef0 100644 --- a/tests/CrowdinApiClient/Api/ApplicationApiTest.php +++ b/tests/CrowdinApiClient/Api/ApplicationApiTest.php @@ -171,10 +171,12 @@ public function testUpdateOrRestoreApplicationData(): void { $requestBody = ['key' => 'updated-value']; - $this->mockRequestPut( - '/applications/my-application/api/settings', - $this->getResponseJson() - ); + $this->mockRequest([ + 'path' => '/applications/my-application/api/settings', + 'method' => 'put', + 'body' => json_encode($requestBody), + 'response' => $this->getResponseJson(), + ]); $data = $this->crowdin->application->updateOrRestoreApplicationData( $this->applicationIdentifier, @@ -197,10 +199,12 @@ public function testEditApplicationData(): void { $requestBody = ['key' => 'edited-value']; - $this->mockRequestPatch( - '/applications/my-application/api/settings', - $this->getResponseJson() - ); + $this->mockRequest([ + 'path' => '/applications/my-application/api/settings', + 'method' => 'patch', + 'body' => json_encode($requestBody), + 'response' => $this->getResponseJson(), + ]); $data = $this->crowdin->application->editApplicationData( $this->applicationIdentifier, From b9ae3f448a3a63e19b292c71218e7979731a88c7 Mon Sep 17 00:00:00 2001 From: Max Inno Date: Sat, 6 Jun 2026 01:18:53 +0300 Subject: [PATCH 4/4] fix: code review --- src/CrowdinApiClient/Api/ApplicationApi.php | 3 +++ tests/CrowdinApiClient/Api/ApplicationApiTest.php | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/CrowdinApiClient/Api/ApplicationApi.php b/src/CrowdinApiClient/Api/ApplicationApi.php index 470ee7bd..b547037e 100644 --- a/src/CrowdinApiClient/Api/ApplicationApi.php +++ b/src/CrowdinApiClient/Api/ApplicationApi.php @@ -161,6 +161,9 @@ public function updateInstallation(ApplicationInstallation $installation): ?Appl * @param string $applicationIdentifier * @param string $path * @param array $data Application-specific key-value payload (free-form, not RFC 6902 patch operations) + * string $data[op] required Patch operation to perform (replace, add, remove, test)
+ * string $data[path] required
+ * mixed $data[value] The value to be used within the operation (string, int, bool, or object) * @return ApplicationData|null */ public function editApplicationData(string $applicationIdentifier, string $path, array $data): ?ApplicationData diff --git a/tests/CrowdinApiClient/Api/ApplicationApiTest.php b/tests/CrowdinApiClient/Api/ApplicationApiTest.php index a2cceef0..e0ade29f 100644 --- a/tests/CrowdinApiClient/Api/ApplicationApiTest.php +++ b/tests/CrowdinApiClient/Api/ApplicationApiTest.php @@ -197,19 +197,25 @@ public function testDeleteApplicationData(): void public function testEditApplicationData(): void { - $requestBody = ['key' => 'edited-value']; + $operations = [ + [ + 'op' => 'replace', + 'path' => '/key', + 'value' => 'edited-value' + ], + ]; $this->mockRequest([ 'path' => '/applications/my-application/api/settings', 'method' => 'patch', - 'body' => json_encode($requestBody), + 'body' => json_encode($operations), 'response' => $this->getResponseJson(), ]); $data = $this->crowdin->application->editApplicationData( $this->applicationIdentifier, $this->path, - $requestBody + $operations ); $this->assertInstanceOf(ApplicationData::class, $data);