feat: added User Report Settings Templates support#265
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #265 +/- ##
============================================
+ Coverage 92.75% 93.01% +0.27%
- Complexity 1722 1779 +57
============================================
Files 163 167 +4
Lines 4644 4820 +176
============================================
+ Hits 4307 4483 +176
Misses 337 337 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
cc602d3 to
03cc944
Compare
03cc944 to
2771256
Compare
There was a problem hiding this comment.
Pull request overview
Adds support for User Report Settings Templates to the Crowdin API PHP client, including new models for user-scoped templates and hourly-based rate configurations, plus corresponding API methods and tests.
Changes:
- Introduce
UserReportSettingsTemplatemodel andUserApimethods to list/create/get/update/delete user report settings templates. - Add hourly-specific config models (
HourlyReportSettingsTemplateConfig,HourlyBaseRates,HourlyIndividualRates) and enablehoursas a supported report unit. - Extend
ReportSettingsTemplateConfigwith additional boolean configuration flags and update test coverage accordingly.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/CrowdinApiClient/Model/UserReportSettingsTemplateTest.php | New tests covering user template model hydration, setters, validation, and toArray(). |
| tests/CrowdinApiClient/Model/ReportSettingsTemplateTest.php | Updates fixture/config expectations and inlines assertions for loading. |
| tests/CrowdinApiClient/Model/ReportSettingsTemplateConfigTest.php | Adds coverage for new boolean config flags and updates setter/load assertions. |
| tests/CrowdinApiClient/Model/HourlyReportSettingsTemplateConfigTest.php | New tests for hourly config model (load/set/exception/toArray). |
| tests/CrowdinApiClient/Model/HourlyIndividualRatesTest.php | New tests for hourly individual rates model behavior and validation. |
| tests/CrowdinApiClient/Model/HourlyBaseRatesTest.php | New tests for hourly base rates model behavior and toArray(). |
| tests/CrowdinApiClient/Api/UserApiTest.php | Adds API tests for the new user report settings templates endpoints. |
| src/CrowdinApiClient/Model/UserReportSettingsTemplate.php | New model representing a user report settings template with unit-dependent config. |
| src/CrowdinApiClient/Model/ReportSettingsTemplateConfig.php | Adds new boolean config fields with getters/setters and includes them in toArray(). |
| src/CrowdinApiClient/Model/Report.php | Adds UNIT_HOURS and includes it in supported units. |
| src/CrowdinApiClient/Model/IndividualRates.php | Docblock tightening (string[]) for setLanguageIds() parameter. |
| src/CrowdinApiClient/Model/HourlyReportSettingsTemplateConfig.php | New hourly settings template config model (base + individual rates). |
| src/CrowdinApiClient/Model/HourlyIndividualRates.php | New hourly individual rates model with validation and serialization. |
| src/CrowdinApiClient/Model/HourlyBaseRates.php | New hourly base rates model with serialization. |
| src/CrowdinApiClient/Api/UserApi.php | Adds user report settings templates CRUD methods. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| foreach ($individualRates as $individualRate) { | ||
| if (!$individualRate instanceof HourlyIndividualRates) { | ||
| throw new InvalidArgumentException( | ||
| 'Argument "individualRates" must contain only IndividualRates objects' |
There was a problem hiding this comment.
In setIndividualRates(), the exception message says it must contain only IndividualRates objects, but this method actually requires HourlyIndividualRates instances. This is misleading for callers debugging type errors; update the message (and corresponding test expectation) to mention HourlyIndividualRates.
| 'Argument "individualRates" must contain only IndividualRates objects' | |
| 'Argument "individualRates" must contain only HourlyIndividualRates objects' |
| * @param HourlyReportSettingsTemplateConfig|ReportSettingsTemplateConfig $config | ||
| */ | ||
| public function setConfig($config): void | ||
| { |
There was a problem hiding this comment.
setConfig() accepts any value and assigns it to $this->config, but toArray() assumes the config has toArray(); passing the wrong type will cause a runtime error. Consider validating that $config is an instance of ReportSettingsTemplateConfig or HourlyReportSettingsTemplateConfig and throwing InvalidArgumentException otherwise.
| { | |
| { | |
| if ( | |
| !$config instanceof HourlyReportSettingsTemplateConfig | |
| && !$config instanceof ReportSettingsTemplateConfig | |
| ) { | |
| throw new InvalidArgumentException( | |
| sprintf( | |
| 'Argument "config" must be instance of %s or %s', | |
| HourlyReportSettingsTemplateConfig::class, | |
| ReportSettingsTemplateConfig::class | |
| ) | |
| ); | |
| } |
| ) | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
Changing the unit via setUnit() can put the model into an inconsistent state (e.g., switching to hours while $config is still a ReportSettingsTemplateConfig). Since the constructor chooses config type based on unit, setUnit() should either update/recreate $config accordingly or enforce that the existing config type matches the chosen unit (throwing an exception on mismatch).
| if (isset($this->config)) { | |
| $expectedConfigClass = $unit === Report::UNIT_HOURS | |
| ? HourlyReportSettingsTemplateConfig::class | |
| : ReportSettingsTemplateConfig::class; | |
| if (!($this->config instanceof $expectedConfigClass)) { | |
| throw new InvalidArgumentException( | |
| sprintf( | |
| 'Config type "%s" does not match unit "%s". Expected instance of %s.', | |
| is_object($this->config) ? get_class($this->config) : gettype($this->config), | |
| $unit, | |
| $expectedConfigClass | |
| ) | |
| ); | |
| } | |
| } |
| [ | ||
| 'languageIds' => ['uk', 'en', 'jp'], | ||
| 'userIds' => [8], | ||
| 'hourly' => 0.3, | ||
| ], |
There was a problem hiding this comment.
In testSetData(), HourlyIndividualRates is instantiated with an extra array nesting (new HourlyIndividualRates([ [ ... ] ])). That structure doesn’t match the model’s expected input shape (associative array with languageIds, userIds, hourly) and makes the test pass without actually verifying the intended values. Pass the associative array directly so the test validates real behavior.
| [ | |
| 'languageIds' => ['uk', 'en', 'jp'], | |
| 'userIds' => [8], | |
| 'hourly' => 0.3, | |
| ], | |
| 'languageIds' => ['uk', 'en', 'jp'], | |
| 'userIds' => [8], | |
| 'hourly' => 0.3, |
| /** | ||
| * @dataProvider languageIdsExceptionDataProvider | ||
| */ | ||
| public function testSetLanguageIdsEmptyThrowsException(array $languageIds, string $exceptionMessage): void |
There was a problem hiding this comment.
This test method name mentions only the “empty” case, but the data provider also covers an invalid-type case. Renaming the test to something like testSetLanguageIdsException (or splitting into two tests) would better reflect what’s being asserted.
| public function testSetLanguageIdsEmptyThrowsException(array $languageIds, string $exceptionMessage): void | |
| public function testSetLanguageIdsException(array $languageIds, string $exceptionMessage): void |
No description provided.