diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a75a8df7a0..64e47bc785 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: PHPUnit Tests on: push: - branches: [ master, main ] + branches: [ master, main, test_api ] pull_request: - branches: [ master, main ] + branches: [ master, main, test_api ] env: DB_NAME: facturascripts_test @@ -161,6 +161,31 @@ jobs: EOF fi + - name: Add api to file + run: | + echo "define('FS_API_KEY', 'prueba');" >> config.php + - name: Start PHP built-in server + run: | + php -S 127.0.0.1:8000 -t . & + sleep 2 + + - name: Seed database + run: | + if [ "${{ matrix.database.type }}" = "mysql" ] || [ "${{ matrix.database.type }}" = "mariadb" ]; then + mysql --host=${{ env.DB_HOST }} --user=${{ env.DB_USER }} --password=${{ env.DB_PASS }} ${{ env.DB_NAME }} < seed.sql + else + PGPASSWORD=${{ env.DB_PASS }} psql -h ${{ env.DB_HOST }} -U ${{ env.DB_USER }} -d ${{ env.DB_NAME }} -f seed.sql + fi + + - name: Run PHPUnit API tests + run: | + if [ -f phpunit-api.xml ] || [ -f phpunit-api.xml.dist ]; then + vendor/bin/phpunit --configuration phpunit-api.xml --coverage-text --coverage-clover=coverage.xml + else + echo "No PHPUnit configuration found. Running with default settings..." + vendor/bin/phpunit --bootstrap vendor/autoload.php tests/ + fi + - name: Run PHPUnit tests run: | if [ -f phpunit.xml ] || [ -f phpunit.xml.dist ]; then diff --git a/Core/Model/ApiAccess.php b/Core/Model/ApiAccess.php index 13c1c0366c..cac29cd497 100644 --- a/Core/Model/ApiAccess.php +++ b/Core/Model/ApiAccess.php @@ -131,6 +131,26 @@ public static function primaryColumn(): string return 'id'; } + /** + * Update HTTP method permissions for this API resource and save the changes. + * + * @param bool $get Whether GET is allowed. + * @param bool $post Whether POST is allowed. + * @param bool $put Whether PUT is allowed. + * @param bool $delete Whether DELETE is allowed. + * + * @return bool True if saved successfully, false otherwise. + */ + public function setAllowed(bool $get, bool $post, bool $put, bool $delete): bool + { + $this->allowget = $get; + $this->allowpost = $post; + $this->allowput = $put; + $this->allowdelete = $delete; + + return $this->save(); + } + public static function tableName(): string { return 'api_access'; diff --git a/Core/Model/ApiKey.php b/Core/Model/ApiKey.php index 1d4163d0d4..c32aa4242d 100644 --- a/Core/Model/ApiKey.php +++ b/Core/Model/ApiKey.php @@ -19,7 +19,9 @@ namespace FacturaScripts\Core\Model; +use FacturaScripts\Core\Base\DataBase\DataBaseWhere; use FacturaScripts\Core\Tools; +use FacturaScripts\Dinamic\Model\ApiAccess; /** * ApiKey model to manage the connection tokens through the api @@ -53,6 +55,38 @@ class ApiKey extends Base\ModelClass /** @var string */ public $nick; + /** + * Adds a new API access entry for the given resource with the specified permissions. + * + * If the resource already exists for this API key, no changes are made. + * + * @param string $resource Resource name to grant access to. + * @param bool $state Initial permission state (applied to all methods). + * + * @return bool True if created or already exists, false on failure. + */ + public function addResourceAccess(string $resource, bool $state = false): bool + { + if (false !== $this->getResourceAccess($resource)) { + return true; // already exists + } + + $apiAccess = new ApiAccess(); + + $apiAccess->idapikey = $this->id; + $apiAccess->resource = $resource; + $apiAccess->allowdelete = $state; + $apiAccess->allowget = $state; + $apiAccess->allowpost = $state; + $apiAccess->allowput = $state; + + if (false === $apiAccess->save()) { + return false; + } + + return true; + } + public function clear() { parent::clear(); @@ -62,6 +96,31 @@ public function clear() $this->fullaccess = false; } + /** + * Retrieves the API access entry for the specified resource. + * + * Use addResourceAccess() first if the resource does not exist. + * + * @param string $resource Resource name to look up. + * + * @return ApiAccess|bool The ApiAccess object if found, false otherwise. + */ + public function getResourceAccess(string $resource): ApiAccess|bool + { + $apiAccess = new ApiAccess(); + + $where = [ + new DataBaseWhere('idapikey', $this->id), + new DataBaseWhere('resource', $resource) + ]; + + if ($apiAccess->loadFromCode('', $where)) { + return $apiAccess; + } else { + return false; + } + } + public static function primaryColumn(): string { return 'id'; diff --git a/Core/Template/ApiController.php b/Core/Template/ApiController.php index 18b2548cd4..907607c982 100644 --- a/Core/Template/ApiController.php +++ b/Core/Template/ApiController.php @@ -130,7 +130,7 @@ private function clientHasManyIncidents(): bool $ipCount++; } } - return $ipCount > self::MAX_INCIDENT_COUNT; + return $ipCount >= self::MAX_INCIDENT_COUNT; } private function getIpList(): array diff --git a/Test/API/CRUDTest.php b/Test/API/CRUDTest.php new file mode 100644 index 0000000000..e62bca8852 --- /dev/null +++ b/Test/API/CRUDTest.php @@ -0,0 +1,99 @@ +startAPIServer(); + } + + public function testListResources() + { + + $result = $this->makeGETCurl(); + + $expected = [ 'resources' => $this->getResourcesList() ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + + } + + public function testCreateData(){ + $form = [ + 'coddivisa' => '123', + 'descripcion' => 'Divisa 123', + ]; + + + $result = $this->makePOSTCurl("divisas", $form); + + + $expected = [ + 'ok' => 'Registro actualizado correctamente.', + 'data' => [ + 'coddivisa' => '123', + 'codiso' => null, + 'descripcion' => 'Divisa 123', + 'simbolo' => '?', + 'tasaconv' => 1, + 'tasaconvcompra' => 1 + ] + ]; + + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + + public function testUpdateData(){ + $result = $this->makePUTCurl("divisas/123", [ + 'descripcion' => 'Divisa 123 Actualizada' + ]); + $expected = [ + 'ok' => 'Registro actualizado correctamente.', + 'data' => [ + 'coddivisa' => '123', + 'codiso' => null, + 'descripcion' => 'Divisa 123 Actualizada', + 'simbolo' => '?', + 'tasaconv' => 1, + 'tasaconvcompra' => 1 + ] + ]; + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testDeleteData() + { + $result = $this->makeDELETECurl("divisas/123"); + + $expected = [ + 'ok' => 'Registro eliminado correctamente!', + 'data' => [ + 'coddivisa' => '123', + 'codiso' => null, + 'descripcion' => 'Divisa 123 Actualizada', + 'simbolo' => '?', + 'tasaconv' => 1, + 'tasaconvcompra' => 1 + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + protected function tearDown(): void + { + $this->stopAPIServer(); + $this->logErrors(); + } +} diff --git a/Test/API/ParametersTest.php b/Test/API/ParametersTest.php new file mode 100644 index 0000000000..e0e50cb1eb --- /dev/null +++ b/Test/API/ParametersTest.php @@ -0,0 +1,345 @@ +startAPIServer(); + } + + public function testListResources() + { + + $result = $this->makeGETCurl(); + + $expected = [ 'resources' => $this->getResourcesList() ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + + } + + public function testFilterLike() + { + $result = $this->makeGETCurl('pais?filter[nombre_like]=Esp'); + + $expected = [ + [ + "alias" => null, + "codiso" => "ES", + "codpais" => "ESP", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 40.4637, + "longitude" => -3.7492, + "nick" => null, + "nombre" => "España", + "telephone_prefix" => "+34" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testFilterData(){ + $result = $this->makeGETCurl('pais?filter[codpais]=ESP'); + + $expected = [ + [ + "alias" => null, + "codiso" => "ES", + "codpais" => "ESP", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 40.4637, + "longitude" => -3.7492, + "nick" => null, + "nombre" => "España", + "telephone_prefix" => "+34" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testFilterGreaterThan() + { + $result = $this->makeGETCurl('pais?filter[latitude_gt]=71.7069'); + + $expected = [ + [ + "alias" => null, + "codiso" => "SJ", + "codpais" => "SJM", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 77.5536, + "longitude" => 23.6703, + "nick" => null, + "nombre" => "Svalbard y Jan Mayen", + "telephone_prefix" => "" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testFilterGreaterThanOrEqual(){ + $result = $this->makeGETCurl('pais?filter[latitude_gte]=71.7069'); + + $expected = [ + [ + "alias" => null, + "codiso" => "GL", + "codpais" => "GRL", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 71.7069, + "longitude" => -42.6043, + "nick" => null, + "nombre" => "Groenlandia", + "telephone_prefix" => "+299" + ], + [ + "alias" => null, + "codiso" => "SJ", + "codpais" => "SJM", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 77.5536, + "longitude" => 23.6703, + "nick" => null, + "nombre" => "Svalbard y Jan Mayen", + "telephone_prefix" => "" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testFilterLessThan() + { + $result = $this->makeGETCurl('pais?filter[latitude_lt]=-54.4296'); + + $expected = [ + [ + "alias" => null, + "codiso" => "AQ", + "codpais" => "ATA", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => -90, + "longitude" => 0, + "nick" => null, + "nombre" => "Antártida", + "telephone_prefix" => "" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testFilterLessThanOrEqual() + { + $result = $this->makeGETCurl('pais?filter[latitude_lte]=-54.4296'); + + $expected = [ + [ + "alias" => null, + "codiso" => "AQ", + "codpais" => "ATA", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => -90, + "longitude" => 0, + "nick" => null, + "nombre" => "Antártida", + "telephone_prefix" => "" + ], + [ + "alias" => null, + "codiso" => "GS", + "codpais" => "SGS", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => -54.4296, + "longitude" => -36.5879, + "nick" => null, + "nombre" => "Islas Georgias del Sur y Sandwich del Sur", + "telephone_prefix" => "" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testFilterDistinct(){ + $result = $this->makeGETCurl('pais?filter[latitude_lte]=-54.4296&filter[latitude_neq]=-90'); + + $expected = [ + [ + "alias" => null, + "codiso" => "GS", + "codpais" => "SGS", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => -54.4296, + "longitude" => -36.5879, + "nick" => null, + "nombre" => "Islas Georgias del Sur y Sandwich del Sur", + "telephone_prefix" => "" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testsort(){ + $result = $this->makeGETCurl('pais?filter[latitude_lte]=-54.4296&sort[latitude]=DESC'); + + $expected = [ + [ + "alias" => null, + "codiso" => "GS", + "codpais" => "SGS", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => -54.4296, + "longitude" => -36.5879, + "nick" => null, + "nombre" => "Islas Georgias del Sur y Sandwich del Sur", + "telephone_prefix" => "" + ], + [ + "alias" => null, + "codiso" => "AQ", + "codpais" => "ATA", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => -90, + "longitude" => 0, + "nick" => null, + "nombre" => "Antártida", + "telephone_prefix" => "" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + } + + public function testPagination(){ + $result = $this->makeGETCurl('pais?offset=0&limit=3'); + $expected = [ + [ + "alias" => null, + "codiso" => "AW", + "codpais" => "ABW", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 12.5211, + "longitude" => -69.9683, + "nick" => null, + "nombre" => "Aruba", + "telephone_prefix" => "+297" + ], + [ + "alias" => null, + "codiso" => "AF", + "codpais" => "AFG", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 33.9391, + "longitude" => 67.71, + "nick" => null, + "nombre" => "Afganistán", + "telephone_prefix" => "+93" + ], + [ + "alias" => null, + "codiso" => "AO", + "codpais" => "AGO", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 11.2027, + "longitude" => 17.8739, + "nick" => null, + "nombre" => "Angola", + "telephone_prefix" => "+244" + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + + $result = $this->makeGETCurl('pais?offset=3&limit=3'); + + $expected = [ + [ + "alias" => null, + "codiso" => "AI", + "codpais" => "AIA", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 18.2206, + "longitude" => -63.0686, + "nick" => null, + "nombre" => "Anguila", + "telephone_prefix" => "+1 264" + ], + [ + "alias" => null, + "codiso" => "AX", + "codpais" => "ALA", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 60.1785, + "longitude" => 19.9156, + "nick" => null, + "nombre" => "Islas Gland", + "telephone_prefix" => "" + ], + [ + "alias" => null, + "codiso" => "AL", + "codpais" => "ALB", + "creation_date" => null, + "last_nick" => null, + "last_update" => null, + "latitude" => 41.1533, + "longitude" => 20.1683, + "nick" => null, + "nombre" => "Albania", + "telephone_prefix" => "+355" + ] + ]; + } + + protected function tearDown(): void + { + $this->stopAPIServer(); + $this->logErrors(); + } +} diff --git a/Test/API/SecurityTest.php b/Test/API/SecurityTest.php new file mode 100644 index 0000000000..aa034f66ef --- /dev/null +++ b/Test/API/SecurityTest.php @@ -0,0 +1,204 @@ +startAPIServer(); + + $agencia = new AgenciaTransporte(); + $agencia = $agencia->get('TestTest'); + if($agencia !== false) { + $this->assertTrue($agencia->delete(), 'agenciaTransporte-cant-delete'); + } + + Cache::deleteMulti(ApiController::IP_LIST); + } + + // Test para comprobar el flujo de seguridad de la API facturascripts + public function testSecurityFlow() + { + + $form = [ + 'codtrans' => 'TestTest', + 'nombre' => 'La agencia inexistente', + ]; + + + // paso 1: API Enabled? + Tools::settingsSet('default', 'enable_api', false); + Tools::settingsSave(); + + $result = []; + + $expected = [ + "status" => "error", + "message" => "API desactivada. Puede activarla desde el panel de control" + ]; + + $result = $this->makePOSTCurl("agenciatransportes", $form); + + $this->assertEquals($expected, $result, 'response-not-equal'); + + Tools::settingsSet('default', 'enable_api', true); + Tools::settingsSave(); + + + // paso 2: clave de API incorrecta + $expected = [ + "status" => "error", + "message" => "Clave de API no válida" + ]; + + $this->setApiToken("invalid-token"); + + for ($attempt = 0; $attempt < ApiController::MAX_INCIDENT_COUNT; $attempt++) { + $result = $this->makePOSTCurl("agenciatransportes", $form); + $this->assertEquals($expected, $result, 'response-not-equal-' . $attempt); + } + + + // paso 3: IP baneada + $this->setApiToken('prueba'); + $expected = [ + "status" => "error", + "message" => "Por motivos de seguridad se ha bloqueado temporalmente el acceso desde su IP." + ]; + + $result = $this->makePOSTCurl("agenciatransportes", $form); + $this->assertEquals($expected, $result, 'response-not-equal-' . $attempt); + + Cache::deleteMulti(ApiController::IP_LIST); // limpiar cache de ips bloqueadas + $this->stopAPIServer(); + $this->startAPIServer(); + + + // paso 4: Allowed resource + $this->token = 'invalid-token'; + $result = $this->makePOSTCurl("agenciatransportes", $form); + + $expected = [ + "status" => "error", + "message" => "Clave de API no válida" + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + + + //paso 5: Allowed resource + // clave api desactivada + $ApiKeyObj = new ApiKey(); + $ApiKeyObj->clear(); + $ApiKeyObj->description = 'Clave de pruebas'; + $ApiKeyObj->nick = 'tester'; + $ApiKeyObj->enabled = false; + $this->assertTrue($ApiKeyObj->save(), 'can-not-save-key'); + + $this->setApiToken($ApiKeyObj->apikey); + + $expected = [ + "status" => "error", + "message" => "Clave de API no válida" + ]; + + $result = $this->makePOSTCurl("agenciatransportes", $form); + $this->assertEquals($expected, $result, 'response-not-equal'); + + // clave api sin permisos (pero activada) + $ApiKeyObj->enabled = true; + $this->assertTrue($ApiKeyObj->save(), 'can-not-save-key'); + + $expected = [ + "status" => "error", + "message" => "forbidden" + ]; + + $result = $this->makePOSTCurl("agenciatransportes", $form); + $this->assertEquals($expected, $result, 'response-not-equal'); + + // clave api con todos los permisos + $ApiKeyObj->fullaccess = true; + $this->assertTrue($ApiKeyObj->save(), 'can-not-save-key'); + + $expected = [ + "ok" => "Registro actualizado correctamente.", + "data" => [ + "activo" => true, + "codtrans" => "TestTest", + "nombre" => "La agencia inexistente", + "telefono" => null, + "web" => null + ] + ]; + + $result = $this->makePOSTCurl("agenciatransportes", $form); + $this->assertEquals($expected, $result, 'response-not-equal'); + + // clave api con permisos limitados + $ApiKeyObj->fullaccess = false; + $this->assertTrue($ApiKeyObj->save(), 'can-not-save-key'); + $this->assertTrue(ApiAccess::addResourcesToApiKey($ApiKeyObj->id, ['agenciatransportes'], true), 'can-not-add-resource'); + + $form = [ + 'nombre' => 'La agencia intangible', + 'activo' => false + ]; + + $result = $this->makePUTCurl("agenciatransportes/TestTest", $form); + + $expected = [ + "ok" => "Registro actualizado correctamente.", + "data" => [ + "activo" => false, + "codtrans" => "TestTest", + "nombre" => "La agencia intangible", + "telefono" => null, + "web" => null + ] + ]; + + $this->assertEquals($expected, $result, 'response-not-equal'); + + $expected = [ + "status" => "error", + "message" => "forbidden" + ]; + + $result = $this->makeGETCurl("divisas"); + $this->assertEquals($expected, $result, 'response-not-equal'); + + $this->assertTrue(ApiAccess::addResourcesToApiKey($ApiKeyObj->id, ['agenciatransportes'], false), 'can-not-add-resource'); + $result = $this->makeGETCurl("agenciatransportes"); + $this->assertEquals($expected, $result, 'response-not-equal'); + + $ApiKeyObj->delete(); + Cache::deleteMulti(ApiController::IP_LIST); + } + + protected function tearDown(): void + { + $agencia = new AgenciaTransporte(); + $agencia->get('TestTest'); + if($agencia !== false) { + $this->assertTrue($agencia->delete(), 'agenciaTransporte-cant-delete'); + } + + $this->stopAPIServer(); + $this->logErrors(); + } +} diff --git a/Test/Traits/ApiTrait.php b/Test/Traits/ApiTrait.php new file mode 100644 index 0000000000..6a4994b27c --- /dev/null +++ b/Test/Traits/ApiTrait.php @@ -0,0 +1,264 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +namespace FacturaScripts\Test\Traits; + +use FacturaScripts\Core\Tools; +use FacturaScripts\Dinamic\Model\ApiKey; + +trait ApiTrait +{ + private string $host = '127.0.0.2'; + private string $port = '8000'; + private string $document_root = '/../../'; + private string $router = 'index.php'; + + private string $url; + private string $token; // generado en start server y removido en stop server + private string $pid; + private string $command; + + private bool $defaultApiEnabled; + + private ApiKey $ApiKeyObj; + + protected function startAPIServer($enableAPI = true): void + { + $document_root = __DIR__ . $this->document_root; + $router = $document_root . $this->router; + + $this->defaultApiEnabled = Tools::settings('default', 'enable_api', false); + Tools::settingsSet('default', 'enable_api', $enableAPI); + Tools::settingsSave(); + + $ApiKeyObj = new ApiKey(); + // $ApiKeyObj->id = $IdKey.'Test'; + $ApiKeyObj->clear(); + $ApiKeyObj->description = 'Clave de pruebas'; + $ApiKeyObj->nick = 'tester'; + $ApiKeyObj->enabled = true; + $ApiKeyObj->fullaccess = true; + + $ApiKeyObj->save(); + $this->token = $ApiKeyObj->apikey; + $this->ApiKeyObj = $ApiKeyObj; + + $this->url = "http://{$this->host}:{$this->port}/api/3/"; + $this->command = "php -S {$this->host}:{$this->port} -t {$document_root} {$router} > /dev/null 2>&1 & echo $!"; + $this->pid = shell_exec($this->command); + sleep(1); + } + + protected function stopAPIServer(): void + { + Tools::settingsSet('default', 'enable_api', $this->defaultApiEnabled); + Tools::settingsSave(); + shell_exec("kill $this->pid"); + } + + protected function setApiUrl(string $url): void + { + $this->url = $url; + } + + protected function setApiToken(string $token): void + { + $this->token = $token; + } + + protected function makeGETCurl(string $params = ''): array + { + $ch = curl_init($this->url . $params); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Token: " . $this->token + ]); + $respuesta = curl_exec($ch); + curl_close($ch); + + $data = json_decode($respuesta, true); + if (json_last_error() === JSON_ERROR_NONE) { + return $data; + } else { + throw new \Exception('Error al decodificar la respuesta JSON: ' . json_last_error_msg()); + } + } + + protected function makePOSTCurl(string $params = '', array $data = []): array + { + $ch = curl_init($this->url . $params); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); // <-- Aquí el cambio + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Token: " . $this->token, + "Content-Type: application/x-www-form-urlencoded" // <-- Aquí el cambio + ]); + + $respuesta = curl_exec($ch); + curl_close($ch); + $data = json_decode($respuesta, true); + if (json_last_error() === JSON_ERROR_NONE) { + return $data; + } else { + echo $respuesta; + throw new \Exception('Error al decodificar la respuesta JSON: ' . json_last_error_msg()); + } + } + + protected function makePUTCurl(string $params = '', array $data = []): array + { + $ch = curl_init($this->url . $params); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); // <-- Cambiado aquí + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Token: " . $this->token, + "Content-Type: application/x-www-form-urlencoded" // <-- Cambiado aquí + ]); + + $respuesta = curl_exec($ch); + curl_close($ch); + + $data = json_decode($respuesta, true); + if (json_last_error() === JSON_ERROR_NONE) { + return $data; + } else { + throw new \Exception('Error al decodificar la respuesta JSON: ' . json_last_error_msg()); + } + } + + protected function makeDELETECurl(string $params = '', array $data = []): array + { + $ch = curl_init($this->url . $params); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); // <-- Cambiado aquí + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Token: " . $this->token, + "Content-Type: application/x-www-form-urlencoded" // <-- Cambiado aquí + ]); + + $respuesta = curl_exec($ch); + curl_close($ch); + + $data = json_decode($respuesta, true); + if (json_last_error() === JSON_ERROR_NONE) { + return $data; + } else { + throw new \Exception('Error al decodificar la respuesta JSON: ' . json_last_error_msg()); + } + } + + + protected function getResourcesList(): array + { + return [ + "agenciatransportes", + "agentes", + "albaranclientes", + "albaranproveedores", + "almacenes", + "apiaccess", + "apikeyes", + "asientos", + "atributos", + "atributovalores", + "attachedfilerelations", + "attachedfiles", + "ciudades", + "clientes", + "codemodeles", + "codigopostales", + "conceptopartidas", + "contactos", + "crearFacturaCliente", + "crearFacturaRectificativaCliente", + "cronjobes", + "cuentabancoclientes", + "cuentabancoproveedores", + "cuentabancos", + "cuentaespeciales", + "cuentas", + "diarios", + "divisas", + "doctransformations", + "ejercicios", + "emailnotifications", + "emailsentes", + "empresas", + "estadodocumentos", + "exportarFacturaCliente", + "fabricantes", + "facturaclientes", + "facturaproveedores", + "familias", + "formapagos", + "formatodocumentos", + "grupoclientes", + "identificadorfiscales", + "impuestos", + "impuestozonas", + "lineaalbaranclientes", + "lineaalbaranproveedores", + "lineafacturaclientes", + "lineafacturaproveedores", + "lineapedidoclientes", + "lineapedidoproveedores", + "lineapresupuestoclientes", + "lineapresupuestoproveedores", + "logmessages", + "pagefilteres", + "pageoptions", + "pages", + "pagoclientes", + "pagoproveedores", + "pais", + "partidas", + "pedidoclientes", + "pedidoproveedores", + "presupuestoclientes", + "presupuestoproveedores", + "productoimagenes", + "productoproveedores", + "productos", + "proveedores", + "provincias", + "puntointeresciudades", + "reciboclientes", + "reciboproveedores", + "regularizacionimpuestos", + "retenciones", + "roleaccess", + "roles", + "roleusers", + "secuenciadocumentos", + "series", + "settings", + "stocks", + "subcuentas", + "tarifas", + "totalmodeles", + "uploadFiles", + "users", + "variantes", + "workeventes" + ]; + } +} \ No newline at end of file diff --git a/phpunit-api.xml b/phpunit-api.xml new file mode 100644 index 0000000000..db79a44cd0 --- /dev/null +++ b/phpunit-api.xml @@ -0,0 +1,21 @@ + + + + + + Test/API/ + + + + \ No newline at end of file diff --git a/seed.sql b/seed.sql new file mode 100644 index 0000000000..268ceabed0 --- /dev/null +++ b/seed.sql @@ -0,0 +1,32 @@ +create database facturascripts; +use facturascripts; +create table proveedores ( + acreedor int not null, + cifnif varchar(20) not null, + codcliente varchar(20), + codimpuestoportes varchar(20), + codpago varchar(20), + codproveedor varchar(20) primary key, + codretencion varchar(20), + codserie varchar(20), + codsubcuenta varchar(20), + debaja int not null default 0, + email varchar(100), + fax varchar(50), + fechaalta date not null, + fechabaja date, + idcontacto int not null default 1, + langcode varchar(10) not null default 'es_ES', + nombre varchar(100) not null, + observaciones text, + personafisica int not null default 0, + razonsocial varchar(100) not null, + regimeniva varchar(50) not null default 'General', + telefono1 varchar(50), + telefono2 varchar(50), + tipoidfiscal varchar(10) not null default 'NIF', + web varchar(100) +); + +INSERT INTO facturascripts.proveedores (acreedor,cifnif,codcliente,codimpuestoportes,codpago,codproveedor,codretencion,codserie,codsubcuenta,debaja,email,fax,fechaalta,fechabaja,idcontacto,langcode,nombre,observaciones,personafisica,razonsocial,regimeniva,telefono1,telefono2,tipoidfiscal,web) VALUES + (0,'',NULL,'IVA21',NULL,'1',NULL,NULL,'',0,'','','2025-06-03',NULL,1,'es_ES','prueba','',1,'prueba','General','','','NIF','') \ No newline at end of file