From a2f8a2afe3716fe1597a7742fe3228859ce27de5 Mon Sep 17 00:00:00 2001 From: Dmitry Gladyshev Date: Thu, 10 Mar 2022 19:23:50 +0300 Subject: [PATCH] Complete task mapping. Some tests. --- Makefile | 5 +- README.md | 2 +- composer.json | 1 - examples/antigate_task.php | 36 +++++++++++++ phpunit.xml.dist | 4 +- psalm.xml | 16 ------ src/Client.php | 6 +-- src/Configuration.php | 4 +- src/Entity.php | 35 +++++++++++++ src/Response/AbstractResponse.php | 10 ++-- src/Task/AbstractTask.php | 7 ++- src/Task/AntiGateTask.php | 43 ++++++++++++++++ src/Task/FunCaptchaTask.php | 8 +++ src/Task/FunCaptchaTaskProxyless.php | 30 +++++++++++ src/Task/GeeTestTask.php | 8 +++ src/Task/GeeTestTaskProxyless.php | 34 +++++++++++++ src/Task/HCaptchaTask.php | 8 +++ src/Task/HCaptchaTaskProxyless.php | 17 +++++++ src/Task/ImageToTextTask.php | 5 -- src/Task/RecaptchaV2EnterpriseTask.php | 8 +++ ...=> RecaptchaV2EnterpriseTaskProxyless.php} | 7 +-- tests/Unit/ConfigurationTest.php | 36 +++++++++++++ tests/Unit/EntityTest.php | 48 +++++++++++++++++ tests/Unit/Response/AbstractResponseTest.php | 51 +++++++++++++++++++ tests/Unit/Task/AbstractTaskTest.php | 35 +++++++++++++ 25 files changed, 419 insertions(+), 45 deletions(-) create mode 100644 examples/antigate_task.php delete mode 100644 psalm.xml create mode 100644 src/Task/AntiGateTask.php create mode 100644 src/Task/FunCaptchaTask.php create mode 100644 src/Task/FunCaptchaTaskProxyless.php create mode 100644 src/Task/GeeTestTask.php create mode 100644 src/Task/GeeTestTaskProxyless.php create mode 100644 src/Task/HCaptchaTask.php create mode 100644 src/Task/HCaptchaTaskProxyless.php create mode 100644 src/Task/RecaptchaV2EnterpriseTask.php rename src/Task/{RecaptchaV2EnterpriseProxylessTask.php => RecaptchaV2EnterpriseTaskProxyless.php} (88%) create mode 100644 tests/Unit/ConfigurationTest.php create mode 100644 tests/Unit/EntityTest.php create mode 100644 tests/Unit/Response/AbstractResponseTest.php create mode 100644 tests/Unit/Task/AbstractTaskTest.php diff --git a/Makefile b/Makefile index 72eec54..498148f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -check-all: lint cs psalm test +check-all: lint cs test cs: composer cs-check @@ -6,9 +6,6 @@ cs: cs-fix: composer cs-fix -psalm: - composer psalm - lint: composer lint diff --git a/README.md b/README.md index 3a4b555..3e4c825 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ $client = new \Anticaptcha\Client( $httpClient ); -$result = $client->resolveImage(__DIR__.'/data/yandex.gif'); +$result = $client->resolveImage(__DIR__.'/data/captcha.png'); var_dump($result->solution); diff --git a/composer.json b/composer.json index 2adfa39..b645bf6 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,6 @@ "require-dev": { "guzzlehttp/guzzle": "~7.4", "phpunit/phpunit": "~9.5", - "vimeo/psalm": "^4.15", "squizlabs/php_codesniffer": "^3.6", "overtrue/phplint": "^4.1", "dg/bypass-finals": "^1.3" diff --git a/examples/antigate_task.php b/examples/antigate_task.php new file mode 100644 index 0000000..59f37e8 --- /dev/null +++ b/examples/antigate_task.php @@ -0,0 +1,36 @@ + 'http://antigate.com/logintest.php', + 'templateName' => 'Sign-in and wait for control text', + 'variables' => [ + "login_input_css" => "#login", + "login_input_value" => "the login", + "password_input_css" => "#password", + "password_input_value" => "test password", + "control_text" => "You have been logged successfully" + ] +]); + +$createTaskResponse = $client->createTask($task); + +$getTaskResponse = $client->getTaskResult($createTaskResponse->getTaskId()); +$getTaskResponse->wait(5, 600); + +var_dump( + $getTaskResponse->getTaskId(), + $getTaskResponse->solution +); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c43d80e..cb76dc1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,7 +2,7 @@ - + src diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 66a4e4f..0000000 --- a/psalm.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/src/Client.php b/src/Client.php index 1dd8db1..d5dd3c4 100644 --- a/src/Client.php +++ b/src/Client.php @@ -424,13 +424,13 @@ public function getAppStats( /** * @param string $uri - * @param array $params + * @param array $payload * * @return RequestInterface * * @throws JsonException */ - private function createRequest(string $uri, array $params = []): RequestInterface + private function createRequest(string $uri, array $payload = []): RequestInterface { return new Request( 'POST', @@ -438,7 +438,7 @@ private function createRequest(string $uri, array $params = []): RequestInterfac [ 'Content-Type' => 'application/json' ], - json_encode($params, JSON_THROW_ON_ERROR), + json_encode($payload, JSON_THROW_ON_ERROR), '1.1' ); } diff --git a/src/Configuration.php b/src/Configuration.php index 6349002..aa55d19 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -4,6 +4,8 @@ final class Configuration implements ConfigurationInterface { + public const DEFAULT_API_URL = 'https://api.anti-captcha.com'; + private string $apiKey; private string $apiUrl; private ?string $languagePool; @@ -11,7 +13,7 @@ final class Configuration implements ConfigurationInterface public function __construct( string $clientKey, - string $apiUrl = 'https://api.anti-captcha.com', + string $apiUrl = self::DEFAULT_API_URL, ?string $languagePool = null, ?string $callbackUrl = null ) { diff --git a/src/Entity.php b/src/Entity.php index 2e0dea8..5fe8857 100644 --- a/src/Entity.php +++ b/src/Entity.php @@ -2,10 +2,24 @@ namespace Anticaptcha; +use InvalidArgumentException; +use ReflectionClass; +use ReflectionProperty; + abstract class Entity { public function __construct(array $properties = []) { + /* Validate required */ + + foreach ($this->getRequiredProperties() as $property) { + if (!isset($properties[$property])) { + throw new InvalidArgumentException( + sprintf('Property "%s" is required.', $property) + ); + } + } + /** * @var string $option * @var mixed $value @@ -16,7 +30,28 @@ public function __construct(array $properties = []) call_user_func([$this, $setter], $value); } elseif (property_exists($this, $option)) { $this->$option = $value; + } else { + throw new InvalidArgumentException( + sprintf('Property "%s" not found in class "%s".', $option, static::class) + ); + } + } + } + + /** + * @return string[] + */ + protected function getRequiredProperties(): array + { + $properties = []; + + foreach ((new ReflectionClass(static::class))->getProperties(ReflectionProperty::IS_PUBLIC) as $reflection) { + if ($reflection->hasDefaultValue()) { + continue; } + $properties[] = $reflection->getName(); } + + return $properties; } } diff --git a/src/Response/AbstractResponse.php b/src/Response/AbstractResponse.php index a7d8ff7..05f337d 100644 --- a/src/Response/AbstractResponse.php +++ b/src/Response/AbstractResponse.php @@ -30,23 +30,23 @@ abstract class AbstractResponse extends Entity public string $errorDescription = ''; /** - * @param ResponseInterface $response + * @param ResponseInterface $httpResponse * * @return $this * * @throws Exception */ - public static function fromHttpResponse(ResponseInterface $response): self + public static function fromHttpResponse(ResponseInterface $httpResponse): self { $properties = json_decode( - $response->getBody()->__toString(), + $httpResponse->getBody()->__toString(), true, JSON_THROW_ON_ERROR ); if (!is_array($properties)) { throw new RuntimeException( - sprintf('Unexpected API response. Dump: %s', var_export($response, true)) + sprintf('Unexpected API response. Dump: %s', var_export($httpResponse, true)) ); } @@ -55,7 +55,7 @@ public static function fromHttpResponse(ResponseInterface $response): self public function hasError(): bool { - return $this->errorId > 1; + return $this->errorId > 0; } public function getErrorId(): int diff --git a/src/Task/AbstractTask.php b/src/Task/AbstractTask.php index 0a8ce8c..1b2d9d5 100644 --- a/src/Task/AbstractTask.php +++ b/src/Task/AbstractTask.php @@ -8,7 +8,12 @@ abstract class AbstractTask extends Entity { - abstract public function getType(): string; + public function getType(): string + { + $parts = explode('\\', static::class); + + return end($parts); + } public function toArray(): array { diff --git a/src/Task/AntiGateTask.php b/src/Task/AntiGateTask.php new file mode 100644 index 0000000..12c6045 --- /dev/null +++ b/src/Task/AntiGateTask.php @@ -0,0 +1,43 @@ +getClientKey()); + self::assertEquals($apiUrl, $config->getApiUrl()); + self::assertEquals($languagePool, $config->getLanguagePool()); + self::assertEquals($callbackUrl, $config->getCallbackUrl()); + self::assertEquals(857, $config->getSoftId()); + } + + public function testClientKeyBuilder(): void + { + $config = Configuration::fromClientKey('key'); + + self::assertEquals('key', $config->getClientKey()); + self::assertEquals(Configuration::DEFAULT_API_URL, $config->getApiUrl()); + self::assertNull($config->getLanguagePool()); + self::assertNull($config->getCallbackUrl()); + self::assertEquals(857, $config->getSoftId()); + } +} diff --git a/tests/Unit/EntityTest.php b/tests/Unit/EntityTest.php new file mode 100644 index 0000000..35e9138 --- /dev/null +++ b/tests/Unit/EntityTest.php @@ -0,0 +1,48 @@ + 'test' + ]); + + self::assertEquals($entity->requiredAttribute, 'test'); + + $this->expectException(\InvalidArgumentException::class); + + new TestEntity(); + } + + public function testOptionalAttributeUnfilled(): void + { + $entity = new TestEntity([ + 'requiredAttribute' => 'test' + ]); + + self::assertNull($entity->optionalAttribute); + } + + public function testOptionalAttributeFilled(): void + { + $entity = new TestEntity([ + 'requiredAttribute' => 'test', + 'optionalAttribute' => 'testOptional' + ]); + + self::assertEquals($entity->optionalAttribute, 'testOptional'); + } +} + + +class TestEntity extends Entity +{ + public string $requiredAttribute; + public ?string $optionalAttribute = null; +} \ No newline at end of file diff --git a/tests/Unit/Response/AbstractResponseTest.php b/tests/Unit/Response/AbstractResponseTest.php new file mode 100644 index 0000000..ee03e98 --- /dev/null +++ b/tests/Unit/Response/AbstractResponseTest.php @@ -0,0 +1,51 @@ +hasError()); + self::assertEquals(1, $response->getErrorId()); + self::assertEquals('ERROR_KEY_DOES_NOT_EXIST', $response->getErrorCode()); + self::assertEquals('Account authorization key not found in the system', $response->getErrorDescription()); + + self::assertNull($response->payload); + } + + public function testCorrectPayload(): void + { + $httpResponse = new Response( + 200, + [], + '{"errorId": 0,"payload":"test"}' + ); + + $response = TestResponse::fromHttpResponse($httpResponse); + + self::assertEquals(false, $response->hasError()); + self::assertEquals(0, $response->getErrorId()); + self::assertEquals('', $response->getErrorCode()); + self::assertEquals('', $response->getErrorDescription()); + + self::assertEquals('test', $response->payload); + } +} + +class TestResponse extends AbstractResponse +{ + public ?string $payload = null; +} \ No newline at end of file diff --git a/tests/Unit/Task/AbstractTaskTest.php b/tests/Unit/Task/AbstractTaskTest.php new file mode 100644 index 0000000..fc74fb4 --- /dev/null +++ b/tests/Unit/Task/AbstractTaskTest.php @@ -0,0 +1,35 @@ + 'test' + ]); + + self::assertEquals('TestTask', $task->getType()); + } + + public function testToArray(): void + { + $task = new TestTask([ + 'requiredAttribute' => 'test', + 'optionalAttribute' => 'testOptional' + ]); + + self::assertArrayHasKey('requiredAttribute', $task->toArray()); + self::assertArrayHasKey('optionalAttribute', $task->toArray()); + } +} + +class TestTask extends AbstractTask +{ + public string $requiredAttribute; + public ?string $optionalAttribute = null; +} \ No newline at end of file