-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from HarmonyIO/url-validators
Implemented URL validation rules
- Loading branch information
Showing
5 changed files
with
372 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace HarmonyIO\Validation\Rule\Network\Url; | ||
|
||
use Amp\Artax\DnsException; | ||
use Amp\Promise; | ||
use Amp\Success; | ||
use HarmonyIO\HttpClient\Client\Client; | ||
use HarmonyIO\HttpClient\Message\Request; | ||
use HarmonyIO\HttpClient\Message\Response; | ||
use HarmonyIO\Validation\Rule\Rule; | ||
use function Amp\call; | ||
|
||
final class OkResponse implements Rule | ||
{ | ||
/** @var Client */ | ||
private $httpClient; | ||
|
||
public function __construct(Client $httpClient) | ||
{ | ||
$this->httpClient = $httpClient; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function validate($value): Promise | ||
{ | ||
if (!is_string($value)) { | ||
return new Success(false); | ||
} | ||
|
||
return call(function () use ($value) { | ||
if (!yield (new Url())->validate($value)) { | ||
return false; | ||
} | ||
|
||
try { | ||
/** @var Response $response */ | ||
$response = yield $this->httpClient->request(new Request($value)); | ||
} catch (DnsException $e) { | ||
return false; | ||
} | ||
|
||
return $response->getNumericalStatusCode() === 200; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace HarmonyIO\Validation\Rule\Network\Url; | ||
|
||
use Amp\Promise; | ||
use Amp\Success; | ||
use HarmonyIO\Validation\Rule\Rule; | ||
|
||
final class Url implements Rule | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function validate($value): Promise | ||
{ | ||
if (!is_string($value)) { | ||
return new Success(false); | ||
} | ||
|
||
return new Success(filter_var($value, FILTER_VALIDATE_URL) !== false); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace HarmonyIO\ValidationTest\Integration\Rule\Network\Url; | ||
|
||
use Amp\Artax\DefaultClient; | ||
use Amp\Redis\Client as RedisClient; | ||
use HarmonyIO\Cache\Provider\Redis; | ||
use HarmonyIO\HttpClient\Client\ArtaxClient; | ||
use HarmonyIO\PHPUnitExtension\TestCase; | ||
use HarmonyIO\Validation\Rule\Network\Url\OkResponse; | ||
|
||
class OkResponseTest extends TestCase | ||
{ | ||
/** @var ArtaxClient */ | ||
private $httpClient; | ||
|
||
//phpcs:ignore SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint | ||
public function setUp() | ||
{ | ||
$this->httpClient = new ArtaxClient(new DefaultClient(), new Redis(new RedisClient('tcp://localhost:6379'))); | ||
} | ||
|
||
public function testValidateReturnsFalseOnNotFoundResponse(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate('http://pieterhordijk.com/dlksjksjfkhdsfjk')); | ||
} | ||
|
||
public function testValidateReturnsFalseOnNonExistingDomain(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate('http://dkhj3kry43iufhr3e.example.com')); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenResponseHasErrorStatusCode(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate('https://httpbin.org/status/500')); | ||
} | ||
|
||
public function testValidateReturnsTrueOnRedirectedOkResponse(): void | ||
{ | ||
$this->assertTrue((new OkResponse($this->httpClient))->validate('http://pieterhordijk.com')); | ||
} | ||
|
||
public function testValidateReturnsTrueOnOkResponse(): void | ||
{ | ||
$this->assertTrue((new OkResponse($this->httpClient))->validate('https://pieterhordijk.com')); | ||
} | ||
|
||
public function testValidateReturnsTrueOnOkResponseWithPath(): void | ||
{ | ||
$this->assertTrue((new OkResponse($this->httpClient))->validate('https://pieterhordijk.com/contact')); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace HarmonyIO\ValidationTest\Unit\Rule\Network\Url; | ||
|
||
use Amp\Success; | ||
use HarmonyIO\HttpClient\Client\Client; | ||
use HarmonyIO\HttpClient\Message\Request; | ||
use HarmonyIO\HttpClient\Message\Response; | ||
use HarmonyIO\PHPUnitExtension\TestCase; | ||
use HarmonyIO\Validation\Rule\Network\Url\OkResponse; | ||
use HarmonyIO\Validation\Rule\Rule; | ||
use PHPUnit\Framework\MockObject\MockObject; | ||
use function Amp\Promise\wait; | ||
|
||
class OkResponseTest extends TestCase | ||
{ | ||
/** @var MockObject|Client */ | ||
private $httpClient; | ||
|
||
//phpcs:ignore SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint | ||
public function setUp() | ||
{ | ||
$this->httpClient = $this->createMock(Client::class); | ||
} | ||
|
||
public function testRuleImplementsInterface(): void | ||
{ | ||
$this->assertInstanceOf(Rule::class, new OkResponse($this->httpClient)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAnInteger(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate(1)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAFloat(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate(1.1)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingABoolean(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate(true)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAnArray(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate([])); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAnObject(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate(new \DateTimeImmutable())); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingNull(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate(null)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAResource(): void | ||
{ | ||
$resource = fopen('php://memory', 'r'); | ||
|
||
if ($resource === false) { | ||
$this->fail('Could not open the memory stream used for the test'); | ||
|
||
return; | ||
} | ||
|
||
$this->assertFalse((new OkResponse($this->httpClient))->validate($resource)); | ||
|
||
fclose($resource); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingACallable(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate(static function (): void { | ||
})); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAUrlWithoutProtocol(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate('pieterhordijk.com')); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAUrlWithoutHost(): void | ||
{ | ||
$this->assertFalse((new OkResponse($this->httpClient))->validate('https://')); | ||
} | ||
|
||
public function testValidatePassesUrlToClient(): void | ||
{ | ||
$this->httpClient | ||
->method('request') | ||
->willReturnCallback(function (Request $request) { | ||
$this->assertSame('https://pieterhordijk.com', $request->getArtaxRequest()->getUri()); | ||
|
||
$response = $this->createMock(Response::class); | ||
|
||
$response | ||
->method('getBody') | ||
->willReturn('foo') | ||
; | ||
|
||
return new Success($response); | ||
}) | ||
; | ||
|
||
wait((new OkResponse($this->httpClient))->validate('https://pieterhordijk.com')); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenRequestResultsInANon200Response(): void | ||
{ | ||
$this->httpClient | ||
->method('request') | ||
->willReturnCallback(function (Request $request) { | ||
$this->assertSame('https://pieterhordijk.com/foobar', $request->getArtaxRequest()->getUri()); | ||
|
||
$response = $this->createMock(Response::class); | ||
|
||
$response | ||
->method('getBody') | ||
->willReturn('foo') | ||
; | ||
|
||
$response | ||
->method('getNumericalStatusCode') | ||
->willReturn(404) | ||
; | ||
|
||
return new Success($response); | ||
}) | ||
; | ||
|
||
$this->assertFalse((new OkResponse($this->httpClient))->validate('https://pieterhordijk.com/foobar')); | ||
} | ||
|
||
public function testValidateReturnsTrueWhenClientReturnsOkResponse(): void | ||
{ | ||
$this->httpClient | ||
->method('request') | ||
->willReturnCallback(function (Request $request) { | ||
$this->assertSame('https://pieterhordijk.com/contact', $request->getArtaxRequest()->getUri()); | ||
|
||
$response = $this->createMock(Response::class); | ||
|
||
$response | ||
->method('getBody') | ||
->willReturn('foo') | ||
; | ||
|
||
$response | ||
->method('getNumericalStatusCode') | ||
->willReturn(200) | ||
; | ||
|
||
return new Success($response); | ||
}) | ||
; | ||
|
||
$this->assertTrue((new OkResponse($this->httpClient))->validate('https://pieterhordijk.com/contact')); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace HarmonyIO\ValidationTest\Unit\Rule\Network\Url; | ||
|
||
use HarmonyIO\PHPUnitExtension\TestCase; | ||
use HarmonyIO\Validation\Rule\Network\Url\Url; | ||
use HarmonyIO\Validation\Rule\Rule; | ||
|
||
class UrlTest extends TestCase | ||
{ | ||
public function testRuleImplementsInterface(): void | ||
{ | ||
$this->assertInstanceOf(Rule::class, new Url()); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAnInteger(): void | ||
{ | ||
$this->assertFalse((new Url())->validate(1)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAFloat(): void | ||
{ | ||
$this->assertFalse((new Url())->validate(1.1)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingABoolean(): void | ||
{ | ||
$this->assertFalse((new Url())->validate(true)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAnArray(): void | ||
{ | ||
$this->assertFalse((new Url())->validate([])); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAnObject(): void | ||
{ | ||
$this->assertFalse((new Url())->validate(new \DateTimeImmutable())); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingNull(): void | ||
{ | ||
$this->assertFalse((new Url())->validate(null)); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAResource(): void | ||
{ | ||
$resource = fopen('php://memory', 'r'); | ||
|
||
if ($resource === false) { | ||
$this->fail('Could not open the memory stream used for the test'); | ||
|
||
return; | ||
} | ||
|
||
$this->assertFalse((new Url())->validate($resource)); | ||
|
||
fclose($resource); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingACallable(): void | ||
{ | ||
$this->assertFalse((new Url())->validate(static function (): void { | ||
})); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAUrlWithoutProtocol(): void | ||
{ | ||
$this->assertFalse((new Url())->validate('pieterhordijk.com')); | ||
} | ||
|
||
public function testValidateReturnsFalseWhenPassingAUrlWithoutHost(): void | ||
{ | ||
$this->assertFalse((new Url())->validate('https://')); | ||
} | ||
|
||
public function testValidateReturnsTrueWhenPassingAValidUrl(): void | ||
{ | ||
$this->assertTrue((new Url())->validate('https://pieterhordijk.com')); | ||
} | ||
|
||
public function testValidateReturnsTrueWhenPassingAValidUrlWithPort(): void | ||
{ | ||
$this->assertTrue((new Url())->validate('https://pieterhordijk.com:1337')); | ||
} | ||
} |