diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index 6488198..0000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,91 +0,0 @@
-name: "Build - CI"
-
-on:
- push:
- branches:
- - "master"
-
-jobs:
- coding-standards-php-8:
- name: "[BACKEND] - Coding standards (PHP-8)"
-
- runs-on: "ubuntu-latest"
-
- steps:
- - name: "Checkout"
- uses: "actions/checkout@v2"
-
- - name: "Install PHP"
- uses: "shivammathur/setup-php@v2"
- with:
- coverage: "none"
- php-version: "8.0"
-
- - name: "Cache dependencies"
- uses: "actions/cache@v2"
- with:
- path: "~/.composer/cache"
- key: "php-8.0-composer-${{ hashFiles('**/composer.json') }}"
- restore-keys: "php-8.0-composer-"
-
- - name: "Install dependencies"
- run: "composer install --no-interaction --no-progress"
-
- - name: "PHP - Coding Standard"
- run: "vendor/bin/phing phpcs"
-
- static-analysis-php-8:
- name: "[BACKEND] - Static analysis (PHP-8)"
-
- runs-on: "ubuntu-latest"
-
- steps:
- - name: "Checkout"
- uses: "actions/checkout@v2"
-
- - name: "Install PHP"
- uses: "shivammathur/setup-php@v2"
- with:
- coverage: "none"
- php-version: "8.0"
-
- - name: "Cache dependencies"
- uses: "actions/cache@v2"
- with:
- path: "~/.composer/cache"
- key: "php-8.0-composer-${{ hashFiles('**/composer.json') }}"
- restore-keys: "php-8.0-composer-"
-
- - name: "Install dependencies"
- run: "composer install --no-interaction --no-progress"
-
- - name: "PHPStan"
- run: "vendor/bin/phing phpstan"
-
- tests-php-8:
- name: "[BACKEND] - Tests (PHP-8)"
-
- runs-on: "ubuntu-latest"
-
- steps:
- - name: "Checkout"
- uses: "actions/checkout@v2"
-
- - name: "Install PHP"
- uses: "shivammathur/setup-php@v2"
- with:
- coverage: "none"
- php-version: "8.0"
-
- - name: "Cache dependencies"
- uses: "actions/cache@v2"
- with:
- path: "~/.composer/cache"
- key: "php-8.0-composer-${{ hashFiles('**/composer.json') }}"
- restore-keys: "php-8.0-composer-"
-
- - name: "Install dependencies"
- run: "composer install --no-interaction --no-progress"
-
- - name: "PHPUnit"
- run: "vendor/bin/paratest tests"
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
new file mode 100644
index 0000000..446a885
--- /dev/null
+++ b/.github/workflows/checks.yml
@@ -0,0 +1,91 @@
+name: Checks
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ checks:
+ name: Checks (PHP ${{ matrix.php }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['8.4', '8.5']
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: curl, json, mbstring, simplexml
+ coverage: none
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: |
+ ${{ runner.os }}-php-${{ matrix.php }}-composer-
+
+ - name: Install dependencies
+ run: composer install --no-interaction --no-progress --prefer-dist
+
+ - name: Security audit
+ run: composer security-audit
+
+ - name: Composer normalize
+ run: composer composer-normalize-check
+
+ - name: PHP CodeSniffer
+ run: composer phpcs-check
+
+ - name: Rector
+ run: composer rector-check
+
+ - name: PHPStan
+ run: composer analyse
+
+ - name: Tests
+ run: composer test
+
+ runtime:
+ name: Runtime (PHP ${{ matrix.php }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['8.2', '8.3']
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: curl, json, mbstring, simplexml
+ coverage: none
+
+ - name: Validate composer.json
+ run: composer validate --strict --no-check-lock
+
+ # The development tools (pekral/*, PHPStan 2, Rector 2) require PHP 8.4+, so composer --no-dev
+ # still fails to resolve require-dev here. Resolve a dev-free manifest instead, so this step
+ # verifies exactly what a consumer on PHP 8.2 / 8.3 installs.
+ - name: Verify runtime dependencies resolve
+ run: |
+ php -r '$c = json_decode(file_get_contents("composer.json"), true); unset($c["require-dev"], $c["scripts"], $c["autoload-dev"]); file_put_contents("composer.runtime.json", json_encode($c, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));'
+ COMPOSER=composer.runtime.json composer update --no-interaction --no-progress --prefer-dist --no-plugins
+
+ - name: Lint shipped sources
+ run: find src -name '*.php' -print0 | xargs -0 -n1 php -l
diff --git a/.gitignore b/.gitignore
index 9064738..a3d0bb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,12 @@
.DS_Store
tests/.DS_Store
.phpunit.result.cache
+/.phpunit.cache
composer.lock
+
+# Generated by pekral/ai-olympus (vendor/bin/ai-olympus install)
+/.claude
+/.codex
+/.agents
+/AGENTS.md
+/composer.runtime.*
diff --git a/README.md b/README.md
index 90b8513..7976839 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,10 @@ Jednoduchý cURL client, který se stará o správné složení requestu do syst
[](https://packagist.org/packages/ecomailcz/flexibee-client)
[](https://packagist.org/packages/ecomailcz/flexibee-client)
[](https://packagist.org/packages/ecomailcz/flexibee-client)
-
+
+
+Podporované verze PHP: 8.2, 8.3, 8.4, 8.5.
+
## Sponsored by
[](https://ecomail.cz)
diff --git a/build.xml b/build.xml
deleted file mode 100644
index b88df78..0000000
--- a/build.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/composer.json b/composer.json
index 19b1354..7e2871e 100644
--- a/composer.json
+++ b/composer.json
@@ -10,33 +10,34 @@
"ext-json": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
- "ecomailcz/purl": "^2.0",
- "rakit/validation": "^1.4"
+ "ecomailcz/purl": "^2.0"
},
"require-dev": {
"brianium/paratest": "^6.10.0",
"ergebnis/composer-normalize": "^2.34.0",
- "fzaninotto/faker": "^1.9",
+ "fakerphp/faker": "^1.24",
"mockery/mockery": "^1.6.4",
- "phing/phing": "^2.17.4",
- "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "pekral/ai-olympus": "^0.4",
+ "pekral/phpcs-rules": "dev-master",
+ "pekral/phpstan-rules": "dev-master",
+ "pekral/rector-rules": "dev-master",
"phpstan/extension-installer": "^1.3.1",
- "phpstan/phpstan": "^1.10.26",
- "phpstan/phpstan-deprecation-rules": "^1.1.3",
- "phpstan/phpstan-phpunit": "^1.3.13",
- "phpstan/phpstan-strict-rules": "^1.5.1",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-mockery": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^9.6.10",
- "slevomat/coding-standard": "^6.0 || ^7.2.1"
+ "rector/rector": "^2.8"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
- "EcomailFlexibee\\": [
- "src/"
- ],
"EcomailFlexibeeTest\\": [
"tests/"
+ ],
+ "EcomailFlexibee\\": [
+ "src/"
]
}
},
@@ -47,10 +48,39 @@
},
"config": {
"allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true,
"ergebnis/composer-normalize": true,
- "phpstan/extension-installer": true,
- "dealerdirect/phpcodesniffer-composer-installer": true
+ "pekral/ai-olympus": true,
+ "phpstan/extension-installer": true
},
"sort-packages": true
+ },
+ "scripts": {
+ "ai-olympus:install": "vendor/bin/ai-olympus install --force",
+ "analyse": "vendor/bin/phpstan analyse --memory-limit=2G --no-progress",
+ "build": [
+ "@fix",
+ "@check"
+ ],
+ "check": [
+ "@composer-normalize-check",
+ "@phpcs-check",
+ "@rector-check",
+ "@analyse",
+ "@test"
+ ],
+ "composer-normalize-check": "@composer normalize --dry-run",
+ "fix": [
+ "@composer normalize",
+ "@rector-fix",
+ "@phpcs-fix"
+ ],
+ "phpcs-check": "vendor/bin/phpcs --standard=ruleset.xml src tests",
+ "phpcs-fix": "vendor/bin/phpcbf --standard=ruleset.xml src tests || true",
+ "rector-check": "vendor/bin/rector process --dry-run",
+ "rector-fix": "vendor/bin/rector process",
+ "security-audit": "@composer audit",
+ "test": "vendor/bin/paratest --exclude-group=integration",
+ "test:integration": "vendor/bin/paratest --group=integration"
}
}
diff --git a/phpstan.neon b/phpstan.neon
index 118aa45..5b81967 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,4 +1,8 @@
parameters:
level: max
- checkMissingIterableValueType: false
- treatPhpDocTypesAsCertain: false
+ phpVersion:
+ min: 80200
+ max: 80599
+ paths:
+ - src
+ - tests
diff --git a/phpunit.xml b/phpunit.xml
index 409dcbc..d8dd3e1 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,11 +1,11 @@
-
+
- ../src
+ src
- .
+ tests
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..212fe7a
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,12 @@
+withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
+ // The package supports PHP 8.2+: rules that need a newer PHP version are skipped.
+ ->withPhpVersion(PhpVersion::PHP_82)
+ ->withSets([__DIR__ . '/vendor/pekral/rector-rules/rector.php']);
diff --git a/ruleset.xml b/ruleset.xml
index 029af56..70ba0ce 100644
--- a/ruleset.xml
+++ b/ruleset.xml
@@ -1,197 +1,17 @@
-
-
-
-
-
-
-
+
+
- /resources
+
-
-
-
-
-
+
+
+ src/Config.php
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ src/Client.php
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Client.php b/src/Client.php
index 4bdbb25..adbccd8 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -13,9 +13,15 @@
use EcomailFlexibee\Http\ResponseHydrator;
use EcomailFlexibee\Http\UrlBuilder;
use EcomailFlexibee\Result\EvidenceResult;
+
use function array_filter;
use function array_merge;
use function count;
+use function is_array;
+use function is_int;
+use function is_numeric;
+use function is_scalar;
+use function is_string;
use function mb_strtolower;
use function sprintf;
@@ -23,17 +29,29 @@ class Client
{
protected UrlBuilder $queryBuilder;
+
protected HttpClient $httpClient;
- protected Config $config;
- private ResponseHydrator $responseHydrator;
+ protected Config $config;
- public function __construct(string $url, string $company, string $user, string $password, string $evidence, bool $verifySSLCertificate, ?string $authSessionId = null, bool $debugMode = false)
+ private readonly ResponseHydrator $responseHydrator;
+
+ public function __construct(
+ string $url,
+ string $company,
+ string $user,
+ string $password,
+ string $evidence,
+ bool $verifySSLCertificate,
+ ?string $authSessionId = null,
+ bool $debugMode = false,
+ ?HttpClient $httpClient = null,
+ )
{
$this->config = new Config($url, $company, $user, $password, $evidence, $verifySSLCertificate, $authSessionId, $debugMode);
$this->queryBuilder = new UrlBuilder($this->config);
$this->responseHydrator = new ResponseHydrator($this->config);
- $this->httpClient = new HttpClient();
+ $this->httpClient = $httpClient ?? new HttpClient();
}
public function isAllowedChangesApi(): bool
@@ -101,12 +119,16 @@ public function getCompanies(): Response
*/
public function getCompany(): array
{
+ $companies = $this->getCompanies()->getData()['companies'] ?? [];
$result = array_filter(
- $this->getCompanies()->getData()['companies'],
- fn (array $data): bool => mb_strtolower($data['dbNazev']) === mb_strtolower($this->config->getCompany()),
+ is_array($companies) ? $companies : [],
+ fn (mixed $data): bool => is_array($data)
+ && isset($data['dbNazev'])
+ && is_string($data['dbNazev'])
+ && mb_strtolower($data['dbNazev']) === mb_strtolower($this->config->getCompany()),
);
- if (isset($result['company'])) {
+ if (isset($result['company']) && is_array($result['company'])) {
return $result['company'];
}
@@ -128,8 +150,8 @@ public function getAuthAndRefreshToken(): Response
Method::POST,
[],
[
- 'username' => $this->config->getUser(),
'password' => $this->config->getPassword(),
+ 'username' => $this->config->getUser(),
],
[
'Content-Type: application/x-www-form-urlencoded',
@@ -166,19 +188,19 @@ public function deleteByCode(string $id, bool $dryRun = false): void
}
/**
- * @param array $uriParameters
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
+ * @param array $uriParameters
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
public function findById(int $id, array $uriParameters = []): EvidenceResult
{
try {
return $this->getById($id, $uriParameters);
- } catch (EcomailFlexibeeNoEvidenceResult $exception) {
+ } catch (EcomailFlexibeeNoEvidenceResult) {
return new EvidenceResult([]);
}
}
@@ -205,7 +227,7 @@ public function getByCode(string $code, array $uriParameters = []): EvidenceResu
[],
$this->config,
) ,
- true,
+ throwException: true,
);
}
@@ -230,7 +252,7 @@ public function getById(int $id, array $uriParameters = []): EvidenceResult
[],
$this->config,
),
- true,
+ throwException: true,
);
}
@@ -248,7 +270,7 @@ public function findByCode(string $code, array $uriParameters = []): EvidenceRes
{
try {
return $this->getByCode($code, $uriParameters);
- } catch (EcomailFlexibeeNoEvidenceResult $exception) {
+ } catch (EcomailFlexibeeNoEvidenceResult) {
return new EvidenceResult([]);
}
}
@@ -276,55 +298,39 @@ public function save(array $evidenceData, ?int $id, bool $dryRun = false, array
? array_merge($uriParameters, ['dry-run' => 'true'])
: $uriParameters;
/** @var \EcomailFlexibee\Result\EvidenceResult $response */
- $response = $this->callRequest(Method::PUT, null, $uriParameters, $postData, [])[0];
+ $response = $this->callRequest(Method::PUT, queryFilterOrId: null, uriParameters: $uriParameters, postFields: $postData, headers: [])[0];
$data = $response->getData();
-
- if (
- isset($data['created'])
- && (int) $data['created'] === 0
- && isset($data['updated'])
- && (int) $data['updated'] === 0
- ) {
- $errorMessage = sprintf('(%d) %s', $data['status_code'], $data['message']);
-
- throw new EcomailFlexibeeSaveFailed($errorMessage);
- }
-
- if (isset($data['success']) && $data['success'] !== 'true' && isset($data['message'])) {
- throw new EcomailFlexibeeSaveFailed($data['message']);
- }
+ $this->throwWhenNotSaved($data);
return new FlexibeeResponse(
200,
- null,
- true,
- null,
- count($data),
- null,
- $response->getData(),
- [],
+ version: null,
+ success: true,
+ rowCount: count($data),
+ data: $response->getData(),
+ statistics: [],
);
}
public function getUserRelations(int $objectId): EvidenceResult
{
- return new EvidenceResult(
- $this->getById($objectId, ['relations' => 'uzivatelske-vazby'])->getData()[0]['uzivatelske-vazby'],
- );
+ $relations = $this->getFirstEvidenceItem($objectId, ['relations' => 'uzivatelske-vazby'])['uzivatelske-vazby'] ?? null;
+
+ return new EvidenceResult(is_array($relations) ? $relations : []);
}
public function addUserRelation(int $objectAId, int $objectBId, float $price, int $relationTypeId, ?string $description = null): void
{
- $objectBData = $this->getById($objectBId, [])->getData()[0];
+ $objectBCode = $this->getFirstEvidenceItem($objectBId, [])['kod'] ?? null;
$relationData = [
'id' => $objectAId,
'uzivatelske-vazby' => [
'uzivatelska-vazba' => [
- 'vazbaTyp' => $relationTypeId,
'cena' => $price,
- 'popis' => $description,
'evidenceType' => $this->config->getEvidence(),
- 'object' => sprintf('code:%s', $objectBData['kod']),
+ 'object' => sprintf('code:%s', is_scalar($objectBCode) ? $objectBCode : ''),
+ 'popis' => $description,
+ 'vazbaTyp' => $relationTypeId,
],
],
];
@@ -366,7 +372,7 @@ public function countInEvidence(): int
$this->config,
);
- return $response->getRowCount();
+ return $response->getRowCount();
}
/**
@@ -444,7 +450,7 @@ public function searchInEvidencePaginated(string $query, array $uriParameters):
/**
* @param array $uriParameters
* @param array $postFields
- * @param array $headers
+ * @param array $headers
* @return array<\EcomailFlexibee\Result\EvidenceResult>
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
@@ -453,7 +459,7 @@ public function searchInEvidencePaginated(string $query, array $uriParameters):
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
- public function callRequest(Method $httpMethod, int|string|null $queryFilterOrId, array $uriParameters, array $postFields, array $headers): array
+ public function callRequest(Method $httpMethod, null|int|string $queryFilterOrId, array $uriParameters, array $postFields, array $headers): array
{
$response = $this->httpClient->request(
$this->queryBuilder->createUri($queryFilterOrId, $uriParameters),
@@ -502,4 +508,40 @@ public function findLastInEvidence(bool $fullDetail): Response
);
}
+ /**
+ * @param array $data
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeSaveFailed
+ */
+ private function throwWhenNotSaved(array $data): void
+ {
+ if ($this->isZero($data['created'] ?? null) && $this->isZero($data['updated'] ?? null)) {
+ $statusCode = $data['status_code'] ?? 0;
+ $message = $data['message'] ?? '';
+
+ throw new EcomailFlexibeeSaveFailed(
+ sprintf('(%d) %s', is_int($statusCode) ? $statusCode : 0, is_string($message) ? $message : ''),
+ );
+ }
+
+ if (isset($data['success'], $data['message']) && $data['success'] !== 'true' && is_string($data['message'])) {
+ throw new EcomailFlexibeeSaveFailed($data['message']);
+ }
+ }
+
+ private function isZero(mixed $value): bool
+ {
+ return is_numeric($value) && (int) $value === 0;
+ }
+
+ /**
+ * @param array $uriParameters
+ * @return array
+ */
+ private function getFirstEvidenceItem(int $id, array $uriParameters): array
+ {
+ $item = $this->getById($id, $uriParameters)->getData()[0] ?? null;
+
+ return is_array($item) ? $item : [];
+ }
+
}
diff --git a/src/CompanyClient.php b/src/CompanyClient.php
index efae900..9f26dde 100644
--- a/src/CompanyClient.php
+++ b/src/CompanyClient.php
@@ -4,6 +4,7 @@
namespace EcomailFlexibee;
+use EcomailFlexibee\Http\HttpClient;
use EcomailFlexibee\Http\Method;
use EcomailFlexibee\Http\Response\Response;
@@ -12,7 +13,7 @@ class CompanyClient extends Client
private const EVIDENCE = 'c';
- public function __construct(Config $config)
+ public function __construct(Config $config, ?HttpClient $httpClient = null)
{
parent::__construct(
$config->getUrl(),
@@ -22,14 +23,16 @@ public function __construct(Config $config)
self::EVIDENCE,
$config->verifySSLCertificate(),
$config->getAuthSessionId(),
- );
+ $config->debugMode(),
+ $httpClient,
+ );
}
public function backup(): Response
{
return $this->httpClient->request(
$this->queryBuilder->createBackupUrl(),
- Method::get(Method::GET),
+ Method::GET,
[],
[],
[],
@@ -41,7 +44,7 @@ public function restore(string $companyName, string $data): Response
{
return $this->httpClient->request(
$this->queryBuilder->createRestoreUrl($companyName),
- Method::get(Method::PUT),
+ Method::PUT,
[$data],
[],
[],
diff --git a/src/Config.php b/src/Config.php
index 3e52e29..1d3ce6c 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -4,7 +4,7 @@
namespace EcomailFlexibee;
-final class Config
+final readonly class Config
{
public function __construct(
diff --git a/src/Enum/SearchQueryOperator.php b/src/Enum/SearchQueryOperator.php
index 5924908..da848e4 100644
--- a/src/Enum/SearchQueryOperator.php
+++ b/src/Enum/SearchQueryOperator.php
@@ -4,52 +4,47 @@
namespace EcomailFlexibee\Enum;
-use function array_keys;
-use function array_values;
+use function array_map;
use function implode;
use function preg_match_all;
use function preg_replace;
use function preg_split;
use function str_replace;
+use function strtr;
use function urldecode;
class SearchQueryOperator
{
/**
- * @var array
+ * @var array
*/
private static array $operators = [
- '==' => ' eq ',
+ '!=' => ' neq ',
+ '<' => ' lt ',
'<=' => ' lte ',
'<>' => ' neq ',
- '>=' => ' gte ',
- '!=' => ' neq ',
'=' => ' eq ',
- '<' => ' lt ',
+ '==' => ' eq ',
'>' => ' gt ',
+ '>=' => ' gte ',
];
public static function convertOperatorsInQuery(string $query): string
{
$query = urldecode($query);
- /** @var array $queryExploded */
$queryExploded = preg_split('/\s+/', $query);
- foreach($queryExploded as &$part) {
- $toReplace = [];
+ $converted = array_map(static function (string $part): string {
preg_match_all('/[^\'](?=(?:[^\']*\'[^"]*\'[^\']*|[^\'])*$)/', $part, $matches);
+ $text = implode('', $matches[0]);
- if (isset($matches[0])) {
- $text = implode('', $matches[0]);
- $toReplace[$text] = str_replace(array_keys(self::$operators), array_values(self::$operators), $text);
- }
-
- $part = str_replace(array_keys($toReplace), array_values($toReplace), $part);
- }
+ // strtr() always replaces the longest operator first ("<=" before "<"), independent of key order.
+ return str_replace($text, strtr($text, self::$operators), $part);
+ }, $queryExploded === false ? [] : $queryExploded);
/** @var string $result */
- $result = preg_replace('/\s+/', ' ', implode(' ',$queryExploded));
+ $result = preg_replace('/\s+/', ' ', implode(' ', $converted));
return $result;
}
diff --git a/src/Exception/EcomailFlexibeeInvalidRequestParameter.php b/src/Exception/EcomailFlexibeeInvalidRequestParameter.php
index adee262..d916819 100644
--- a/src/Exception/EcomailFlexibeeInvalidRequestParameter.php
+++ b/src/Exception/EcomailFlexibeeInvalidRequestParameter.php
@@ -5,17 +5,8 @@
namespace EcomailFlexibee\Exception;
use Exception;
-use Rakit\Validation\Validation;
-use Throwable;
-use function implode;
-use const PHP_EOL;
final class EcomailFlexibeeInvalidRequestParameter extends Exception
{
- public function __construct(Validation $validation, ?Throwable $previous = null)
- {
- parent::__construct(implode(PHP_EOL,$validation->errors()->all()), 0, $previous);
- }
-
}
diff --git a/src/Http/CurlOptionsFactory.php b/src/Http/CurlOptionsFactory.php
new file mode 100644
index 0000000..4f9b261
--- /dev/null
+++ b/src/Http/CurlOptionsFactory.php
@@ -0,0 +1,115 @@
+
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
+ */
+ public function create(CurlRequest $request, Config $config): array
+ {
+ if ($request->url === '') {
+ throw new EcomailFlexibeeRequestFail('Request URL must not be empty.');
+ }
+
+ $verifySSLCertificate = $config->verifySSLCertificate();
+ $options = [
+ CURLINFO_HEADER_OUT => $config->debugMode(),
+ CURLOPT_CONNECTTIMEOUT => self::CONNECT_TIMEOUT_SECONDS,
+ CURLOPT_CUSTOMREQUEST => $request->httpMethod->value,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_SSL_VERIFYHOST => $verifySSLCertificate ? 2 : 0,
+ CURLOPT_SSL_VERIFYPEER => $verifySSLCertificate,
+ CURLOPT_TIMEOUT => self::TIMEOUT_SECONDS,
+ CURLOPT_URL => $request->url,
+ CURLOPT_USERAGENT => self::USER_AGENT,
+ ] + $this->createAuthenticationOptions($config);
+
+ if (count($request->postFields) > 0) {
+ $options[CURLOPT_POSTFIELDS] = $this->encodePostFields($request->postFields);
+ }
+
+ if (count($request->queryParameters) > 0) {
+ $options[CURLOPT_POSTFIELDS] = http_build_query($request->queryParameters);
+ }
+
+ $headers = $request->headers;
+
+ if ($config->getAuthSessionId() !== null) {
+ $headers[] = sprintf('X-authSessionId: %s', $config->getAuthSessionId());
+ }
+
+ if (count($headers) !== 0) {
+ $options[CURLOPT_HTTPHEADER] = $headers;
+ }
+
+ return $options;
+ }
+
+ /**
+ * @return array
+ */
+ private function createAuthenticationOptions(Config $config): array
+ {
+ if ($config->getAuthSessionId() !== null) {
+ return [CURLOPT_HTTPAUTH => 0];
+ }
+
+ return [
+ CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
+ CURLOPT_USERPWD => sprintf('%s:%s', $config->getUser(), $config->getPassword()),
+ ];
+ }
+
+ /**
+ * @param array $postFields
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
+ */
+ private function encodePostFields(array $postFields): string
+ {
+ try {
+ return json_encode(['winstrom' => $postFields], JSON_THROW_ON_ERROR);
+ } catch (JsonException $exception) {
+ throw new EcomailFlexibeeRequestFail('Request data cannot be encoded to JSON.', 0, $exception);
+ }
+ }
+
+}
diff --git a/src/Http/CurlRequest.php b/src/Http/CurlRequest.php
new file mode 100644
index 0000000..befe031
--- /dev/null
+++ b/src/Http/CurlRequest.php
@@ -0,0 +1,25 @@
+ $postFields
+ * @param array $queryParameters
+ * @param array $headers
+ */
+ public function __construct(
+ public string $url,
+ public Method $httpMethod,
+ public array $postFields = [],
+ public array $queryParameters = [],
+ public array $headers = [],
+ )
+ {
+ }
+
+}
diff --git a/src/Http/HttpClient.php b/src/Http/HttpClient.php
index e851b94..921cb3c 100644
--- a/src/Http/HttpClient.php
+++ b/src/Http/HttpClient.php
@@ -4,8 +4,10 @@
namespace EcomailFlexibee\Http;
+use CurlHandle;
use EcomailFlexibee\Config;
use EcomailFlexibee\Http\Response\FlexibeeResponse;
+
use function curl_errno;
use function curl_error;
use function curl_exec;
@@ -13,28 +15,29 @@
use function is_string;
use function mb_strlen;
use function trim;
+
use const CURLINFO_HTTP_CODE;
-final class HttpClient
+class HttpClient
{
- private HttpCurlBuilder $httpCurlBuilder;
+ private readonly HttpCurlBuilder $httpCurlBuilder;
public function __construct() {
$this->httpCurlBuilder = new HttpCurlBuilder();
}
- /**
- * @param array $postFields
- * @param array $queryParameters
- * @param array $headers
- * @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
- * @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
- * @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
- * @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
- * @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
- * @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
- */
+ /**
+ * @param array $postFields
+ * @param array $queryParameters
+ * @param array $headers
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeConnectionFail
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
+ */
public function request(string $url, Method $httpMethod, array $postFields, array $queryParameters, array $headers, Config $config): FlexibeeResponse
{
@@ -55,8 +58,19 @@ public function request(string $url, Method $httpMethod, array $postFields, arra
$statusCode,
curl_errno($ch),
$errorMessage,
- $config->debugMode() ? json_encode(curl_getinfo($ch, CURLINFO_HEADER_OUT)) : null
+ $this->getDebugInfo($ch, $config),
);
}
+ private function getDebugInfo(CurlHandle $ch, Config $config): ?string
+ {
+ if (!$config->debugMode()) {
+ return null;
+ }
+
+ $debugInfo = json_encode(curl_getinfo($ch, CURLINFO_HEADER_OUT));
+
+ return $debugInfo === false ? null : $debugInfo;
+ }
+
}
diff --git a/src/Http/HttpCurlBuilder.php b/src/Http/HttpCurlBuilder.php
index 9b42aa0..6bad11c 100644
--- a/src/Http/HttpCurlBuilder.php
+++ b/src/Http/HttpCurlBuilder.php
@@ -6,72 +6,29 @@
use CurlHandle;
use EcomailFlexibee\Config;
-use function count;
+
use function curl_init;
-use function curl_setopt;
-use function http_build_query;
-use function json_encode;
-use function sprintf;
-use const CURLOPT_CUSTOMREQUEST;
-use const CURLOPT_FOLLOWLOCATION;
-use const CURLOPT_HTTPAUTH;
-use const CURLOPT_HTTPHEADER;
-use const CURLOPT_POSTFIELDS;
-use const CURLOPT_RETURNTRANSFER;
-use const CURLOPT_SSL_VERIFYHOST;
-use const CURLOPT_SSL_VERIFYPEER;
-use const CURLOPT_URL;
-use const CURLOPT_USERAGENT;
-use const CURLOPT_USERPWD;
+use function curl_setopt_array;
-final class HttpCurlBuilder
+final readonly class HttpCurlBuilder
{
+ private CurlOptionsFactory $curlOptionsFactory;
+
+ public function __construct()
+ {
+ $this->curlOptionsFactory = new CurlOptionsFactory();
+ }
+
/**
- * @param array $postFields
- * @param array $queryParameters
- * @param array $headers
+ * @param array $postFields
+ * @param array $queryParameters
+ * @param array $headers
*/
public function build(string $url, Method $httpMethod, array $postFields, array $queryParameters, array $headers, Config $config): CurlHandle
{
$ch = curl_init();
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
-
- if ($config->getAuthSessionId() !== null) {
- curl_setopt($ch, CURLOPT_HTTPAUTH, FALSE);
- $headers[] = sprintf('X-authSessionId: %s', $config->getAuthSessionId());
- } else {
- curl_setopt($ch, CURLOPT_HTTPAUTH, TRUE);
- curl_setopt($ch, CURLOPT_USERPWD, sprintf('%s:%s', $config->getUser(), $config->getPassword()));
- }
-
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $httpMethod->value);
- curl_setopt($ch, CURLOPT_USERAGENT, 'Ecomail.cz Flexibee client (https://github.com/Ecomailcz/flexibee-client)');
- $verifySSLCertificate = $config->verifySSLCertificate() && $config->getAuthSessionId() === null;
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifySSLCertificate);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifySSLCertificate);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
- curl_setopt($ch, CURLOPT_TIMEOUT, 25);
-
- if($config->debugMode()) {
- curl_setopt($ch, CURLINFO_HEADER_OUT, true);
- }
-
- if (count($postFields) > 0) {
- curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
- 'winstrom' => $postFields,
- ]));
- }
-
- if (count($queryParameters) > 0) {
- curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($queryParameters));
- }
-
- if (count($headers) !== 0) {
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
- }
+ curl_setopt_array($ch, $this->curlOptionsFactory->create(new CurlRequest($url, $httpMethod, $postFields, $queryParameters, $headers), $config));
return $ch;
}
diff --git a/src/Http/Response/FlexibeeBackupResponse.php b/src/Http/Response/FlexibeeBackupResponse.php
index 346cf4c..c6f7271 100644
--- a/src/Http/Response/FlexibeeBackupResponse.php
+++ b/src/Http/Response/FlexibeeBackupResponse.php
@@ -9,7 +9,7 @@ class FlexibeeBackupResponse extends FlexibeeResponse
public function __construct(string $backupContent)
{
- parent::__construct(200, null, true, null,0,null, [
+ parent::__construct(200, version: null, success: true,rowCount: 0, data: [
'backupContent' => $backupContent,
]);
}
diff --git a/src/Http/Response/FlexibeePdfResponse.php b/src/Http/Response/FlexibeePdfResponse.php
index a93f71d..2429003 100644
--- a/src/Http/Response/FlexibeePdfResponse.php
+++ b/src/Http/Response/FlexibeePdfResponse.php
@@ -9,7 +9,7 @@ class FlexibeePdfResponse extends FlexibeeResponse
public function __construct(string $pdfContent)
{
- parent::__construct(200, null, true, null,0,null, [
+ parent::__construct(200, version: null, success: true,rowCount: 0, data: [
'pdfContent' => $pdfContent,
]);
}
diff --git a/src/Http/Response/FlexibeeResponse.php b/src/Http/Response/FlexibeeResponse.php
index 283bee0..2f0c9a0 100644
--- a/src/Http/Response/FlexibeeResponse.php
+++ b/src/Http/Response/FlexibeeResponse.php
@@ -11,9 +11,18 @@ class FlexibeeResponse implements Response
* FlexibeeResponse constructor.
*
* @param array $data
- * @param array $statistics
+ * @param array $statistics
*/
- public function __construct(private int $statusCode, private ?float $version, private bool $success, private ?string $message = null, private int $rowCount = 0, private ?int $globalVersion = null, private array $data = [], private array $statistics = [])
+ public function __construct(
+ private readonly int $statusCode,
+ private readonly ?float $version,
+ private readonly bool $success,
+ private readonly ?string $message = null,
+ private readonly int $rowCount = 0,
+ private readonly ?int $globalVersion = null,
+ private readonly array $data = [],
+ private readonly array $statistics = [],
+ )
{
}
@@ -46,7 +55,7 @@ public function getData(): array
}
/**
- * @return array
+ * @return array
*/
public function getStatistics(): array
{
diff --git a/src/Http/Response/Response.php b/src/Http/Response/Response.php
index 213f5b2..36a9b10 100644
--- a/src/Http/Response/Response.php
+++ b/src/Http/Response/Response.php
@@ -16,12 +16,12 @@ public function isSuccess(): bool;
public function getStatusCode(): int;
/**
- * @return array
+ * @return array
*/
public function getData(): array;
/**
- * @return array
+ * @return array
*/
public function getStatistics(): array;
diff --git a/src/Http/ResponseFactory.php b/src/Http/ResponseFactory.php
index a769233..c1af413 100644
--- a/src/Http/ResponseFactory.php
+++ b/src/Http/ResponseFactory.php
@@ -13,18 +13,40 @@
use EcomailFlexibee\Http\Response\FlexibeeBackupResponse;
use EcomailFlexibee\Http\Response\FlexibeePdfResponse;
use EcomailFlexibee\Http\Response\FlexibeeResponse;
+
use function implode;
use function in_array;
+use function is_array;
+use function is_numeric;
+use function is_scalar;
+use function is_string;
use function json_decode;
use function mb_stripos;
use function mb_strpos;
+use function reset;
use function sprintf;
+
use const CURLE_OK;
final class ResponseFactory
{
- public static function createFromOutput(string $url, Method $httpMethod, ?string $responseContent, int $statusCode, int $errorNumber, ?string $errorMessage, ?string $curlInfoDebug): FlexibeeResponse
+ private const ERROR_FIELD_LABELS = [
+ ['code', 'code'],
+ ['for', 'for attribute'],
+ ['path', 'path'],
+ ['message', 'message'],
+ ];
+
+ public static function createFromOutput(
+ string $url,
+ Method $httpMethod,
+ ?string $responseContent,
+ int $statusCode,
+ int $errorNumber,
+ ?string $errorMessage,
+ ?string $curlInfoDebug,
+ ): FlexibeeResponse
{
if ($responseContent === null) {
throw new EcomailFlexibeeRequestFail();
@@ -34,124 +56,144 @@ public static function createFromOutput(string $url, Method $httpMethod, ?string
throw new EcomailFlexibeeConnectionFail(sprintf('cURL error (%s): %s', $errorNumber, $errorMessage));
}
- // PDF content
if (mb_strpos($url, '.pdf') !== false) {
return new FlexibeePdfResponse($responseContent);
}
- // Backup content
if ($httpMethod === Method::GET && mb_stripos($url, '/backup') !== false) {
return new FlexibeeBackupResponse($responseContent);
}
- /** @var array|null $data */
- $data = json_decode($responseContent, true);
- $data ??= [];
- $data = $data['winstrom'] ?? $data;
- $results = $data['results'] ?? $data;
-
- $version = null;
- /** @var string|null $message */
- $message = $responseContent;
- $success = false;
- $statistics = [];
- $rowCount = 0;
- $globalVersion = null;
-
- if (isset($data['@version'])) {
- $version = (float) $data['@version'];
- unset($data['@version']);
- }
+ return self::createFromJson($responseContent, $statusCode, $curlInfoDebug);
+ }
- if (isset($data['@rowCount'])) {
- $rowCount = (int) $data['@rowCount'];
- unset($data['@rowCount']);
- }
+ /**
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
+ */
+ private static function createFromJson(string $responseContent, int $statusCode, ?string $curlInfoDebug): FlexibeeResponse
+ {
+ $decoded = json_decode($responseContent, associative: true);
+ $data = is_array($decoded) ? $decoded : [];
+ $data = self::getArray($data, 'winstrom') ?? $data;
+ $results = self::getArray($data, 'results') ?? $data;
+ $message = isset($data['message']) && is_string($data['message']) ? $data['message'] : $responseContent;
+
+ self::throwExceptionForStatusCode($statusCode, $curlInfoDebug);
+
+ if (in_array($statusCode, [500, 400], strict: true)) {
+ self::throwErrorFromResults($results, $statusCode, $message);
+ }
+
+ return new FlexibeeResponse(
+ $statusCode,
+ self::getNumber($data, '@version'),
+ self::isSuccess($data, $statusCode),
+ $message,
+ self::getInt($data, '@rowCount') ?? 0,
+ self::getInt($data, '@globalVersion'),
+ $results,
+ self::getArray($data, 'stats') ?? [],
+ );
+ }
- if (isset($data['@globalVersion'])) {
- $globalVersion = (int) $data['@globalVersion'];
- unset($data['@globalVersion']);
- }
+ /**
+ * @param array $data
+ * @return array|null
+ */
+ private static function getArray(array $data, string $key): ?array
+ {
+ return isset($data[$key]) && is_array($data[$key]) ? $data[$key] : null;
+ }
- if (isset($data['message'])) {
- $message = $data['message'];
- unset($data['message']);
- }
+ /**
+ * @param array $data
+ */
+ private static function getNumber(array $data, string $key): ?float
+ {
+ return isset($data[$key]) && is_numeric($data[$key]) ? (float) $data[$key] : null;
+ }
- if (isset($data['stats'])) {
- $statistics = $data['stats'];
- unset($data['stats']);
- }
+ /**
+ * @param array $data
+ */
+ private static function getInt(array $data, string $key): ?int
+ {
+ return isset($data[$key]) && is_numeric($data[$key]) ? (int) $data[$key] : null;
+ }
+ /**
+ * @param array $data
+ */
+ private static function isSuccess(array $data, int $statusCode): bool
+ {
if (isset($data['success'])) {
- $success = (isset($data['success']) && ($data['success'] === 'true' || $data['success'] === true));
- unset($data['success']);
- } elseif(in_array($statusCode, [200, 201], true)) {
- $success = true;
- }
-
- if ($statusCode === 401) {
- throw new EcomailFlexibeeInvalidAuthorization('Uživatel se musí pro provedení dané operace přihlásit.');
+ return $data['success'] === 'true' || $data['success'] === true;
}
- if ($statusCode === 403) {
- if($curlInfoDebug !== null) {
- throw new EcomailFlexibeeForbidden($curlInfoDebug);
- } else {
- throw new EcomailFlexibeeForbidden('Uživatel na tuto operaci nemá oprávnění. Tato chyba se zobrazí i v případě, že danou operaci neumožňuje licence.');
- }
- }
-
- if ($statusCode === 406) {
- throw new EcomailFlexibeeNotAcceptableRequest('Cílový formát není nad konkrétním zdrojem podporovaný (např. export adresáře jako ISDOC).');
- }
-
- if ($statusCode === 405) {
- throw new EcomailFlexibeeMethodNotAllowed();
- }
+ return in_array($statusCode, [200, 201], strict: true);
+ }
- if (in_array($statusCode, [500, 400], true)) {
- foreach ($results as $resultData) {
- if (!isset($resultData['errors'])) {
- continue;
- }
+ /**
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeForbidden
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidAuthorization
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeMethodNotAllowed
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeNotAcceptableRequest
+ */
+ private static function throwExceptionForStatusCode(int $statusCode, ?string $curlInfoDebug): void
+ {
+ match ($statusCode) {
+ 401 => throw new EcomailFlexibeeInvalidAuthorization('Uživatel se musí pro provedení dané operace přihlásit.'),
+ 403 => throw new EcomailFlexibeeForbidden(
+ $curlInfoDebug ?? 'Uživatel na tuto operaci nemá oprávnění. Tato chyba se zobrazí i v případě, že danou operaci neumožňuje licence.',
+ ),
+ 405 => throw new EcomailFlexibeeMethodNotAllowed(),
+ 406 => throw new EcomailFlexibeeNotAcceptableRequest(
+ 'Cílový formát není nad konkrétním zdrojem podporovaný (např. export adresáře jako ISDOC).',
+ ),
+ default => null,
+ };
+ }
+ /**
+ * @param array $results
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
+ */
+ private static function throwErrorFromResults(array $results, int $statusCode, string $message): never
+ {
+ foreach ($results as $resultData) {
+ if (is_array($resultData) && isset($resultData['errors']) && is_array($resultData['errors'])) {
self::throwErrorMessage($resultData['errors'], $statusCode);
}
-
- throw new EcomailFlexibeeRequestFail($message);
}
- return new FlexibeeResponse($statusCode, $version, $success, $message, $rowCount, $globalVersion, $results, $statistics);
+ throw new EcomailFlexibeeRequestFail($message);
}
/**
- * @param array $errors
+ * @param array $errors
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
private static function throwErrorMessage(array $errors, int $statusCode): void
{
- foreach ($errors as $error) {
- $messageLines = [];
+ $error = reset($errors);
- if (isset($error['code'])) {
- $messageLines[] = sprintf('code: %s',$error['code']);
- }
-
- if (isset($error['for'])) {
- $messageLines[] = sprintf('for attribute: %s', $error['for']);
- }
+ if (!is_array($error)) {
+ return;
+ }
- if (isset($error['path'])) {
- $messageLines[] = sprintf('path: %s',$error['path']);
- }
+ $messageLines = [];
- if (isset($error['message'])) {
- $messageLines[] = sprintf('message: %s', $error['message']);
+ foreach (self::ERROR_FIELD_LABELS as [$field, $label]) {
+ if (isset($error[$field]) && is_scalar($error[$field])) {
+ $messageLines[] = sprintf('%s: %s', $label, $error[$field]);
}
-
- throw new EcomailFlexibeeRequestFail(implode("\n", $messageLines), $statusCode);
}
+
+ throw new EcomailFlexibeeRequestFail(implode("\n", $messageLines), $statusCode);
}
}
diff --git a/src/Http/ResponseHydrator.php b/src/Http/ResponseHydrator.php
index e7becaa..9f5c2d7 100644
--- a/src/Http/ResponseHydrator.php
+++ b/src/Http/ResponseHydrator.php
@@ -9,13 +9,17 @@
use EcomailFlexibee\Exception\EcomailFlexibeeRequestFail;
use EcomailFlexibee\Http\Response\Response;
use EcomailFlexibee\Result\EvidenceResult;
+
+use function array_filter;
use function array_map;
use function count;
+use function is_array;
+use function is_string;
class ResponseHydrator
{
- public function __construct(private Config $config)
+ public function __construct(private readonly Config $config)
{
}
@@ -25,95 +29,129 @@ public function __construct(private Config $config)
*/
public function convertResponseToEvidenceResults(Response $response): array
{
- $data = $response->getData();
- $hasErrors = isset($data[0], $data[0]['errors']);
+ $data = $this->getDataOrThrowRequestFail($response);
+ $evidenceData = $this->getEvidenceData($data);
- if ($hasErrors || (isset($data['success']) && $data['success'] === 'false')) {
+ return $evidenceData === null
+ ? [new EvidenceResult($this->getDataOrStatistics($response, $data))]
+ : $this->createEvidenceResults($evidenceData);
+ }
- if ($hasErrors && !isset($data['message'])) {
- $data['message'] = '';
+ /**
+ * @return array
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
+ */
+ public function convertResponseToPaginatedEvidenceResults(Response $response): array
+ {
+ $data = $this->getDataOrThrowRequestFail($response);
+ $evidenceData = $this->getEvidenceData($data);
- foreach ($data[0]['errors'] as $errors) {
- $data['message'] .= "\n" . $errors['message'];
- }
- }
+ return [
+ 'data' => $evidenceData === null
+ ? [new EvidenceResult($this->getDataOrStatistics($response, $data))]
+ : $this->createEvidenceResults($evidenceData),
+ 'row_count' => $response->getRowCount(),
+ ];
+ }
- throw new EcomailFlexibeeRequestFail($data['message'], $response->getStatusCode());
- }
+ public function convertResponseToEvidenceResult(Response $response, bool $throwException): EvidenceResult
+ {
+ $data = $response->getData();
+
+ $evidenceData = $this->getEvidenceData($data);
- if (!isset($data[$this->config->getEvidence()])) {
- if (count($data) === 0) {
- $data = $response->getStatistics();
- $data['status_code'] = $response->getStatusCode();
- $data['message'] = $response->getMessage();
- $data['version'] = $response->getVersion();
- $data['row_count'] = $response->getRowCount();
+ if ($response->getStatusCode() === 404 || $evidenceData === null) {
+ if ($throwException) {
+ throw new EcomailFlexibeeNoEvidenceResult();
}
- return [new EvidenceResult($data)];
+ return count($data) !== 0
+ ? new EvidenceResult($data)
+ : new EvidenceResult([]);
}
- return array_map(static fn (array $data) => new EvidenceResult($data), $data[$this->config->getEvidence()]);
+ return new EvidenceResult($evidenceData);
}
/**
* @return array
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
*/
- public function convertResponseToPaginatedEvidenceResults(Response $response): array
+ private function getDataOrThrowRequestFail(Response $response): array
{
$data = $response->getData();
- $hasErrors = isset($data[0], $data[0]['errors']);
+ $errors = isset($data[0]) && is_array($data[0]) && isset($data[0]['errors']) && is_array($data[0]['errors'])
+ ? $data[0]['errors']
+ : null;
- if ($hasErrors || (isset($data['success']) && $data['success'] === 'false')) {
+ if ($errors === null && (!isset($data['success']) || $data['success'] !== 'false')) {
+ return $data;
+ }
- if ($hasErrors && !isset($data['message'])) {
- $data['message'] = '';
+ $message = $data['message'] ?? null;
- foreach ($data[0]['errors'] as $errors) {
- $data['message'] .= "\n" . $errors['message'];
- }
- }
+ throw new EcomailFlexibeeRequestFail(
+ is_string($message) ? $message : $this->createErrorMessage($errors ?? []),
+ $response->getStatusCode(),
+ );
+ }
- throw new EcomailFlexibeeRequestFail($data['message'], $response->getStatusCode());
- }
+ /**
+ * @param array $errors
+ */
+ private function createErrorMessage(array $errors): string
+ {
+ $message = '';
- if (!isset($data[$this->config->getEvidence()])) {
- if (count($data) === 0) {
- $data = $response->getStatistics();
- $data['status_code'] = $response->getStatusCode();
- $data['message'] = $response->getMessage();
- $data['version'] = $response->getVersion();
- $data['row_count'] = $response->getRowCount();
+ foreach ($errors as $error) {
+ if (is_array($error) && isset($error['message']) && is_string($error['message'])) {
+ $message .= "\n" . $error['message'];
}
-
- return [
- 'row_count' => $response->getRowCount(),
- 'data' => [new EvidenceResult($data)],
- ];
}
- return [
- 'row_count' => $response->getRowCount(),
- 'data' => array_map(static fn (array $data) => new EvidenceResult($data), $data[$this->config->getEvidence()]),
- ];
+ return $message;
}
- public function convertResponseToEvidenceResult(Response $response, bool $throwException): EvidenceResult
+ /**
+ * @param array $data
+ * @return array
+ */
+ private function getDataOrStatistics(Response $response, array $data): array
{
- $data = $response->getData();
+ if (count($data) !== 0) {
+ return $data;
+ }
- if ($response->getStatusCode() === 404 || !isset($data[$this->config->getEvidence()])) {
- if ($throwException) {
- throw new EcomailFlexibeeNoEvidenceResult();
- }
+ $data = $response->getStatistics();
+ $data['status_code'] = $response->getStatusCode();
+ $data['message'] = $response->getMessage();
+ $data['version'] = $response->getVersion();
+ $data['row_count'] = $response->getRowCount();
- return count($data) !== 0
- ? new EvidenceResult($data)
- : new EvidenceResult([]);
- }
+ return $data;
+ }
- return new EvidenceResult($data[$this->config->getEvidence()]);
+ /**
+ * @param array $data
+ * @return array|null
+ */
+ private function getEvidenceData(array $data): ?array
+ {
+ $evidenceData = $data[$this->config->getEvidence()] ?? null;
+
+ return is_array($evidenceData) ? $evidenceData : null;
+ }
+
+ /**
+ * @param array $evidenceData
+ * @return array<\EcomailFlexibee\Result\EvidenceResult>
+ */
+ private function createEvidenceResults(array $evidenceData): array
+ {
+ return array_map(
+ static fn (array $item): EvidenceResult => new EvidenceResult($item),
+ array_filter($evidenceData, is_array(...)),
+ );
}
}
diff --git a/src/Http/UrlBuilder.php b/src/Http/UrlBuilder.php
index 518a214..9ba122b 100644
--- a/src/Http/UrlBuilder.php
+++ b/src/Http/UrlBuilder.php
@@ -11,6 +11,7 @@
use Purl\Path;
use Purl\Query;
use Purl\Url;
+
use function array_merge;
use function http_build_query;
use function sprintf;
@@ -18,11 +19,11 @@
class UrlBuilder extends Url
{
- private string $company;
+ private readonly string $company;
- private string $evidence;
+ private readonly string $evidence;
- private ParameterValidator $validator;
+ private readonly ParameterValidator $validator;
public function __construct(Config $config, ?ParserInterface $parser = null)
{
@@ -148,7 +149,7 @@ public function getUrl(): string
/**
* @param array $uriParams
*/
- public function createUri(int|string|null $filterQueryOrId, array $uriParams): string
+ public function createUri(null|int|string $filterQueryOrId, array $uriParams): string
{
if ($filterQueryOrId === null) {
$this->setPath($this->buildPathForOnlyEvidence());
diff --git a/src/Result/EvidenceResult.php b/src/Result/EvidenceResult.php
index 3de4133..b6b9895 100644
--- a/src/Result/EvidenceResult.php
+++ b/src/Result/EvidenceResult.php
@@ -4,18 +4,18 @@
namespace EcomailFlexibee\Result;
-final class EvidenceResult
+final readonly class EvidenceResult
{
/**
- * @param array $data
+ * @param array $data
*/
public function __construct(private array $data)
{
}
/**
- * @return array
+ * @return array
*/
public function getData(): array
{
diff --git a/src/Validator/ParameterValidator.php b/src/Validator/ParameterValidator.php
index aa21153..2d66dd8 100644
--- a/src/Validator/ParameterValidator.php
+++ b/src/Validator/ParameterValidator.php
@@ -5,30 +5,25 @@
namespace EcomailFlexibee\Validator;
use EcomailFlexibee\Exception\EcomailFlexibeeInvalidRequestParameter;
-use Rakit\Validation\Validator;
-final class ParameterValidator extends Validator
+use function mb_strlen;
+use function mb_strtoupper;
+
+final class ParameterValidator
{
+ private const CODE_MAX_LENGTH = 20;
+
+ /**
+ * @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidRequestParameter
+ */
public function validateFlexibeeRequestCodeParameter(string $code): void
{
- $validation = $this->make(
- [
- 'code' => $code,
- ],
- [
- 'code' => 'max:20|uppercase',
- ],
- [
- 'code' => 'Parameter code must have a maximum of 20 characters and must be an uppercase',
- ],
- );
-
- $validation->validate();
-
- if ($validation->fails()) {
- throw new EcomailFlexibeeInvalidRequestParameter($validation);
+ if (mb_strlen($code) <= self::CODE_MAX_LENGTH && mb_strtoupper($code) === $code) {
+ return;
}
+
+ throw new EcomailFlexibeeInvalidRequestParameter('Parameter code must have a maximum of 20 characters and must be an uppercase');
}
}
diff --git a/tests/ClientOfflineTest.php b/tests/ClientOfflineTest.php
new file mode 100644
index 0000000..389847d
--- /dev/null
+++ b/tests/ClientOfflineTest.php
@@ -0,0 +1,212 @@
+ 'DEMO', 'nazev' => 'Demo s.r.o.'];
+ $client = $this->createClient($this->respondWith(['companies' => ['company' => $company]]));
+
+ Assert::assertSame($company, $client->getCompany());
+ }
+
+ public function testGetCompanyThrowsWhenTheCompanyIsMissing(): void
+ {
+ $client = $this->createClient($this->respondWith(['companies' => ['company' => ['dbNazev' => 'other']]]));
+
+ $this->expectException(EcomailFlexibeeNoEvidenceResult::class);
+
+ $client->getCompany();
+ }
+
+ public function testGetAuthAndRefreshTokenPostsCredentials(): void
+ {
+ $httpClient = Mockery::mock(HttpClient::class);
+ $httpClient->shouldReceive('request')
+ ->once()
+ ->with(
+ Mockery::type('string'),
+ Method::POST,
+ [],
+ ['password' => 'pass', 'username' => 'user'],
+ ['Content-Type: application/x-www-form-urlencoded'],
+ Mockery::type(ClientConfig::class),
+ )
+ ->andReturn($this->createResponse(['authSessionId' => 'abc']));
+
+ Assert::assertSame(['authSessionId' => 'abc'], $this->createClient($httpClient)->getAuthAndRefreshToken()->getData());
+ }
+
+ public function testGetByIdAndGetByCodeReturnTheEvidence(): void
+ {
+ $client = $this->createClient($this->respondWith(['adresar' => [['id' => '1', 'kod' => 'A']]]));
+
+ Assert::assertSame([['id' => '1', 'kod' => 'A']], $client->getById(1)->getData());
+ Assert::assertSame([['id' => '1', 'kod' => 'A']], $client->getByCode('A')->getData());
+ }
+
+ public function testFindByIdAndFindByCodeReturnEmptyResultWhenMissing(): void
+ {
+ $client = $this->createClient($this->respondWith([], statusCode: 404));
+
+ Assert::assertSame([], $client->findById(1)->getData());
+ Assert::assertSame([], $client->findByCode('A')->getData());
+ }
+
+ public function testSaveSendsEvidenceDataAndReturnsTheResult(): void
+ {
+ $httpClient = Mockery::mock(HttpClient::class);
+ $httpClient->shouldReceive('request')
+ ->once()
+ ->with(
+ Mockery::pattern('/dry-run=true/'),
+ Method::PUT,
+ ['adresar' => ['kod' => 'A', 'id' => 5]],
+ [],
+ [],
+ Mockery::type(ClientConfig::class),
+ )
+ ->andReturn($this->createResponse([['id' => '5']]));
+
+ $response = $this->createClient($httpClient)->save(['kod' => 'A'], id: 5, dryRun: true);
+
+ Assert::assertTrue($response->isSuccess());
+ Assert::assertSame(1, $response->getRowCount());
+ Assert::assertSame([['id' => '5']], $response->getData());
+ }
+
+ public function testSaveThrowsWhenNothingWasCreatedOrUpdated(): void
+ {
+ $response = new FlexibeeResponse(
+ 200,
+ version: null,
+ success: true,
+ message: 'Nothing saved',
+ rowCount: 0,
+ data: [],
+ statistics: ['created' => '0', 'updated' => '0'],
+ );
+ $client = $this->createClient($this->respondWithResponse($response));
+
+ $this->expectException(EcomailFlexibeeSaveFailed::class);
+ $this->expectExceptionMessage('(200) Nothing saved');
+
+ $client->save(['kod' => 'A'], id: null);
+ }
+
+ public function testSaveThrowsWhenTheResultIsNotSuccessful(): void
+ {
+ $client = $this->createClient($this->respondWith(['message' => 'Invalid data', 'success' => 'no']));
+
+ $this->expectException(EcomailFlexibeeSaveFailed::class);
+ $this->expectExceptionMessage('Invalid data');
+
+ $client->save(['kod' => 'A'], id: null);
+ }
+
+ public function testGetUserRelationsReturnsTheRelations(): void
+ {
+ $client = $this->createClient($this->respondWith(['adresar' => [['uzivatelske-vazby' => [['id' => '9']]]]]));
+
+ Assert::assertSame([['id' => '9']], $client->getUserRelations(1)->getData());
+ }
+
+ public function testGetUserRelationsReturnsEmptyResultWithoutRelations(): void
+ {
+ $client = $this->createClient($this->respondWith(['adresar' => [['id' => '1']]]));
+
+ Assert::assertSame([], $client->getUserRelations(1)->getData());
+ }
+
+ public function testAddUserRelationLinksTheObjectByCode(): void
+ {
+ $httpClient = Mockery::mock(HttpClient::class);
+ $httpClient->shouldReceive('request')
+ ->once()
+ ->with(Mockery::type('string'), Method::GET, [], [], [], Mockery::type(ClientConfig::class))
+ ->andReturn($this->createResponse(['adresar' => [['kod' => 'B']]]));
+ $httpClient->shouldReceive('request')
+ ->once()
+ ->with(
+ Mockery::type('string'),
+ Method::PUT,
+ Mockery::on(static fn (array $postFields): bool => $postFields === [
+ 'adresar' => [
+ 'id' => 1,
+ 'uzivatelske-vazby' => [
+ 'uzivatelska-vazba' => [
+ 'cena' => 10.5,
+ 'evidenceType' => 'adresar',
+ 'object' => 'code:B',
+ 'popis' => 'note',
+ 'vazbaTyp' => 3,
+ ],
+ ],
+ ],
+ ]),
+ [],
+ [],
+ Mockery::type(ClientConfig::class),
+ )
+ ->andReturn($this->createResponse([['id' => '1']]));
+
+ $this->createClient($httpClient)->addUserRelation(1, 2, 10.5, 3, 'note');
+ }
+
+ public function testCountInEvidenceReturnsTheRowCount(): void
+ {
+ $response = new FlexibeeResponse(200, version: null, success: true, rowCount: 12, data: []);
+
+ Assert::assertSame(12, $this->createClient($this->respondWithResponse($response))->countInEvidence());
+ }
+
+ private function createClient(HttpClient $httpClient): Client
+ {
+ return new Client('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: true, httpClient: $httpClient);
+ }
+
+ /**
+ * @param array $data
+ */
+ private function respondWith(array $data, int $statusCode = 200): HttpClient&MockInterface
+ {
+ return $this->respondWithResponse($this->createResponse($data, $statusCode));
+ }
+
+ private function respondWithResponse(FlexibeeResponse $response): HttpClient&MockInterface
+ {
+ $httpClient = Mockery::mock(HttpClient::class);
+ $httpClient->shouldReceive('request')->andReturn($response);
+
+ return $httpClient;
+ }
+
+ /**
+ * @param array $data
+ */
+ private function createResponse(array $data, int $statusCode = 200): FlexibeeResponse
+ {
+ return new FlexibeeResponse($statusCode, version: null, success: true, rowCount: 0, data: $data);
+ }
+
+}
diff --git a/tests/ClientTest.php b/tests/ClientTest.php
index 22741b1..5ae2338 100644
--- a/tests/ClientTest.php
+++ b/tests/ClientTest.php
@@ -9,35 +9,32 @@
use EcomailFlexibee\Exception\EcomailFlexibeeNoEvidenceResult;
use EcomailFlexibee\Exception\EcomailFlexibeeRequestFail;
use EcomailFlexibee\Http\Method;
-use EcomailFlexibee\Result\EvidenceResult;
use Faker\Factory;
use Faker\Generator;
+use Iterator;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
+
use function count;
use function mb_substr;
use function uniqid;
+/**
+ * @group integration
+ */
final class ClientTest extends TestCase
{
private Client $client;
- private Generator $faker;
-
- public function setUp(): void
- {
- parent::setUp();
- $this->faker = Factory::create();
- $this->client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, Config::EVIDENCE, false, null);
- }
+ private Generator $faker;
public function testGetCompanies(): void
{
/** @var array $companies */
$companies = $this->client->getCompanies()->getData();
Assert::assertArrayHasKey('companies', $companies);
- Assert::assertTrue(count($companies) > 0);
+ Assert::assertNotEmpty($companies['companies']);
}
public function testGetCompany(): void
@@ -55,7 +52,7 @@ public function testGetLoginFormUrl(): void
public function testInvalidAuthorization(): void
{
- $client = new Client(Config::HOST, Config::COMPANY, 'xxx', 'xxx', Config::EVIDENCE, false, null);
+ $client = new Client(Config::HOST, Config::COMPANY, 'xxx', 'xxx', Config::EVIDENCE, verifySSLCertificate: false);
$this->expectException(EcomailFlexibeeInvalidAuthorization::class);
$client->findById($this->faker->numberBetween());
}
@@ -66,7 +63,15 @@ public function testGetAuthTokenAndMakeSuccessCallWithSessionAuthId(): void
Assert::assertArrayHasKey('refreshToken', $authToken);
Assert::assertArrayHasKey('authSessionId', $authToken);
Assert::assertArrayHasKey('csrfToken', $authToken);
- $client = new Client(Config::HOST, Config::COMPANY, 'xxx', 'xxx', Config::EVIDENCE, false, $authToken['authSessionId']);
+ $client = new Client(
+ Config::HOST,
+ Config::COMPANY,
+ 'xxx',
+ 'xxx',
+ Config::EVIDENCE,
+ verifySSLCertificate: false,
+ authSessionId: $this->getString($authToken, 'authSessionId'),
+ );
Assert::assertNotEmpty($client->allInEvidence());
}
@@ -77,11 +82,12 @@ public function testCount(): void
public function testInvalidGetAuthToken(): void
{
- $client = new Client(Config::HOST, Config::COMPANY, 'xxx', 'xxx', Config::EVIDENCE, false, null);
+ $client = new Client(Config::HOST, Config::COMPANY, 'xxx', 'xxx', Config::EVIDENCE, verifySSLCertificate: false);
$flexibeeResponse = $client->getAuthAndRefreshToken();
Assert::assertFalse($flexibeeResponse->isSuccess());
$data = $flexibeeResponse->getData();
Assert::assertArrayHasKey('errors', $data);
+ Assert::assertIsArray($data['errors']);
Assert::assertArrayHasKey('reason', $data['errors']);
}
@@ -91,7 +97,7 @@ public function testGetChanges(): void
return;
}
- $response = $this->client->getAllApiChanges(null);
+ $response = $this->client->getAllApiChanges(fromVersion: null);
Assert::assertTrue($response->isSuccess());
$data = $response->getData();
Assert::assertArrayHasKey('changes', $data);
@@ -99,18 +105,21 @@ public function testGetChanges(): void
Assert::assertTrue($response->isSuccess());
}
- public function testCRUDForCustomIds(): void
+ public function testCrudForCustomIds(): void
{
$evidenceData = [
'nazev' => $this->faker->firstName,
];
- $id = (int) $this->client->save($evidenceData, null)->getData()[0]['id'];
- $code = $this->client->getById($id)->getData()[0]['kod'];
+ $id = $this->getInt($this->getFirstItem($this->client->save($evidenceData, id: null)->getData()), 'id');
+ $code = $this->getString($this->getFirstItem($this->client->getById($id)->getData()), 'kod');
$evidenceItem = $this->client->getByCode($code);
Assert::assertCount(1, $evidenceItem->getData());
- Assert::assertEquals($id, (int) $evidenceItem->getData()[0]['id']);
+ Assert::assertSame($id, $this->getInt($this->getFirstItem($evidenceItem->getData()), 'id'));
$evidenceItemFull = $this->client->getByCode($code, ['detail' => 'full']);
- Assert::assertNotEquals(count($evidenceItem->getData()[0]), count($evidenceItemFull->getData()[0]));
+ Assert::assertNotCount(
+ count($this->getFirstItem($evidenceItem->getData())),
+ $this->getFirstItem($evidenceItemFull->getData()),
+ );
$this->client->deleteByCode($code);
Assert::assertCount(0, $this->client->findByCode($code)->getData());
}
@@ -120,11 +129,11 @@ public function testEvidenceGetOnlyCustomFields(): void
$evidenceData = [
'nazev' => $this->faker->firstName,
];
- $id = (int) $this->client->save($evidenceData, null)->getData()[0]['id'];
+ $id = $this->getInt($this->getFirstItem($this->client->save($evidenceData, id: null)->getData()), 'id');
$evidenceData = $this->client->getById($id, ['detail' => 'custom:id,email,kontakty(primarni,email)'])->getData()[0];
Assert::assertEquals([
- 'id' => $id,
'email' => '',
+ 'id' => $id,
'kontakty' => [],
],
$evidenceData,
@@ -144,15 +153,15 @@ public function testEvidenceGetOnlyCustomFields(): void
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeRequestFail
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeSaveFailed
*/
- public function testCRUDOperations(string $evidence, array $evidenceData, array $expectedDataAfterUpdate): void
+ public function testCrudOperations(string $evidence, array $evidenceData, array $expectedDataAfterUpdate): void
{
- $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, $evidence, false, null);
- $addressBookId = (int) $client->save($evidenceData, null)->getData()[0]['id'];
+ $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, $evidence, verifySSLCertificate: false);
+ $addressBookId = $this->getInt($this->getFirstItem($client->save($evidenceData, id: null)->getData()), 'id');
$client->save($expectedDataAfterUpdate, $addressBookId);
- $addressBookRefreshed = $client->getById($addressBookId);
+ $addressBookRefreshed = $this->getFirstItem($client->getById($addressBookId)->getData());
foreach ($expectedDataAfterUpdate as $key => $value) {
- Assert::assertEquals($value, $addressBookRefreshed->getData()[0][$key]);
+ Assert::assertEquals($value, $addressBookRefreshed[$key]);
}
Assert::assertNotEmpty($client->getPdfById($addressBookId, []));
@@ -165,40 +174,39 @@ public function testCRUDOperations(string $evidence, array $evidenceData, array
$client->getById($addressBookId);
$this->expectException(EcomailFlexibeeRequestFail::class);
$evidenceData = [];
- $this->client->save($evidenceData, null);
+ $this->client->save($evidenceData, id: null);
}
/**
- * @return array>
+ * @return \Iterator, array}>
*/
- public function getEvidences(): array
+ public function getEvidences(): Iterator
{
$faker = Factory::create();
$code = mb_substr($faker->uuid, 0, 20);
$name = $faker->userName;
- return [
+ yield [
+ 'adresar',
[
- 'adresar',
- [
- 'kod' => $code,
- 'nazev' => $name,
- ],
- [
- 'nazev' => 'Adresar edited',
- ],
+ 'kod' => $code,
+ 'nazev' => $name,
],
[
- 'faktura-vydana',
- [
- 'kod' => $code,
- 'typDokl' => 'code:FAKTURA',
- 'cisDosle' => '1234',
- 'eetTypK' => 'eetTyp.ne',
- ],
- [
- 'cisDosle' => '1234',
- ],
+ 'nazev' => 'Adresar edited',
+ ],
+ ];
+
+ yield [
+ 'faktura-vydana',
+ [
+ 'cisDosle' => '1234',
+ 'eetTypK' => 'eetTyp.ne',
+ 'kod' => $code,
+ 'typDokl' => 'code:FAKTURA',
+ ],
+ [
+ 'cisDosle' => '1234',
],
];
}
@@ -218,7 +226,7 @@ public function testAllAndChunkInEvidence(): void
public function testSearchInEvidence(): void
{
- $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', false, null);
+ $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', verifySSLCertificate: false);
$result = $client->searchInEvidence('(kod neq \'JAN\')', []);
Assert::assertTrue(count($result) > 0);
$this->expectException(EcomailFlexibeeRequestFail::class);
@@ -227,42 +235,45 @@ public function testSearchInEvidence(): void
public function testSearchInEvidencePaginated(): void
{
- $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', false, null);
+ $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', verifySSLCertificate: false);
$result = $client->searchInEvidencePaginated('(kod neq \'JAN\')', []);
Assert::assertIsInt($result['row_count']);
Assert::assertTrue($result['row_count'] > 0);
- Assert::assertTrue(count($result['data']) > 0);
+ Assert::assertIsArray($result['data']);
+ Assert::assertNotEmpty($result['data']);
$this->expectException(EcomailFlexibeeRequestFail::class);
$client->searchInEvidencePaginated('kod neq \'JAN\'', []);
}
public function testFindLastInEvidence(): void
{
- $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', false, null);
- $result = $client->findLastInEvidence(true);
+ $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', verifySSLCertificate: false);
+ $result = $client->findLastInEvidence(fullDetail: true);
+ Assert::assertIsArray($result->getData()['faktura-vydana']);
Assert::assertCount(1, $result->getData()['faktura-vydana']);
}
public function testSearchInEvidenceWithInvalidUrl(): void
{
- $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', false, null);
+ $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'faktura-vydana', verifySSLCertificate: false);
$this->expectException(EcomailFlexibeeRequestFail::class);
$client->searchInEvidence(' bla.json', []);
}
public function testDryRunRequest(): void
{
- $response = $this->client->save(['kod' => uniqid(), 'nazev' => 'SDSDXXXXX'], null, true);
- $firstItem = $response->getData()[0];
- Assert::assertArrayHasKey('content', $firstItem);
- Assert::assertArrayHasKey(Config::EVIDENCE, $firstItem['content']);
- Assert::assertTrue((int) $firstItem['content'][Config::EVIDENCE]['id'] < 0);
+ $response = $this->client->save(['kod' => uniqid(), 'nazev' => 'SDSDXXXXX'], id: null, dryRun: true);
+ $content = $this->getFirstItem($response->getData())['content'] ?? null;
+ Assert::assertIsArray($content);
+ Assert::assertArrayHasKey(Config::EVIDENCE, $content);
+ Assert::assertIsArray($content[Config::EVIDENCE]);
+ Assert::assertLessThan(0, $this->getInt($content[Config::EVIDENCE], 'id'));
}
public function testMakeCustomRequest(): void
{
$results = $this->client->callRequest(
- Method::get(Method::GET),
+ Method::GET,
'properties',
[],
[],
@@ -275,62 +286,74 @@ public function testMakeCustomRequest(): void
Assert::assertArrayHasKey('properties', $data->getData());
}
- public function testRunBackendProcesses(): void
- {
- $this->markTestSkipped('Live tests is disabled here');
- $client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, 'pokladni-pohyb', false);
- $results = $client->callRequest(Method::get(Method::POST), 'automaticke-parovani', [], [], []);
-
- foreach ($results as $result) {
- $this->checkResponseStructure($result);
- }
- }
-
public function testUnknownCompany(): void
{
- $client = new Client(Config::HOST, 'xxx', Config::USERNAME, Config::PASSWORD, 'faktura-vydana', false);
+ $client = new Client(Config::HOST, 'xxx', Config::USERNAME, Config::PASSWORD, 'faktura-vydana', verifySSLCertificate: false);
$this->expectException(EcomailFlexibeeRequestFail::class);
- $client->save([], null);
-
+ $client->save([], id: null);
}
public function testCreateSameEvidenceRecord(): void
{
$code = uniqid();
$data = [
- 'nazev' => $code,
'kod' => $code,
+ 'nazev' => $code,
];
- $this->client->save($data, null);
+ $this->client->save($data, id: null);
$this->expectException(EcomailFlexibeeRequestFail::class);
$this->expectExceptionCode(400);
- $this->client->save($data, null);
+ $this->client->save($data, id: null);
}
public function testGetPropertiesForEvidence(): void
{
$responseData = $this->client->getPropertiesForEvidence()->getData();
Assert::assertArrayHasKey('properties', $responseData);
+ Assert::assertIsArray($responseData['properties']);
Assert::assertArrayHasKey('property', $responseData['properties']);
}
- private function checkResponseStructure(EvidenceResult $result): void
+ protected function setUp(): void
{
- $requiredKeys = [
- 'created',
- 'updated',
- 'deleted',
- 'skipped',
- 'failed',
- 'status_code',
- 'message',
- 'version',
- 'row_count',
- ];
+ parent::setUp();
- foreach ($requiredKeys as $requiredKey) {
- Assert::assertArrayHasKey($requiredKey, $result->getData());
- }
+ $this->faker = Factory::create();
+ $this->client = new Client(Config::HOST, Config::COMPANY, Config::USERNAME, Config::PASSWORD, Config::EVIDENCE, verifySSLCertificate: false);
+ }
+
+ /**
+ * @param array $data
+ * @return array
+ */
+ private function getFirstItem(array $data): array
+ {
+ $item = $data[0] ?? null;
+ Assert::assertIsArray($item);
+
+ return $item;
+ }
+
+ /**
+ * @param array $data
+ */
+ private function getString(array $data, string $key): string
+ {
+ $value = $data[$key] ?? null;
+ Assert::assertIsString($value);
+
+ return $value;
+ }
+
+ /**
+ * @param array $data
+ */
+ private function getInt(array $data, string $key): int
+ {
+ $value = $data[$key] ?? null;
+ Assert::assertIsNumeric($value);
+
+ return (int) $value;
}
}
diff --git a/tests/CompanyClientTest.php b/tests/CompanyClientTest.php
new file mode 100644
index 0000000..0aa5af6
--- /dev/null
+++ b/tests/CompanyClientTest.php
@@ -0,0 +1,68 @@
+shouldReceive('request')
+ ->once()
+ ->with(
+ Mockery::type('string'),
+ Method::GET,
+ [],
+ [],
+ [],
+ Mockery::on(static fn (ClientConfig $config): bool => $config->debugMode()),
+ )
+ ->andReturn($response);
+ $client = new CompanyClient($this->createConfig(debugMode: true), $httpClient);
+
+ Assert::assertSame($response, $client->backup());
+ }
+
+ public function testRestoreSendsTheBackupWithPut(): void
+ {
+ $response = new FlexibeeResponse(200, version: null, success: true);
+ $httpClient = Mockery::mock(HttpClient::class);
+ $httpClient->shouldReceive('request')
+ ->once()
+ ->with(Mockery::type('string'), Method::PUT, ['backup-data'], [], [], Mockery::type(ClientConfig::class))
+ ->andReturn($response);
+ $client = new CompanyClient($this->createConfig(debugMode: false), $httpClient);
+
+ Assert::assertSame($response, $client->restore('new-company', 'backup-data'));
+ }
+
+ private function createConfig(bool $debugMode): ClientConfig
+ {
+ return new ClientConfig(
+ Config::HOST,
+ Config::COMPANY,
+ Config::USERNAME,
+ Config::PASSWORD,
+ 'c',
+ verifySSLCertificate: false,
+ debugMode: $debugMode,
+ );
+ }
+
+}
diff --git a/tests/Config.php b/tests/Config.php
index 9816e21..210ddac 100644
--- a/tests/Config.php
+++ b/tests/Config.php
@@ -4,15 +4,17 @@
namespace EcomailFlexibeeTest;
-use Consistence\ObjectPrototype;
-
-final class Config extends ObjectPrototype
+final class Config
{
public const HOST = 'https://demo.flexibee.eu';
+
public const COMPANY = 'demo';
+
public const USERNAME = 'winstrom';
+
public const PASSWORD = 'winstrom';
+
public const EVIDENCE = 'adresar';
}
diff --git a/tests/Enum/SearchQueryOperatorTest.php b/tests/Enum/SearchQueryOperatorTest.php
index d162344..246e0ea 100644
--- a/tests/Enum/SearchQueryOperatorTest.php
+++ b/tests/Enum/SearchQueryOperatorTest.php
@@ -5,6 +5,7 @@
namespace EcomailFlexibeeTest\Enum;
use EcomailFlexibee\Enum\SearchQueryOperator;
+use Iterator;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
@@ -16,43 +17,47 @@ final class SearchQueryOperatorTest extends TestCase
*/
public function testConvertOperators(string $query, string $expectedQuery): void
{
- Assert::assertEquals($expectedQuery, SearchQueryOperator::convertOperatorsInQuery($query));
+ Assert::assertSame($expectedQuery, SearchQueryOperator::convertOperatorsInQuery($query));
}
/**
- * @return array
+ * @return \Iterator
*/
- public function getQueryStrings(): array
+ public function getQueryStrings(): Iterator
{
- return [
- [
- 'kod = \'JAN\'',
- 'kod eq \'JAN\'',
- ],
- [
- 'kod=\'=JAN\'',
- 'kod eq \'=JAN\'',
- ],
- [
- 'kod=\'=JAN=<>\'',
- 'kod eq \'=JAN=<>\'',
- ],
- [
- 'kod<>\'JAN\'',
- 'kod neq \'JAN\'',
- ],
- [
- 'datSplat<\'2018-12-04\'%20and%20zuctovano=false',
- 'datSplat lt \'2018-12-04\' and zuctovano eq false',
- ],
- [
- 'datSplat<\'2018-12-04\' and zuctovano=false',
- 'datSplat lt \'2018-12-04\' and zuctovano eq false',
- ],
- [
- 'sparovano = true and lastUpdate <= \'2018-12-11\'',
- 'sparovano eq true and lastUpdate lte \'2018-12-11\'',
- ],
+ yield [
+ 'kod = \'JAN\'',
+ 'kod eq \'JAN\'',
+ ];
+
+ yield [
+ 'kod=\'=JAN\'',
+ 'kod eq \'=JAN\'',
+ ];
+
+ yield [
+ 'kod=\'=JAN=<>\'',
+ 'kod eq \'=JAN=<>\'',
+ ];
+
+ yield [
+ 'kod<>\'JAN\'',
+ 'kod neq \'JAN\'',
+ ];
+
+ yield [
+ 'datSplat<\'2018-12-04\'%20and%20zuctovano=false',
+ 'datSplat lt \'2018-12-04\' and zuctovano eq false',
+ ];
+
+ yield [
+ 'datSplat<\'2018-12-04\' and zuctovano=false',
+ 'datSplat lt \'2018-12-04\' and zuctovano eq false',
+ ];
+
+ yield [
+ 'sparovano = true and lastUpdate <= \'2018-12-11\'',
+ 'sparovano eq true and lastUpdate lte \'2018-12-11\'',
];
}
diff --git a/tests/Http/CurlOptionsFactoryTest.php b/tests/Http/CurlOptionsFactoryTest.php
new file mode 100644
index 0000000..8273992
--- /dev/null
+++ b/tests/Http/CurlOptionsFactoryTest.php
@@ -0,0 +1,139 @@
+create(new CurlRequest('https://demo.flexibee.eu/c/demo/adresar.json', Method::GET, [], [], []), $config);
+
+ Assert::assertTrue($options[CURLOPT_SSL_VERIFYPEER]);
+ Assert::assertSame(2, $options[CURLOPT_SSL_VERIFYHOST]);
+ }
+
+ public function testWrapsJsonEncodingFailureIntoRequestFail(): void
+ {
+ $config = new Config('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: true, debugMode: false);
+
+ try {
+ (new CurlOptionsFactory())->create(
+ new CurlRequest('https://demo.flexibee.eu/c/demo/adresar.json', Method::PUT, ['nazev' => "\xB1\x31"], [], []),
+ $config,
+ );
+ Assert::fail('EcomailFlexibeeRequestFail was not thrown.');
+ } catch (EcomailFlexibeeRequestFail $exception) {
+ Assert::assertInstanceOf(JsonException::class, $exception->getPrevious());
+ }
+ }
+
+ public function testRejectsAnEmptyUrl(): void
+ {
+ $config = new Config('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: true, debugMode: false);
+
+ $this->expectException(EcomailFlexibeeRequestFail::class);
+
+ (new CurlOptionsFactory())->create(new CurlRequest('', Method::GET, [], [], []), $config);
+ }
+
+ public function testUsesBasicAuthenticationWithoutSession(): void
+ {
+ $config = new Config('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: false, debugMode: true);
+
+ $options = (new CurlOptionsFactory())->create(
+ new CurlRequest('https://demo.flexibee.eu/c/demo/adresar.json', Method::DELETE, [], [], []),
+ $config,
+ );
+
+ Assert::assertSame('https://demo.flexibee.eu/c/demo/adresar.json', $options[CURLOPT_URL]);
+ Assert::assertSame('DELETE', $options[CURLOPT_CUSTOMREQUEST]);
+ Assert::assertSame(CURLAUTH_BASIC, $options[CURLOPT_HTTPAUTH]);
+ Assert::assertSame('user:pass', $options[CURLOPT_USERPWD]);
+ Assert::assertFalse($options[CURLOPT_SSL_VERIFYPEER]);
+ Assert::assertSame(0, $options[CURLOPT_SSL_VERIFYHOST]);
+ Assert::assertTrue($options[CURLINFO_HEADER_OUT]);
+ Assert::assertArrayNotHasKey(CURLOPT_HTTPHEADER, $options);
+ Assert::assertArrayNotHasKey(CURLOPT_POSTFIELDS, $options);
+ }
+
+ public function testSessionAuthenticationSendsSessionHeader(): void
+ {
+ $config = new Config(
+ 'https://demo.flexibee.eu',
+ 'demo',
+ 'user',
+ 'pass',
+ 'adresar',
+ verifySSLCertificate: true,
+ authSessionId: 'session-id',
+ debugMode: false,
+ );
+
+ $options = (new CurlOptionsFactory())->create(
+ new CurlRequest('https://demo.flexibee.eu', Method::GET, [], [], ['Accept: application/json']),
+ $config,
+ );
+
+ Assert::assertSame(0, $options[CURLOPT_HTTPAUTH]);
+ Assert::assertArrayNotHasKey(CURLOPT_USERPWD, $options);
+ Assert::assertSame(['Accept: application/json', 'X-authSessionId: session-id'], $options[CURLOPT_HTTPHEADER]);
+ }
+
+ public function testEncodesPostFieldsAsWinstromJson(): void
+ {
+ $config = new Config('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: true, debugMode: false);
+
+ $options = (new CurlOptionsFactory())->create(
+ new CurlRequest('https://demo.flexibee.eu', Method::PUT, ['adresar' => ['kod' => 'A']], [], []),
+ $config,
+ );
+
+ Assert::assertSame('{"winstrom":{"adresar":{"kod":"A"}}}', $options[CURLOPT_POSTFIELDS]);
+ }
+
+ public function testQueryParametersReplacePostFields(): void
+ {
+ $config = new Config('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: true, debugMode: false);
+
+ $request = new CurlRequest('https://demo.flexibee.eu', Method::POST, ['a' => 'b'], ['username' => 'u', 'password' => 'p']);
+
+ $options = (new CurlOptionsFactory())->create($request, $config);
+
+ Assert::assertSame('username=u&password=p', $options[CURLOPT_POSTFIELDS]);
+ }
+
+}
diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php
new file mode 100644
index 0000000..19d9a18
--- /dev/null
+++ b/tests/Http/HttpClientTest.php
@@ -0,0 +1,44 @@
+request($this->getFixtureUrl(), Method::GET, [], [], [], $this->createConfig(debugMode: false));
+
+ Assert::assertSame(1.0, $response->getVersion());
+ Assert::assertSame(['@version' => '1.0', 'adresar' => [['id' => '1']]], $response->getData());
+ }
+
+ public function testDebugModeDoesNotBreakTheRequest(): void
+ {
+ $response = (new HttpClient())->request($this->getFixtureUrl(), Method::GET, [], [], [], $this->createConfig(debugMode: true));
+
+ Assert::assertSame(['@version' => '1.0', 'adresar' => [['id' => '1']]], $response->getData());
+ }
+
+ private function getFixtureUrl(): string
+ {
+ return sprintf('file://%s', realpath(__DIR__ . '/fixtures/adresar.json'));
+ }
+
+ private function createConfig(bool $debugMode): Config
+ {
+ return new Config('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: false, debugMode: $debugMode);
+ }
+
+}
diff --git a/tests/Http/QueryBuilderTest.php b/tests/Http/QueryBuilderTest.php
index ee99e41..ff1f82e 100644
--- a/tests/Http/QueryBuilderTest.php
+++ b/tests/Http/QueryBuilderTest.php
@@ -10,34 +10,18 @@
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
-class QueryBuilderTest extends TestCase
+final class QueryBuilderTest extends TestCase
{
private UrlBuilder $urlBuilder;
- public function setUp(): void
- {
- parent::setUp();
-
- $this->urlBuilder = new UrlBuilder(
- new Config(
- TestConfig::HOST,
- TestConfig::COMPANY,
- TestConfig::USERNAME,
- TestConfig::PASSWORD,
- TestConfig::EVIDENCE,
- false,
- ),
- );
- }
-
public function testCreateUriByCodeOnly(): void
{
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/(kod%20eq%20\'CODE:TEST\').json?limit=0',
$this->urlBuilder->createUriByCodeOnly('CODE:TEST', []),
);
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/(kod%20eq%20\'CODE:TEST\').json?limit=0&test=1',
$this->urlBuilder->createUriByCodeOnly('CODE:TEST', ['test' => true]),
);
@@ -45,7 +29,7 @@ public function testCreateUriByCodeOnly(): void
public function testCreateUriByEvidenceOnly(): void
{
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar.json?limit=0',
$this->urlBuilder->createUriByEvidenceOnly([]),
);
@@ -53,7 +37,7 @@ public function testCreateUriByEvidenceOnly(): void
public function testCreateUriByCustomId(): void
{
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/code:TEST.json?limit=0',
$this->urlBuilder->createFilterQuery('code:TEST', []),
);
@@ -61,14 +45,14 @@ public function testCreateUriByCustomId(): void
public function testCreateUriPdf(): void
{
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/11.pdf?limit=0',
$this->urlBuilder->createPdfUrl(11, []),
);
$queryParams = [];
$queryParams['report-name'] = 'test';
$queryParams['report-lang'] = 'en';
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/11.pdf?limit=0&report-name=test&report-lang=en',
$this->urlBuilder->createPdfUrl(11, $queryParams),
);
@@ -76,22 +60,38 @@ public function testCreateUriPdf(): void
public function testCreateUriByEvidenceForSearchQuery(): void
{
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/1.json?limit=10000',
- $this->urlBuilder->createFilterQuery('1', ['limit' => 10000]),
+ $this->urlBuilder->createFilterQuery('1', ['limit' => 10_000]),
);
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/1.json?limit=10000&start=0',
- $this->urlBuilder->createFilterQuery('1', ['limit' => 10000, 'start' => 0]),
+ $this->urlBuilder->createFilterQuery('1', ['limit' => 10_000, 'start' => 0]),
);
}
public function testCreateUriByEvidenceForSearchQueryWithouLimitParameter(): void
{
- Assert::assertEquals(
+ Assert::assertSame(
'https://demo.flexibee.eu/c/demo/adresar/1.json?limit=0',
$this->urlBuilder->createFilterQuery('1', []),
);
}
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->urlBuilder = new UrlBuilder(
+ new Config(
+ TestConfig::HOST,
+ TestConfig::COMPANY,
+ TestConfig::USERNAME,
+ TestConfig::PASSWORD,
+ TestConfig::EVIDENCE,
+ verifySSLCertificate: false,
+ ),
+ );
+ }
+
}
diff --git a/tests/Http/ResponseFactoryTest.php b/tests/Http/ResponseFactoryTest.php
new file mode 100644
index 0000000..dab8935
--- /dev/null
+++ b/tests/Http/ResponseFactoryTest.php
@@ -0,0 +1,182 @@
+expectException(EcomailFlexibeeRequestFail::class);
+
+ ResponseFactory::createFromOutput(
+ self::URL,
+ Method::GET,
+ responseContent: null,
+ statusCode: 200,
+ errorNumber: CURLE_OK,
+ errorMessage: null,
+ curlInfoDebug: null,
+ );
+ }
+
+ public function testCurlErrorThrowsConnectionFail(): void
+ {
+ $this->expectException(EcomailFlexibeeConnectionFail::class);
+ $this->expectExceptionMessage('cURL error (7): Connection refused');
+
+ ResponseFactory::createFromOutput(self::URL, Method::GET, '', 0, CURLE_COULDNT_CONNECT, 'Connection refused', curlInfoDebug: null);
+ }
+
+ public function testPdfUrlReturnsPdfResponse(): void
+ {
+ $response = ResponseFactory::createFromOutput(
+ 'https://demo.flexibee.eu/c/demo/faktura-vydana/1.pdf',
+ Method::GET,
+ '%PDF',
+ 200,
+ CURLE_OK,
+ errorMessage: null,
+ curlInfoDebug: null,
+ );
+
+ Assert::assertInstanceOf(FlexibeePdfResponse::class, $response);
+ Assert::assertSame(['pdfContent' => '%PDF'], $response->getData());
+ }
+
+ public function testBackupUrlReturnsBackupResponse(): void
+ {
+ $response = ResponseFactory::createFromOutput(
+ 'https://demo.flexibee.eu/c/demo/backup',
+ Method::GET,
+ 'dump',
+ 200,
+ CURLE_OK,
+ errorMessage: null,
+ curlInfoDebug: null,
+ );
+
+ Assert::assertInstanceOf(FlexibeeBackupResponse::class, $response);
+ Assert::assertSame(['backupContent' => 'dump'], $response->getData());
+ }
+
+ public function testParsesWinstromMetadata(): void
+ {
+ $content = '{"winstrom":{"@version":"1.5","@rowCount":"3","@globalVersion":"42","message":"ok","stats":{"created":"1"},'
+ . '"success":"true","results":[{"id":"1"}]}}';
+
+ $response = ResponseFactory::createFromOutput(self::URL, Method::GET, $content, 201, CURLE_OK, errorMessage: null, curlInfoDebug: null);
+
+ Assert::assertSame(201, $response->getStatusCode());
+ Assert::assertSame(1.5, $response->getVersion());
+ Assert::assertTrue($response->isSuccess());
+ Assert::assertSame('ok', $response->getMessage());
+ Assert::assertSame(3, $response->getRowCount());
+ Assert::assertSame(42, $response->getGlobalVersion());
+ Assert::assertSame([['id' => '1']], $response->getData());
+ Assert::assertSame(['created' => '1'], $response->getStatistics());
+ }
+
+ public function testUsesDefaultsWhenMetadataIsMissing(): void
+ {
+ $response = ResponseFactory::createFromOutput(self::URL, Method::GET, '{"adresar":[]}', 200, CURLE_OK, errorMessage: null, curlInfoDebug: null);
+
+ Assert::assertNull($response->getVersion());
+ Assert::assertTrue($response->isSuccess());
+ Assert::assertSame('{"adresar":[]}', $response->getMessage());
+ Assert::assertSame(0, $response->getRowCount());
+ Assert::assertNull($response->getGlobalVersion());
+ Assert::assertSame(['adresar' => []], $response->getData());
+ Assert::assertSame([], $response->getStatistics());
+ }
+
+ public function testExplicitSuccessFlagOverridesStatusCode(): void
+ {
+ $response = ResponseFactory::createFromOutput(self::URL, Method::GET, '{"success":false}', 200, CURLE_OK, errorMessage: null, curlInfoDebug: null);
+
+ Assert::assertFalse($response->isSuccess());
+ }
+
+ public function testNonJsonContentProducesEmptyData(): void
+ {
+ $response = ResponseFactory::createFromOutput(self::URL, Method::GET, 'not json', 302, CURLE_OK, errorMessage: null, curlInfoDebug: null);
+
+ Assert::assertFalse($response->isSuccess());
+ Assert::assertSame([], $response->getData());
+ Assert::assertSame('not json', $response->getMessage());
+ }
+
+ /**
+ * @dataProvider getStatusCodeExceptions
+ * @param class-string<\Throwable> $exceptionClass
+ */
+ public function testStatusCodeThrowsMatchingException(int $statusCode, ?string $curlInfoDebug, string $exceptionClass, string $message): void
+ {
+ $this->expectException($exceptionClass);
+ $this->expectExceptionMessage($message);
+
+ ResponseFactory::createFromOutput(self::URL, Method::GET, '{}', $statusCode, CURLE_OK, errorMessage: null, curlInfoDebug: $curlInfoDebug);
+ }
+
+ /**
+ * @return \Iterator, string}>
+ */
+ public function getStatusCodeExceptions(): Iterator
+ {
+ yield 'unauthorized' => [401, null, EcomailFlexibeeInvalidAuthorization::class, 'přihlásit'];
+ yield 'forbidden' => [403, null, EcomailFlexibeeForbidden::class, 'nemá oprávnění'];
+ yield 'forbidden with debug info' => [403, '"GET /c/demo"', EcomailFlexibeeForbidden::class, 'GET /c/demo'];
+ yield 'method not allowed' => [405, null, EcomailFlexibeeMethodNotAllowed::class, ''];
+ yield 'not acceptable' => [406, null, EcomailFlexibeeNotAcceptableRequest::class, 'ISDOC'];
+ }
+
+ public function testBadRequestThrowsFirstErrorWithStatusCode(): void
+ {
+ $content = '{"winstrom":{"results":[{"id":"1"},{"errors":[{"code":"E1","for":"kod","path":"adresar","message":"Duplicate","extra":[]},'
+ . '{"message":"Second"}]}]}}';
+
+ $this->expectException(EcomailFlexibeeRequestFail::class);
+ $this->expectExceptionMessage("code: E1\nfor attribute: kod\npath: adresar\nmessage: Duplicate");
+ $this->expectExceptionCode(400);
+
+ ResponseFactory::createFromOutput(self::URL, Method::PUT, $content, 400, CURLE_OK, errorMessage: null, curlInfoDebug: null);
+ }
+
+ public function testServerErrorWithoutErrorDetailsThrowsResponseMessage(): void
+ {
+ $this->expectException(EcomailFlexibeeRequestFail::class);
+ $this->expectExceptionMessage('Internal failure');
+
+ ResponseFactory::createFromOutput(
+ self::URL,
+ Method::GET,
+ '{"message":"Internal failure","results":[{"errors":["invalid"]}]}',
+ 500,
+ CURLE_OK,
+ errorMessage: null,
+ curlInfoDebug: null,
+ );
+ }
+
+}
diff --git a/tests/Http/ResponseHydratorTest.php b/tests/Http/ResponseHydratorTest.php
new file mode 100644
index 0000000..9b3b1f9
--- /dev/null
+++ b/tests/Http/ResponseHydratorTest.php
@@ -0,0 +1,151 @@
+ $data
+ */
+ public function testFailedResponseThrowsRequestFail(array $data, string $expectedMessage): void
+ {
+ $this->expectException(EcomailFlexibeeRequestFail::class);
+ $this->expectExceptionMessage($expectedMessage);
+ $this->expectExceptionCode(400);
+
+ $this->createHydrator()->convertResponseToEvidenceResults($this->createResponse($data, statusCode: 400));
+ }
+
+ /**
+ * @return \Iterator, string}>
+ */
+ public function getFailedResponses(): Iterator
+ {
+ yield 'errors without message' => [[['errors' => [['message' => 'First'], 'ignored', ['message' => 'Second']]]], "\nFirst\nSecond"];
+ yield 'errors with message' => [['message' => 'Explicit', ['errors' => []]], 'Explicit'];
+ yield 'success false flag' => [['message' => 'Rejected', 'success' => 'false'], 'Rejected'];
+ yield 'success false without message' => [['success' => 'false'], ''];
+ }
+
+ public function testFailedPaginatedResponseThrowsRequestFail(): void
+ {
+ $this->expectException(EcomailFlexibeeRequestFail::class);
+
+ $this->createHydrator()->convertResponseToPaginatedEvidenceResults($this->createResponse(['success' => 'false', 'message' => 'x']));
+ }
+
+ public function testEvidenceItemsBecomeEvidenceResults(): void
+ {
+ $results = $this->createHydrator()->convertResponseToEvidenceResults(
+ $this->createResponse(['adresar' => [['id' => '1'], 'not-an-item', ['id' => '2']]]),
+ );
+
+ Assert::assertEquals([0 => new EvidenceResult(['id' => '1']), 2 => new EvidenceResult(['id' => '2'])], $results);
+ }
+
+ public function testResponseWithoutEvidenceIsReturnedAsSingleResult(): void
+ {
+ $results = $this->createHydrator()->convertResponseToEvidenceResults($this->createResponse(['properties' => []]));
+
+ Assert::assertEquals([new EvidenceResult(['properties' => []])], $results);
+ }
+
+ public function testEmptyResponseIsReplacedByStatistics(): void
+ {
+ $response = new FlexibeeResponse(201, 1.0, success: true, message: 'done', rowCount: 4, data: [], statistics: ['created' => '1']);
+
+ $results = $this->createHydrator()->convertResponseToEvidenceResults($response);
+
+ Assert::assertEquals([
+ new EvidenceResult([
+ 'created' => '1',
+ 'message' => 'done',
+ 'row_count' => 4,
+ 'status_code' => 201,
+ 'version' => 1.0,
+ ]),
+ ], $results);
+ }
+
+ public function testPaginatedResultsCarryRowCount(): void
+ {
+ $response = new FlexibeeResponse(200, version: null, success: true, rowCount: 7, data: ['adresar' => [['id' => '1']]]);
+
+ $results = $this->createHydrator()->convertResponseToPaginatedEvidenceResults($response);
+
+ Assert::assertEquals(['data' => [new EvidenceResult(['id' => '1'])], 'row_count' => 7], $results);
+ }
+
+ public function testPaginatedResponseWithoutEvidenceIsReturnedAsSingleResult(): void
+ {
+ $response = new FlexibeeResponse(200, version: null, success: true, rowCount: 0, data: ['properties' => []]);
+
+ $results = $this->createHydrator()->convertResponseToPaginatedEvidenceResults($response);
+
+ Assert::assertEquals(['data' => [new EvidenceResult(['properties' => []])], 'row_count' => 0], $results);
+ }
+
+ public function testSingleEvidenceResultIsReturned(): void
+ {
+ $result = $this->createHydrator()->convertResponseToEvidenceResult(
+ $this->createResponse(['adresar' => [['id' => '1']]]),
+ throwException: true,
+ );
+
+ Assert::assertEquals(new EvidenceResult([['id' => '1']]), $result);
+ }
+
+ public function testMissingEvidenceThrowsWhenRequested(): void
+ {
+ $this->expectException(EcomailFlexibeeNoEvidenceResult::class);
+
+ $this->createHydrator()->convertResponseToEvidenceResult(
+ $this->createResponse(['adresar' => [['id' => '1']]], statusCode: 404),
+ throwException: true,
+ );
+ }
+
+ public function testMissingEvidenceReturnsRawDataWhenNotThrowing(): void
+ {
+ $result = $this->createHydrator()->convertResponseToEvidenceResult($this->createResponse(['other' => 'x']), throwException: false);
+
+ Assert::assertEquals(new EvidenceResult(['other' => 'x']), $result);
+ }
+
+ public function testMissingEvidenceReturnsEmptyResultForEmptyData(): void
+ {
+ $result = $this->createHydrator()->convertResponseToEvidenceResult($this->createResponse([]), throwException: false);
+
+ Assert::assertEquals(new EvidenceResult([]), $result);
+ }
+
+ private function createHydrator(): ResponseHydrator
+ {
+ return new ResponseHydrator(
+ new Config('https://demo.flexibee.eu', 'demo', 'user', 'pass', 'adresar', verifySSLCertificate: true, debugMode: false),
+ );
+ }
+
+ /**
+ * @param array $data
+ */
+ private function createResponse(array $data, int $statusCode = 200): FlexibeeResponse
+ {
+ return new FlexibeeResponse($statusCode, version: null, success: true, rowCount: 0, data: $data);
+ }
+
+}
diff --git a/tests/Http/fixtures/adresar.json b/tests/Http/fixtures/adresar.json
new file mode 100644
index 0000000..5dfc42b
--- /dev/null
+++ b/tests/Http/fixtures/adresar.json
@@ -0,0 +1 @@
+{"winstrom":{"@version":"1.0","adresar":[{"id":"1"}]}}
\ No newline at end of file
diff --git a/tests/Validator/ParameterValidatorTest.php b/tests/Validator/ParameterValidatorTest.php
index 94f9003..4b9a754 100644
--- a/tests/Validator/ParameterValidatorTest.php
+++ b/tests/Validator/ParameterValidatorTest.php
@@ -6,6 +6,7 @@
use EcomailFlexibee\Exception\EcomailFlexibeeInvalidRequestParameter;
use EcomailFlexibee\Validator\ParameterValidator;
+use Iterator;
use PHPUnit\Framework\TestCase;
final class ParameterValidatorTest extends TestCase
@@ -13,16 +14,10 @@ final class ParameterValidatorTest extends TestCase
private ParameterValidator $validator;
- public function setUp(): void
- {
- parent::setUp();
-
- $this->validator = new ParameterValidator();
- }
-
/**
* @dataProvider getDataForTestValidateFlexibeeRequestCodeParameter
* @throws \EcomailFlexibee\Exception\EcomailFlexibeeInvalidRequestParameter
+ * @param class-string<\Throwable>|null $exceptionClass
*/
public function testValidateFlexibeeRequestCodeParameter(string $code, ?string $exceptionClass): void
{
@@ -31,28 +26,49 @@ public function testValidateFlexibeeRequestCodeParameter(string $code, ?string $
}
$this->validator->validateFlexibeeRequestCodeParameter($code);
-
}
/**
- * @return array>
+ * @return \Iterator|null}>
*/
- public function getDataForTestValidateFlexibeeRequestCodeParameter(): array
+ public function getDataForTestValidateFlexibeeRequestCodeParameter(): Iterator
{
- return [
- [
- 'TEST',
- null,
- ],
- [
- 'ahoj',
- EcomailFlexibeeInvalidRequestParameter::class,
- ],
- [
- 'ASASASASASASASASASASASASASASAS',
- EcomailFlexibeeInvalidRequestParameter::class,
- ],
+ yield [
+ 'TEST',
+ null,
];
+
+ yield [
+ 'ahoj',
+ EcomailFlexibeeInvalidRequestParameter::class,
+ ];
+
+ yield [
+ 'ASASASASASASASASASASASASASASAS',
+ EcomailFlexibeeInvalidRequestParameter::class,
+ ];
+ }
+
+ public function testReportsOneMessageWhenBothRulesFail(): void
+ {
+ $this->expectException(EcomailFlexibeeInvalidRequestParameter::class);
+ $this->expectExceptionMessageMatches('/^Parameter code must have a maximum of 20 characters and must be an uppercase$/');
+
+ $this->validator->validateFlexibeeRequestCodeParameter('lowercase-code-longer-than-twenty');
+ }
+
+ public function testAcceptsUppercaseCodeWithDiacriticsAtTheLengthLimit(): void
+ {
+ $this->expectNotToPerformAssertions();
+
+ $this->validator->validateFlexibeeRequestCodeParameter('ŽLUŤOUČKÝ-KŮŇ-ÚPĚL-Á');
+ }
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->validator = new ParameterValidator();
}
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..b7c0abb
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,11 @@
+