Compare commits

..
3 Commits
Author SHA1 Message Date
pierpaolo.mammi 33ff4d9577 major refactor
- add request status to search instance API
- use JSend-type response in elixForms clients
- readjust namespaces
- add more unit tests (manual HTTP requests too)
2026-07-24 14:56:25 +02:00
pierpaolo.mammi ef53ee8364 update logger to stdout/stderr 2026-07-24 12:43:29 +02:00
pierpaolo.mammi 8f1fbbfeef fix xdebug 2026-07-23 23:07:21 +02:00
17 changed files with 359 additions and 147 deletions
+1 -1
View File
@@ -3,6 +3,6 @@ FROM php:8.5.8-cli
RUN pecl install xdebug && docker-php-ext-enable xdebug
WORKDIR /app
EXPOSE 8000 9013
EXPOSE 8000
CMD ["php", "-S", "0.0.0.0:8000", "-t", "public", "public/index.php"]
+2 -1
View File
@@ -5,5 +5,6 @@ xdebug.mode=develop,debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9013
xdebug.log_level=0
xdebug.log=/tmp/xdebug.log
xdebug.log_level=7
xdebug.idekey=VSCODE
+1
View File
@@ -8,6 +8,7 @@
"name": "API - Listen for Xdebug",
"type": "php",
"request": "launch",
"hostname": "0.0.0.0",
"port": 9013,
"pathMappings": {
"/app": "${workspaceFolder}"
+5 -5
View File
@@ -12,9 +12,9 @@ use Api\Core\RateLimiter\RateLimiterInterface;
use Api\Core\RateLimiter\FileRateLimiter;
use Api\Core\RateLimiter\InMemoryRateLimiter;
use Api\Core\Log\LoggerFactory;
use ElixForms\ElixFormsClient;
use ElixForms\Auth\ElixFormsApiClient;
use ElixForms\Auth\ElixFormsAuthenticationClient;
use ElixForms\Clients\ElixFormsApiClient;
use ElixForms\Clients\ElixFormsAuthenticationClient;
use ElixForms\Clients\ElixFormsGenericClient;
use Psr\Log\LoggerInterface;
$container = new Container();
@@ -63,10 +63,10 @@ $container->singleton(LoggerInterface::class, function($c) {
});
// ElixForms client binding
$container->singleton(ElixFormsClient::class, function($c) {
$container->singleton(ElixFormsGenericClient::class, function($c) {
$config = $c->make(Config::class);
$baseUrl = $config->get('elixforms_api_base_url');
return new ElixFormsClient($baseUrl);
return new ElixFormsGenericClient($baseUrl, null);
});
$container->singleton(ElixFormsAuthenticationClient::class, function($c) {
+1 -1
View File
@@ -16,4 +16,4 @@ services:
- ./.docker/php/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
#environment:
# XDEBUG_MODE: debug
# XDEBUG_CONFIG: client_host=host.docker.internal client_port=9003 start_with_request=yes
# XDEBUG_CONFIG: client_host=host.docker.internal client_port=9013 start_with_request=yes
+25 -9
View File
@@ -15,12 +15,11 @@ Il progetto contiene già i file necessari per il debug:
- `.docker/php/Dockerfile` - container PHP con Xdebug
- `docker-compose.yml` - configurazione Docker
- `.vscode/launch.json` - configurazione di avvio VS Code
- `.vscode/settings.json` - impostazioni VS Code
Il container espone:
Il servizio usa:
- **porta `8000`** per HTTP
- **porta `9003`** per Xdebug
- **porta host `9013`** per la connessione in uscita di Xdebug verso VS Code
## 2. Avvia il container
@@ -39,7 +38,7 @@ http://localhost:8000
## 3. Avvia il debug in VS Code
1. Apri la sezione **Run and Debug** in VS Code (Ctrl+Shift+D)
2. Seleziona la configurazione: **Debug PHP in Docker**
2. Seleziona la configurazione: **API - Listen for Xdebug**
3. Premi **F5** per avviare il debugger
## 4. Imposta un breakpoint e testa
@@ -56,12 +55,15 @@ http://localhost:8000
http://localhost:8000/api/users
```
3. Oppure usa curl con il trigger Xdebug:
3. Esegui la stessa richiesta con curl:
```bash
curl -H "XDEBUG_TRIGGER: 1" http://localhost:8000/api/users
curl http://localhost:8000/api/users
```
`xdebug.start_with_request=yes` avvia il tentativo di connessione per ogni
richiesta, quindi non serve aggiungere `XDEBUG_TRIGGER`.
Il debugger dovrebbe fermarsi sul breakpoint e permetterti di ispezionare le variabili.
## 5. Risoluzione dei problemi
@@ -69,13 +71,14 @@ Il debugger dovrebbe fermarsi sul breakpoint e permetterti di ispezionare le var
### VS Code non si ferma sul breakpoint
- Verifica che Xdebug sia abilitato nel container
- Controlla che la configurazione `client_host` nel Dockerfile punti a `host.docker.internal`
- Verifica che la porta 9003 sia disponibile e non bloccata dal firewall
- Controlla che `xdebug.client_host` in `.docker/php/xdebug.ini` punti a
`host.docker.internal`
- Verifica che la porta 9013 sia disponibile e non bloccata dal firewall
### Il container non si avvia
- Assicurati che Docker Desktop sia in esecuzione
- Verifica che le porte 8000 e 9003 non siano già in uso
- Verifica che la porta HTTP 8000 non sia già in uso
- Controlla i log di Docker per errori specifici
### "Failed to fetch" dalle richieste del frontend
@@ -84,6 +87,19 @@ Il debugger dovrebbe fermarsi sul breakpoint e permetterti di ispezionare le var
- Controlla che `allowed_origins` in `config/config.php` includa l'origine del frontend
- Testa con l'endpoint di diagnostica `/cors-check`
### Diagnostica avanzata dei breakpoint
Il log Xdebug è scritto nel container in `/tmp/xdebug.log`. Per analizzare anche
la registrazione e la risoluzione dei breakpoint, imposta temporaneamente
`xdebug.log_level=10` in `.docker/php/xdebug.ini`, riavvia il container e usa:
```bash
docker exec elixforms-ws tail -f /tmp/xdebug.log
```
Al termine ripristina `xdebug.log_level=7`, perché il livello 10 produce molti
dettagli per ogni riga eseguita.
## Note
- La procedura di debug influisce solo sulla sessione VS Code, il server continua a funzionare normalmente
-19
View File
@@ -1,19 +0,0 @@
@term=MA
GET http://localhost:8000/api/dipendenti/cerca?term={{term}}
Authorization: Basic elixforms_ws:password123
X-API-Key: myApiAccessToken
###
@term=ID
GET http://localhost:8000/api/contratti/cerca?term={{term}}&cod_fis=MMMPPL74T17E463A
Authorization: Basic elixforms_ws:password123
X-API-Key: myApiAccessToken
###
@term=ID
GET http://localhost:8000/contratti/cerca?term={{term}}
+42 -9
View File
@@ -4,9 +4,8 @@ namespace Api\Controllers\V1;
use Api\Core\Config;
use Api\Core\Request;
use Api\Core\Response;
use ElixForms\Auth\ElixFormsApiClient;
use ElixForms\Auth\ElixFormsAuthenticationClient;
use ElixForms\Exceptions\ElixFormsException;
use ElixForms\Clients\ElixFormsApiClient;
use ElixForms\Clients\ElixFormsAuthenticationClient;
class ElixFormsController
{
@@ -30,11 +29,15 @@ class ElixFormsController
$moduleTag = $this->requiredString($query, 'moduleTag');
$fieldName = $this->requiredString($query, 'fieldName');
$fieldValue = $this->requiredString($query, 'fieldValue');
$requestStatuses = $this->requiredString($query, 'requestStatuses');
$exportGroup = $this->optionalNonEmptyString($query, 'exportGroup', 'API');
if ($moduleTag === null || $fieldName === null || $fieldValue === null || $exportGroup === null) {
return $res->json([
'error' => 'moduleTag, fieldName e fieldValue sono obbligatori; exportGroup, se specificato, deve essere una stringa non vuota.'
'status' => 'fail',
'data' => [
'parameters' => 'moduleTag, fieldName e fieldValue sono obbligatori; exportGroup, se specificato, deve essere una stringa non vuota.',
],
], 400);
}
@@ -42,11 +45,31 @@ class ElixFormsController
$password = $this->config->secret('elixforms_api_password');
if (!\is_string($username) || trim($username) === '' || !\is_string($password) || $password === '') {
throw new ElixFormsException('Credenziali elixForms non configurate.');
return $res->json([
'status' => 'error',
'message' => 'Credenziali elixForms non configurate.',
'code' => 500,
], 500);
}
$token = $this->authenticationClient->login($username, $password);
$instances = $this->apiClient->lookupByStatus($moduleTag, $token, $username);
$login = $this->authenticationClient->login($username, $password);
if ($login['status'] !== 'success') {
return $this->respondWithJSendFailure($res, $login);
}
$token = $login['data']['authToken'];
$instancesResponse = $this->apiClient->lookupByStatus(
$moduleTag,
$token,
$username,
$requestStatuses
);
if ($instancesResponse['status'] !== 'success') {
return $this->respondWithJSendFailure($res, $instancesResponse);
}
$instances = $instancesResponse['data'];
$matchingInstances = [];
foreach ($instances as $instance) {
@@ -59,13 +82,18 @@ class ElixFormsController
continue;
}
$exportTags = $this->apiClient->getExportTags(
$exportTagsResponse = $this->apiClient->getExportTags(
$requestId,
$moduleTag,
$token,
$username,
$exportGroup
);
if ($exportTagsResponse['status'] !== 'success') {
return $this->respondWithJSendFailure($res, $exportTagsResponse);
}
$exportTags = $exportTagsResponse['data'];
$exportTagsByName = array_column($exportTags, null, 'name');
if (!\array_key_exists($fieldName, $exportTagsByName)) {
@@ -80,7 +108,12 @@ class ElixFormsController
}
}
return $res->json($matchingInstances);
return $res->json(['status' => 'success', 'data' => $matchingInstances]);
}
private function respondWithJSendFailure(Response $response, array $payload)
{
return $response->json($payload, $payload['status'] === 'fail' ? 400 : 502);
}
private function requiredString(array $values, string $key): ?string
+12 -4
View File
@@ -2,7 +2,8 @@
namespace Api\Core\Log;
use Monolog\Logger;
use Monolog\Handler\SyslogHandler;
use Monolog\Handler\FilterHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\UidProcessor;
use Monolog\Formatter\JsonFormatter;
@@ -10,9 +11,16 @@ class LoggerFactory {
public static function create(?string $name): Logger {
$ident = $name ?? 'api';
$logger = new Logger($ident);
$handler = new SyslogHandler($ident, LOG_USER);
$handler->setFormatter(new JsonFormatter());
$logger->pushHandler($handler);
$formatter = new JsonFormatter();
$stdoutHandler = new StreamHandler('php://stdout', Logger::DEBUG);
$stdoutHandler->setFormatter($formatter);
$logger->pushHandler(new FilterHandler($stdoutHandler, Logger::DEBUG, Logger::WARNING));
$stderrHandler = new StreamHandler('php://stderr', Logger::ERROR);
$stderrHandler->setFormatter($formatter);
$logger->pushHandler($stderrHandler);
$logger->pushProcessor(new UidProcessor());
return $logger;
}
@@ -1,8 +1,8 @@
<?php
namespace ElixForms\Auth;
namespace ElixForms\Clients;
use Api\Core\HttpClient;
use ElixForms\Exceptions\ElixFormsException;
use ElixForms\Enums\ElixFormsRequestStatus;
class ElixFormsApiClient
{
@@ -16,40 +16,47 @@ class ElixFormsApiClient
}
/**
* Restituisce le istanze del modulo, opzionalmente filtrate per stato.
* I flag di ElixFormsRequestStatus possono essere combinati con l'operatore |.
* Restituisce le istanze del modulo, opzionalmente filtrate per stato.
* Gli stati sono separati da virgole e diventano query parameter requestStatus ripetuti.
*
* @return array<int,array<string,mixed>>
* @return array{status:string,data?:array<int,array<string,mixed>>,message?:string,code?:int}
*/
public function lookupByStatus(
string $moduleTag,
string $authToken,
string $username,
?int $status = null
?string $requestStatuses = null
): array
{
$queryParts = [];
if ($status !== null) {
foreach (ElixFormsRequestStatus::toApiValues($status) as $statusValue) {
try {
foreach (ElixFormsRequestStatus::fromQueryParameter($requestStatuses) as $statusValue) {
$queryParts[] = 'requestStatus=' . rawurlencode($statusValue);
}
} catch (\InvalidArgumentException $exception) {
return $this->fail(['requestStatuses' => $exception->getMessage()]);
}
$queryParts[] = 'moduleTag=' . rawurlencode($moduleTag);
$url = "{$this->baseUrl}/api/request/lookup/by-status?" . implode('&', $queryParts);
$payload = $this->getJsonAsArray($url, $authToken, $username, 'LookupByStatus');
if ($payload['status'] !== 'success') {
return $payload;
}
$payload = $payload['data'];
$requests = $payload['value']['requests'] ?? [];
if (!\is_array($requests)) {
throw new ElixFormsException('Risposta LookupByStatus non valida: requests deve essere un array.');
return $this->error('Risposta LookupByStatus non valida: requests deve essere un array.');
}
return array_values($requests);
return $this->success(array_values($requests));
}
/**
* @param int|string $requestId
* @return array<int,array{name?:mixed,value?:mixed}>
* @return array{status:string,data?:array<int,array{name?:mixed,value?:mixed}>,message?:string,code?:int}
*/
public function getExportTags(
$requestId,
@@ -61,7 +68,7 @@ class ElixFormsApiClient
{
$exportGroup = trim($exportGroup);
if ($exportGroup === '') {
throw new \InvalidArgumentException('exportGroup deve essere una stringa non vuota.');
return $this->fail(['exportGroup' => 'deve essere una stringa non vuota.']);
}
$url = \sprintf(
@@ -72,13 +79,18 @@ class ElixFormsApiClient
rawurlencode($exportGroup)
);
$payload = $this->getJsonAsArray($url, $authToken, $username, 'GetExportTags');
if ($payload['status'] !== 'success') {
return $payload;
}
$payload = $payload['data'];
$exportTags = $payload['value']['exportTags'] ?? [];
if (!\is_array($exportTags)) {
throw new ElixFormsException('Risposta GetExportTags non valida: exportTags deve essere un array.');
return $this->error('Risposta GetExportTags non valida: exportTags deve essere un array.');
}
return array_values($exportTags);
return $this->success(array_values($exportTags));
}
private function getJsonAsArray(
@@ -94,13 +106,9 @@ class ElixFormsApiClient
'x-api-username' => $username,
]);
$status = $response['status'] ?? 500;
$status = (int) ($response['status'] ?? 500);
if ($status !== 200) {
throw new ElixFormsException(\sprintf(
'Errore durante %s. HTTP Status: %d',
$operation,
$status
));
return $this->error(\sprintf('Errore durante %s.', $operation), $status);
}
$payload = $response['json'] ?? null;
@@ -111,22 +119,33 @@ class ElixFormsApiClient
}
if (!\is_array($payload) || $jsonError !== JSON_ERROR_NONE) {
throw new ElixFormsException(\sprintf(
'Risposta non valida da %s: atteso JSON.',
$operation
));
return $this->error(\sprintf('Risposta non valida da %s: atteso JSON.', $operation));
}
$globalStatus = $payload['value']['globalStatus'] ?? null;
if ($globalStatus === 'ERROR') {
$description = $payload['value']['description'] ?? 'errore non specificato';
throw new ElixFormsException(\sprintf(
'%s ha restituito un errore: %s',
$operation,
$description
));
return $this->fail([
'operation' => $operation,
'description' => $description,
]);
}
return $payload;
return $this->success($payload);
}
private function success(array $data): array
{
return ['status' => 'success', 'data' => $data];
}
private function fail(array $data): array
{
return ['status' => 'fail', 'data' => $data];
}
private function error(string $message, int $code = 502): array
{
return ['status' => 'error', 'message' => $message, 'code' => $code];
}
}
@@ -1,7 +1,6 @@
<?php
namespace ElixForms\Auth;
namespace ElixForms\Clients;
use ElixForms\Exceptions\ElixFormsException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
@@ -19,10 +18,9 @@ class ElixFormsAuthenticationClient {
*
* @param string $username
* @param string $password
* @return string Il token di autenticazione (authToken)
* @throws ElixFormsException Se le credenziali sono errate o c'è un errore server
* @return array{status:string,data?:array{authToken:string},message?:string,code?:int}
*/
public function login(string $username, string $password): string {
public function login(string $username, string $password): array {
$url = "{$this->baseUrl}/services/api/authentication/login/v1";
try {
@@ -39,23 +37,23 @@ class ElixFormsAuthenticationClient {
$status = $response->getStatusCode();
if ($status !== 200) {
throw new ElixFormsException("Errore durante il login elixForms. HTTP Status: " . $status);
return $this->error('Errore durante il login elixForms.', $status);
}
$body = $response->getBody()->getContents();
$json = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE || empty($json)) {
throw new ElixFormsException("Risposta non valida dal server elixForms: atteso JSON.");
return $this->error('Risposta non valida dal server elixForms: atteso JSON.');
}
if (isset($json['value']['authToken'])) {
return $json['value']['authToken'];
return $this->success(['authToken' => $json['value']['authToken']]);
}
throw new ElixFormsException("authToken non trovato nella risposta del login elixForms.");
return $this->fail(['authToken' => 'non trovato nella risposta del login elixForms.']);
} catch (GuzzleException $e) {
throw new ElixFormsException("Errore di connessione al server elixForms: " . $e->getMessage(), 0, $e);
return $this->error('Errore di connessione al server elixForms.');
}
}
@@ -64,10 +62,9 @@ class ElixFormsAuthenticationClient {
*
* @param string $username
* @param string $token
* @return bool True se il logout ha successo
* @throws ElixFormsException Se c'è un errore durante il logout
* @return array{status:string,data?:array{loggedOut:bool},message?:string,code?:int}
*/
public function logout(string $username, string $token): bool {
public function logout(string $username, string $token): array {
$url = "{$this->baseUrl}/services/api/authentication/" . urlencode($username) . '/logout/v1';
try {
@@ -80,12 +77,24 @@ class ElixFormsAuthenticationClient {
$status = $response->getStatusCode();
if ($status !== 200) {
throw new ElixFormsException("Errore durante il logout elixForms. HTTP Status: " . $status);
return $this->error('Errore durante il logout elixForms.', $status);
}
return true;
return $this->success(['loggedOut' => true]);
} catch (GuzzleException $e) {
throw new ElixFormsException("Errore di connessione al server elixForms: " . $e->getMessage(), 0, $e);
return $this->error('Errore di connessione al server elixForms.');
}
}
private function success(array $data): array {
return ['status' => 'success', 'data' => $data];
}
private function fail(array $data): array {
return ['status' => 'fail', 'data' => $data];
}
private function error(string $message, int $code = 502): array {
return ['status' => 'error', 'message' => $message, 'code' => $code];
}
}
@@ -1,12 +1,11 @@
<?php
namespace ElixForms;
namespace ElixForms\Clients;
use ElixForms\Auth\ElixFormsAuthenticationClient as AuthClient;
use ElixForms\Exceptions\ElixFormsException;
use ElixForms\Clients\ElixFormsAuthenticationClient;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class ElixFormsClient
class ElixFormsGenericClient
{
private string $baseUrl;
private Client $httpClient;
@@ -17,9 +16,9 @@ class ElixFormsClient
$this->httpClient = $httpClient ?? new Client(['timeout' => 10.0, 'http_errors' => false]);
}
public function auth(): AuthClient
public function auth(): ElixFormsAuthenticationClient
{
return new AuthClient($this->baseUrl, $this->httpClient);
return new ElixFormsAuthenticationClient($this->baseUrl, $this->httpClient);
}
public function request(string $method, string $path, ?array $body, array $headers = []): array
@@ -38,15 +37,31 @@ class ElixFormsClient
$contentType = $response->getHeaderLine('Content-Type');
$isJson = stripos($contentType, 'application/json') !== false;
return [
$data = [
'status' => $status,
'headers' => $response->getHeaders(),
'body' => $bodyRaw,
'is_json' => $isJson,
'json' => $isJson ? json_decode($bodyRaw, true) : null,
];
if ($status < 200 || $status >= 300) {
return $this->error('Richiesta a elixForms non riuscita.', $status);
}
return $this->success($data);
} catch (GuzzleException $e) {
throw new ElixFormsException('Errore di connessione al server elixForms: ' . $e->getMessage(), 0, $e);
return $this->error('Errore di connessione al server elixForms.');
}
}
private function success(array $data): array
{
return ['status' => 'success', 'data' => $data];
}
private function error(string $message, int $code = 502): array
{
return ['status' => 'error', 'message' => $message, 'code' => $code];
}
}
@@ -1,5 +1,5 @@
<?php
namespace ElixForms\Auth;
namespace ElixForms\Enums;
use InvalidArgumentException;
@@ -28,7 +28,7 @@ final class ElixFormsRequestStatus
/**
* @return array<int,string>
*/
public static function toApiValues(int $status): array
public static function toApiValues(?int $status = self::ALL): array
{
if ($status <= 0 || ($status & ~self::ALL) !== 0) {
throw new InvalidArgumentException('La combinazione di stati elixForms non è valida.');
@@ -43,4 +43,31 @@ final class ElixFormsRequestStatus
return $values;
}
/**
* @return array<int,string>
*/
public static function fromQueryParameter(?string $requestStatuses): array
{
if ($requestStatuses === null || trim($requestStatuses) === '') {
return [];
}
$allowedValues = array_values(self::API_VALUES);
$statuses = [];
foreach (explode(',', $requestStatuses) as $requestStatus) {
$requestStatus = trim($requestStatus);
if ($requestStatus === '') {
continue;
}
if (!in_array($requestStatus, $allowedValues, true)) {
throw new InvalidArgumentException('Lo stato elixForms richiesto non è valido.');
}
$statuses[] = $requestStatus;
}
return array_values(array_unique($statuses));
}
}
+17
View File
@@ -0,0 +1,17 @@
# Sample requests to try out authorization logic
###
# Authorization OK
@term=MA
GET http://localhost:8000/api/dipendenti/cerca?term={{term}}
Authorization: Basic elixforms_ws:password123
X-API-Key: myApiAccessToken
###
# Should return 401 Unauthorized
@term=MA
GET http://localhost:8000/api/dipendenti/cerca?term={{term}}
@@ -0,0 +1,26 @@
# Sample requests to trigger web service
###
# TAG not found
GET http://localhost:8000/elixforms/instances/search?moduleTag=ANTANI&fieldName=responsabileScientifico.codiceFiscale&fieldValue=MMM
###
# No data found (choose a TAG with very few instances...)
GET http://localhost:8000/elixforms/instances/search?moduleTag=RequestForm_EDILIZIA_RDA_MANUTENZIONE&fieldName=responsabileScientifico.codiceFiscale&fieldValue=MMM
###
# With one status
GET http://localhost:8000/elixforms/instances/search?requestStatuses=PROCESSED&moduleTag=RequestForm_EDILIZIA_RDA_MANUTENZIONE&fieldName=responsabileScientifico.codiceFiscale&fieldValue=MMM
###
# With two statuses (comma-separated)
GET http://localhost:8000/elixforms/instances/search?requestStatuses=PROCESSED,SUBMITTED&moduleTag=RequestForm_EDILIZIA_RDA_MANUTENZIONE&fieldName=responsabileScientifico.codiceFiscale&fieldValue=MMM
###
# With wrong status
GET http://localhost:8000/elixforms/instances/search?requestStatuses=UNKNOWN&moduleTag=RequestForm_EDILIZIA_RDA_MANUTENZIONE&fieldName=responsabileScientifico.codiceFiscale&fieldValue=MMM
+67 -16
View File
@@ -2,8 +2,7 @@
namespace Tests\Unit;
use Api\Core\HttpClient;
use ElixForms\Auth\ElixFormsApiClient;
use ElixForms\Auth\ElixFormsRequestStatus;
use ElixForms\Clients\ElixFormsApiClient;
use PHPUnit\Framework\TestCase;
final class ElixFormsApiClientTest extends TestCase
@@ -32,16 +31,17 @@ final class ElixFormsApiClientTest extends TestCase
};
$client = new ElixFormsApiClient('https://example.test', $httpClient);
$requests = $client->lookupByStatus('MODULO TEST', 'token', 'user');
$response = $client->lookupByStatus('MODULO TEST', 'token', 'user');
self::assertSame([['requestId' => 123]], $requests);
self::assertSame('success', $response['status']);
self::assertSame([['requestId' => 123]], $response['data']);
self::assertStringNotContainsString('requestStatus=', $httpClient->url);
self::assertStringContainsString('moduleTag=MODULO%20TEST', $httpClient->url);
self::assertSame('Bearer token', $httpClient->headers['Authorization']);
self::assertSame('user', $httpClient->headers['x-api-username']);
}
public function testLookupByStatusExpandsCombinedFlags(): void
public function testLookupByStatusExpandsCommaSeparatedStatuses(): void
{
$httpClient = new class extends HttpClient {
public string $url = '';
@@ -67,7 +67,7 @@ final class ElixFormsApiClientTest extends TestCase
'MODULO',
'token',
'user',
ElixFormsRequestStatus::IN_PROGRESS | ElixFormsRequestStatus::PROCESSED
'IN_PROGRESS, PROCESSED'
);
self::assertStringContainsString('requestStatus=IN_PROGRESS', $httpClient->url);
@@ -75,11 +75,39 @@ final class ElixFormsApiClientTest extends TestCase
self::assertStringContainsString('requestStatus=PROCESSED', $httpClient->url);
}
public function testRequestStatusRejectsUnknownFlags(): void
public function testLookupByStatusOmitsStatusesWhenTheValueIsEmpty(): void
{
$this->expectException(\InvalidArgumentException::class);
$httpClient = new class extends HttpClient {
public string $url = '';
ElixFormsRequestStatus::toApiValues(8);
public function get(string $url, array $headers = [])
{
$this->url = $url;
return [
'status' => 200,
'json' => ['value' => ['globalStatus' => 'OK', 'requests' => []]],
];
}
};
$client = new ElixFormsApiClient('https://example.test', $httpClient);
$client->lookupByStatus('MODULO', 'token', 'user', ' ');
self::assertStringNotContainsString('requestStatus=', $httpClient->url);
}
public function testLookupByStatusReturnsFailForAnUnknownStatus(): void
{
$httpClient = new HttpClient();
$client = new ElixFormsApiClient('https://example.test', $httpClient);
$response = $client->lookupByStatus('MODULO', 'token', 'user', 'UNKNOWN');
self::assertSame([
'status' => 'fail',
'data' => ['requestStatuses' => 'Lo stato elixForms richiesto non è valido.'],
], $response);
}
public function testGetExportTagsSupportsVendorJsonContentTypeResponses(): void
@@ -107,14 +135,15 @@ final class ElixFormsApiClientTest extends TestCase
};
$client = new ElixFormsApiClient('https://example.test/', $httpClient);
$tags = $client->getExportTags(42, 'MODULO', 'token', 'user');
$response = $client->getExportTags(42, 'MODULO', 'token', 'user');
self::assertSame('success', $response['status']);
self::assertSame(
[['name' => 'contratto.id', 'value' => 'ABC-123']],
$tags
$response['data']
);
self::assertSame(
'https://example.test/eF/services/api/request/42/view/_DEFAULT/exportTags/get/v1?moduleTag=MODULO&exportGroup=API',
'https://example.test/services/api/request/42/view/_DEFAULT/exportTags/get/v1?moduleTag=MODULO&exportGroup=API',
$httpClient->url
);
}
@@ -146,12 +175,34 @@ final class ElixFormsApiClientTest extends TestCase
self::assertStringContainsString('exportGroup=REPORT%20ORE', $httpClient->url);
}
public function testGetExportTagsRejectsAnEmptyExportGroup(): void
public function testGetExportTagsReturnsFailForAnEmptyExportGroup(): void
{
$this->expectException(\InvalidArgumentException::class);
$httpClient = new HttpClient();
$client = new ElixFormsApiClient('https://example.test', $httpClient);
$client->getExportTags(42, 'MODULO', 'token', 'user', ' ');
$response = $client->getExportTags(42, 'MODULO', 'token', 'user', ' ');
self::assertSame([
'status' => 'fail',
'data' => ['exportGroup' => 'deve essere una stringa non vuota.'],
], $response);
}
public function testLookupByStatusReturnsErrorForAnUnsuccessfulHttpResponse(): void
{
$httpClient = new class extends HttpClient {
public function get(string $url, array $headers = [])
{
return ['status' => 503];
}
};
$client = new ElixFormsApiClient('https://example.test', $httpClient);
$response = $client->lookupByStatus('MODULO', 'token', 'user');
self::assertSame([
'status' => 'error',
'message' => 'Errore durante LookupByStatus.',
'code' => 503,
], $response);
}
}
+17 -9
View File
@@ -5,8 +5,8 @@ use Api\Controllers\V1\ElixFormsController;
use Api\Core\Config;
use Api\Core\Request;
use Api\Core\Response;
use ElixForms\Auth\ElixFormsApiClient;
use ElixForms\Auth\ElixFormsAuthenticationClient;
use ElixForms\Clients\ElixFormsApiClient;
use ElixForms\Clients\ElixFormsAuthenticationClient;
use PHPUnit\Framework\TestCase;
final class ElixFormsControllerTest extends TestCase
@@ -18,9 +18,9 @@ final class ElixFormsControllerTest extends TestCase
{
}
public function login(string $username, string $password): string
public function login(string $username, string $password): array
{
return 'token';
return ['status' => 'success', 'data' => ['authToken' => 'token']];
}
};
$apiClient = new class extends ElixFormsApiClient {
@@ -32,14 +32,18 @@ final class ElixFormsControllerTest extends TestCase
string $moduleTag,
string $authToken,
string $username,
?int $status = null
?string $requestStatuses = null
): array
{
return [
if ($requestStatuses !== 'IN_PROGRESS,PROCESSED') {
throw new \RuntimeException('requestStatuses non inoltrato al client.');
}
return ['status' => 'success', 'data' => [
['idDomanda' => 10],
['requestId' => 20],
['idRequest' => 30],
];
]];
}
public function getExportTags(
@@ -69,7 +73,7 @@ final class ElixFormsControllerTest extends TestCase
],
];
return [$tags[$requestId]];
return ['status' => 'success', 'data' => [$tags[$requestId]]];
}
};
$config = new class extends Config {
@@ -85,6 +89,7 @@ final class ElixFormsControllerTest extends TestCase
'moduleTag' => 'MODULO',
'fieldName' => 'contratto.id',
'fieldValue' => 'abc-123',
'requestStatuses' => 'IN_PROGRESS,PROCESSED',
'exportGroup' => ' CUSTOM ',
];
}
@@ -97,7 +102,10 @@ final class ElixFormsControllerTest extends TestCase
self::fail('La risposta avrebbe dovuto interrompere il flusso del test.');
} catch (CapturedResponseException $exception) {
self::assertSame(200, $exception->status);
self::assertSame([['requestId' => 20]], $exception->payload);
self::assertSame([
'status' => 'success',
'data' => [['requestId' => 20]],
], $exception->payload);
}
}