Skip to content

Commit

Permalink
Added more tests and scrutnizer fixes (#107)
Browse files Browse the repository at this point in the history
* Added scrutinizer fixes

* Removed dead code

* Added test for HTTPlug factory

* Added test for UploadedFile

* bump phpunit version

* Adapt to Buzz:1.0 code

* Added some more tests for Request

* Update UploadedFile.php

* Inline function instead

* Inline some more functions

* Improved PHPStan config

* Better run PHP Nightly
  • Loading branch information
Nyholm committed Feb 16, 2019
1 parent ac4cc7a commit 3c4d8e5
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 135 deletions.
10 changes: 8 additions & 2 deletions .travis.yml
Expand Up @@ -9,7 +9,6 @@ php:
- 7.1
- 7.2
- 7.3
- nightly

env:
global:
Expand All @@ -23,13 +22,20 @@ matrix:
fast_finish: true
allow_failures:
- php: nightly
env: COMPOSER_FLAGS="--ignore-platform-reqs"
include:
- php: 7.2
name: Backward compatibillity check
env: DEPENDENCIES="roave/backward-compatibility-check" TEST_COMMAND="./vendor/bin/roave-backward-compatibility-check"
- php: 7.1
- php: 7.2
name: Lowest version of dependencies
env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" COVERAGE=true TEST_COMMAND="composer test-ci"
- php: 7.2
name: PHPStan
env: DEPENDENCIES="phpstan/phpstan" TEST_COMMAND="./vendor/bin/phpstan"
- php: nightly
name: PHP 8.0
env: COMPOSER_FLAGS="--ignore-platform-reqs"

before_install:
- if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi;
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -58,7 +58,7 @@ composer require kriswallsmith/buzz

```php
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
$psr18Client = new Buzz\Client\Curl([], $psr17Factory);
$psr18Client = new Buzz\Client\Curl($psr17Factory);

$request = (new Psr17Factory())->createRequest('GET', 'http://tnyholm.se');
$response = $psr18Client->sendRequest($request);
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -21,7 +21,7 @@
"psr/http-factory": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^7.0",
"phpunit/phpunit": "^7.5",
"php-http/psr7-integration-tests": "dev-master",
"http-interop/http-factory-tests": "dev-master"
},
Expand Down
27 changes: 24 additions & 3 deletions phpstan.neon.dist
@@ -1,4 +1,25 @@
parameters:
level: 3
paths:
- src
level: 5
paths:
- src

ignoreErrors:
-
message: '#Strict comparison using === between false and true will always evaluate to false#'
path: %currentWorkingDirectory%/src/UploadedFile.php

-
message: '#Strict comparison using === between null and string will always evaluate to false#'
path: %currentWorkingDirectory%/src/Response.php

-
message: '#Result of && is always false.#'
path: %currentWorkingDirectory%/src/Response.php

-
message: '#Strict comparison using !== between null and null will always evaluate to false#'
path: %currentWorkingDirectory%/src/ServerRequest.php

-
message: '#Result of && is always false#'
path: %currentWorkingDirectory%/src/ServerRequest.php
4 changes: 2 additions & 2 deletions src/RequestTrait.php
Expand Up @@ -103,9 +103,9 @@ private function updateHostFromUri(): void
if (isset($this->headerNames['host'])) {
$header = $this->headerNames['host'];
} else {
$header = 'Host';
$this->headerNames['host'] = 'Host';
$this->headerNames['host'] = $header = 'Host';
}

// Ensure Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
$this->headers = [$header => [$host]] + $this->headers;
Expand Down
132 changes: 29 additions & 103 deletions src/UploadedFile.php
Expand Up @@ -40,7 +40,7 @@ final class UploadedFile implements UploadedFileInterface
/** @var bool */
private $moved = false;

/** @var int|null */
/** @var int */
private $size;

/** @var StreamInterface|null */
Expand All @@ -55,90 +55,47 @@ final class UploadedFile implements UploadedFileInterface
*/
public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
{
$this->setError($errorStatus);
$this->setSize($size);
$this->setClientFilename($clientFilename);
$this->setClientMediaType($clientMediaType);

if ($this->isOk()) {
$this->setStreamOrFile($streamOrFile);
}
}

/**
* Depending on the value set file or stream variable.
*
* @param string|resource|StreamInterface $streamOrFile
*
* @throws \InvalidArgumentException
*/
private function setStreamOrFile($streamOrFile): void
{
if (\is_string($streamOrFile)) {
$this->file = $streamOrFile;
} elseif (\is_resource($streamOrFile)) {
$this->stream = Stream::create($streamOrFile);
} elseif ($streamOrFile instanceof StreamInterface) {
$this->stream = $streamOrFile;
} else {
throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants.');
}
}

private function setError($error): void
{
if (false === \is_int($error)) {
throw new \InvalidArgumentException('Upload file error status must be an integer');
}

if (!isset(self::ERRORS[$error])) {
throw new \InvalidArgumentException('Invalid error status for UploadedFile');
}

$this->error = $error;
}

private function setSize($size): void
{
if (false === \is_int($size)) {
throw new \InvalidArgumentException('Upload file size must be an integer');
}

$this->size = $size;
}

private function setClientFilename($clientFilename): void
{
if (null !== $clientFilename && !\is_string($clientFilename)) {
throw new \InvalidArgumentException('Upload file client filename must be a string or null');
}

$this->clientFilename = $clientFilename;
}

private function setClientMediaType($clientMediaType): void
{
if (null !== $clientMediaType && !\is_string($clientMediaType)) {
throw new \InvalidArgumentException('Upload file client media type must be a string or null');
}

$this->error = $errorStatus;
$this->size = $size;
$this->clientFilename = $clientFilename;
$this->clientMediaType = $clientMediaType;
}

/**
* @return bool return true if there is no upload error
*/
private function isOk(): bool
{
return \UPLOAD_ERR_OK === $this->error;
if (\UPLOAD_ERR_OK === $this->error) {
// Depending on the value set file or stream variable.
if (\is_string($streamOrFile)) {
$this->file = $streamOrFile;
} elseif (\is_resource($streamOrFile)) {
$this->stream = Stream::create($streamOrFile);
} elseif ($streamOrFile instanceof StreamInterface) {
$this->stream = $streamOrFile;
} else {
throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
}
}
}

/**
* @throws \RuntimeException if is moved or not ok
*/
private function validateActive(): void
{
if (false === $this->isOk()) {
if (\UPLOAD_ERR_OK !== $this->error) {
throw new \RuntimeException('Cannot retrieve stream due to upload error');
}

Expand Down Expand Up @@ -175,7 +132,15 @@ public function moveTo($targetPath): void
if ($stream->isSeekable()) {
$stream->rewind();
}
$this->copyToStream($stream, Stream::create(\fopen($targetPath, 'w')));

// Copy the contents of a stream into another stream until end-of-file.
$dest = Stream::create(\fopen($targetPath, 'w'));
while (!$stream->eof()) {
if (!$dest->write($stream->read(1048576))) {
break;
}
}

$this->moved = true;
}

Expand All @@ -184,7 +149,7 @@ public function moveTo($targetPath): void
}
}

public function getSize(): ?int
public function getSize(): int
{
return $this->size;
}
Expand All @@ -203,43 +168,4 @@ public function getClientMediaType(): ?string
{
return $this->clientMediaType;
}

/**
* Copy the contents of a stream into another stream until the given number
* of bytes have been read.
*
* @author Michael Dowling and contributors to guzzlehttp/psr7
*
* @param StreamInterface $source Stream to read from
* @param StreamInterface $dest Stream to write to
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream
*
* @throws \RuntimeException on error
*/
private function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1): void
{
if (-1 === $maxLen) {
while (!$source->eof()) {
if (!$dest->write($source->read(1048576))) {
break;
}
}

return;
}

$bytes = 0;
while (!$source->eof()) {
$buf = $source->read($maxLen - $bytes);
if (!($len = \strlen($buf))) {
break;
}
$bytes += $len;
$dest->write($buf);
if ($bytes === $maxLen) {
break;
}
}
}
}
31 changes: 11 additions & 20 deletions src/Uri.php
Expand Up @@ -51,7 +51,17 @@ public function __construct(string $uri = '')
throw new \InvalidArgumentException("Unable to parse URI: $uri");
}

$this->applyParts($parts);
// Apply parse_url parts to a URI.
$this->scheme = isset($parts['scheme']) ? \strtolower($parts['scheme']) : '';
$this->userInfo = $parts['user'] ?? '';
$this->host = isset($parts['host']) ? \strtolower($parts['host']) : '';
$this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
$this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
$this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $parts['pass'];
}
}
}

Expand Down Expand Up @@ -211,25 +221,6 @@ public function withFragment($fragment): self
return $new;
}

/**
* Apply parse_url parts to a URI.
*
* @param array $parts Array of parse_url parts to apply
*/
private function applyParts(array $parts): void
{
$this->scheme = isset($parts['scheme']) ? \strtolower($parts['scheme']) : '';
$this->userInfo = $parts['user'] ?? '';
$this->host = isset($parts['host']) ? \strtolower($parts['host']) : '';
$this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
$this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
$this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
if (isset($parts['pass'])) {
$this->userInfo .= ':' . $parts['pass'];
}
}

/**
* Create a URI string from its various parts.
*/
Expand Down
63 changes: 63 additions & 0 deletions tests/Factory/HttplugFactoryTest.php
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Tests\Nyholm\Psr7\Factory;

use Nyholm\Psr7\Factory\HttplugFactory;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

class HttplugFactoryTest extends TestCase
{
public function testCreateRequest()
{
$factory = new HttplugFactory();
$r = $factory->createRequest('POST', 'https://nyholm.tech', ['Content-Type' => 'text/html'], 'foobar', '2.0');

$this->assertEquals('POST', $r->getMethod());
$this->assertEquals('https://nyholm.tech', $r->getUri()->__toString());
$this->assertEquals('2.0', $r->getProtocolVersion());
$this->assertEquals('foobar', $r->getBody()->__toString());

$headers = $r->getHeaders();
$this->assertCount(2, $headers); // Including HOST
$this->assertArrayHasKey('Content-Type', $headers);
$this->assertEquals('text/html', $headers['Content-Type'][0]);
}

public function testCreateResponse()
{
$factory = new HttplugFactory();
$r = $factory->createResponse(217, 'Perfect', ['Content-Type' => 'text/html'], 'foobar', '2.0');

$this->assertEquals(217, $r->getStatusCode());
$this->assertEquals('Perfect', $r->getReasonPhrase());
$this->assertEquals('2.0', $r->getProtocolVersion());
$this->assertEquals('foobar', $r->getBody()->__toString());

$headers = $r->getHeaders();
$this->assertCount(1, $headers);
$this->assertArrayHasKey('Content-Type', $headers);
$this->assertEquals('text/html', $headers['Content-Type'][0]);
}

public function testCreateStream()
{
$factory = new HttplugFactory();
$stream = $factory->createStream('foobar');

$this->assertInstanceOf(StreamInterface::class, $stream);
$this->assertEquals('foobar', $stream->__toString());
}

public function testCreateUri()
{
$factory = new HttplugFactory();
$uri = $factory->createUri('https://nyholm.tech/foo');

$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertEquals('https://nyholm.tech/foo', $uri->__toString());
}
}

0 comments on commit 3c4d8e5

Please sign in to comment.