diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml
new file mode 100644
index 0000000..d9acade
--- /dev/null
+++ b/.github/workflows/code_quality.yml
@@ -0,0 +1,42 @@
+name: "Code Quality"
+
+on:
+ pull_request:
+ paths:
+ - "**.php"
+ - "phpcs.xml"
+ - ".github/workflows/code_quality.yml"
+
+jobs:
+ phpcs:
+ name: "PHP CodeSniffer"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code into the workspace
+ uses: actions/checkout@v2
+ - name: Run PHPCS
+ uses: chekalsky/phpcs-action@v1
+ phpmd:
+ name: "PHP MessDetector"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code into the workspace
+ uses: actions/checkout@v2
+ - name: Run PHPMD
+ uses: GeneaLabs/action-reviewdog-phpmd@1.0.0
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ level: 'warning'
+ reporter: github-pr-check
+ standard: './phpmd.xml'
+ target_directory: 'src'
+ phpstan:
+ name: PHPStan
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code into the workspace
+ uses: actions/checkout@v2
+ - name: Run PHPStan
+ uses: docker://oskarstark/phpstan-ga
+ with:
+ args: analyse src -c phpstan.neon --memory-limit=1G --no-progress
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..e3afaf0
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,38 @@
+name: Tests
+
+on:
+ push:
+ branches:
+ - '**'
+ tags-ignore:
+ - '*.*'
+ pull_request:
+
+jobs:
+ test:
+ name: "PHPUnit"
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php-version: ['7.1', '7.2', '7.3', '7.4', '8.0']
+ steps:
+ - name: Check out code into the workspace
+ uses: actions/checkout@v2
+ - name: Setup PHP ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ coverage: pcov
+ - name: Composer cache
+ uses: actions/cache@v2
+ with:
+ path: ${{ env.HOME }}/.composer/cache
+ key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
+ - name: Install dependencies
+ run: composer install -o
+ - name: Configure matchers
+ uses: mheap/phpunit-matcher-action@v1
+ - name: Run tests
+ run: composer run-script phpunit-ci
+ - name: Coverage
+ run: bash <(curl -s https://codecov.io/bash)
diff --git a/.gitignore b/.gitignore
index 6a78056..afbdeb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
composer.lock
vendor/*
.idea/*
+.php_cs.cache
+.phpunit.result.cache
+test-report.xml
diff --git a/composer.json b/composer.json
index c62ca0d..db1c10a 100644
--- a/composer.json
+++ b/composer.json
@@ -14,15 +14,50 @@
"Pock\\": "src/"
}
},
+ "autoload-dev": {
+ "psr-4": {
+ "Pock\\Tests\\": "tests/src/",
+ "Pock\\TestUtils\\": "tests/utils/"
+ }
+ },
"require": {
"php": ">=7.1.0",
"psr/http-client": "^1.0",
"psr/http-message": "^1.0",
"php-http/httplug": "^1.0 || ^2.0"
},
+ "require-dev": {
+ "squizlabs/php_codesniffer": "^3.6",
+ "phpmd/phpmd": "^2.10",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1",
+ "phpcompatibility/php-compatibility": "^9.3",
+ "phpstan/phpstan": "^0.12.87",
+ "jms/serializer": "^3.12",
+ "symfony/phpunit-bridge": "^5.2",
+ "symfony/var-dumper": "^5.2",
+ "symfony/serializer": "^5.2",
+ "nyholm/psr7": "^1.4"
+ },
"provide": {
"psr/http-client-implementation": "1.0",
"php-http/client-implementation": "1.0",
"php-http/async-client-implementation": "1.0"
+ },
+ "scripts": {
+ "phpunit": "./vendor/bin/simple-phpunit -c phpunit.xml.dist --coverage-text",
+ "phpunit-ci": "@php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude=\"~vendor~\" ./vendor/bin/simple-phpunit --teamcity -c phpunit.xml.dist",
+ "phpmd": "./vendor/bin/phpmd src text ./phpmd.xml",
+ "phpcs": "./vendor/bin/phpcs -p src --runtime-set testVersion 7.1-8.0 && ./vendor/bin/phpcs -p tests --runtime-set testVersion 7.1-8.0 --warning-severity=0",
+ "phpstan": "./vendor/bin/phpstan analyse -c phpstan.neon src --memory-limit=-1",
+ "lint:fix": "./vendor/bin/phpcbf src",
+ "lint": [
+ "@phpcs",
+ "@phpmd",
+ "@phpstan"
+ ],
+ "verify": [
+ "@lint",
+ "@phpunit"
+ ]
}
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..d12440d
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+ src/
+ tests/
+
diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml
new file mode 100644
index 0000000..3743ea2
--- /dev/null
+++ b/phpdoc.dist.xml
@@ -0,0 +1,21 @@
+
+
+ RetailCRM API Client
+
+
+ docs/build/cache
+
+
+
+ public
+
+
+
+
diff --git a/phpmd.xml b/phpmd.xml
new file mode 100644
index 0000000..550b68e
--- /dev/null
+++ b/phpmd.xml
@@ -0,0 +1,16 @@
+
+
+ Ruleset
+
+
+
+
+
+
+
+ tests/*
+
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..1494f51
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,5 @@
+parameters:
+ level: max
+ paths:
+ - src
+ - tests
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..d0314d3
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,39 @@
+
+
+
+
+
+ src
+ dev
+
+
+
+
+
+
+
+ tests/src
+
+
+
+
+
+
+
+
+
diff --git a/src/Client.php b/src/Client.php
index a14967e..1aea0e9 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -70,19 +70,19 @@ class Client implements ClientInterface, HttpClient, HttpAsyncClient
public function sendAsyncRequest(RequestInterface $request): Promise
{
foreach ($this->mocks as $mock) {
- if ($mock->isFired()) {
+ if (!$mock->available()) {
continue;
}
if ($mock->getMatcher()->matches($request)) {
if (null !== $mock->getResponse()) {
- $mock->markAsFired();
+ $mock->registerHit();
return new HttpFulfilledPromise($mock->getResponse());
}
if (null !== $mock->getThrowable()) {
- $mock->markAsFired();
+ $mock->registerHit();
return new HttpRejectedPromise($mock->getThrowable());
}
diff --git a/src/Creator/AbstractJmsSerializerCreator.php b/src/Creator/AbstractJmsSerializerCreator.php
new file mode 100644
index 0000000..0e68cf3
--- /dev/null
+++ b/src/Creator/AbstractJmsSerializerCreator.php
@@ -0,0 +1,55 @@
+build(), static::getFormat()); // @phpstan-ignore-line
+ }
+ } catch (Throwable $throwable) {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns format for the serializer;
+ *
+ * @return string
+ */
+ abstract protected static function getFormat(): string;
+}
diff --git a/src/Creator/JmsJsonSerializerCreator.php b/src/Creator/JmsJsonSerializerCreator.php
new file mode 100644
index 0000000..62e1095
--- /dev/null
+++ b/src/Creator/JmsJsonSerializerCreator.php
@@ -0,0 +1,27 @@
+getUri()) === strtolower((string) $this->uri);
+ return ((string) $request->getUri()) === ((string) $this->uri);
}
}
diff --git a/src/Mock.php b/src/Mock.php
index d96ec64..101ead7 100644
--- a/src/Mock.php
+++ b/src/Mock.php
@@ -30,8 +30,11 @@ class Mock implements MockInterface
/** @var \Throwable|null */
private $throwable;
- /** @var bool */
- private $fired = false;
+ /** @var int */
+ private $hits;
+
+ /** @var int */
+ private $maxHits;
/**
* Mock constructor.
@@ -39,30 +42,40 @@ class Mock implements MockInterface
* @param \Pock\Matchers\RequestMatcherInterface $matcher
* @param \Psr\Http\Message\ResponseInterface|null $response
* @param \Throwable|null $throwable
+ * @param int $maxHits
*/
- public function __construct(RequestMatcherInterface $matcher, ?ResponseInterface $response, ?Throwable $throwable)
- {
+ public function __construct(
+ RequestMatcherInterface $matcher,
+ ?ResponseInterface $response,
+ ?Throwable $throwable,
+ int $maxHits
+ ) {
$this->matcher = $matcher;
$this->response = $response;
$this->throwable = $throwable;
+ $this->maxHits = $maxHits;
+ $this->hits = 0;
}
- public function markAsFired(): MockInterface
+ /**
+ * @inheritDoc
+ */
+ public function registerHit(): MockInterface
{
- $this->fired = true;
+ ++$this->hits;
return $this;
}
/**
- * @return bool
+ * @inheritDoc
*/
- public function isFired(): bool
+ public function available(): bool
{
- return $this->fired;
+ return $this->hits < $this->maxHits;
}
/**
- * @return \Pock\Matchers\RequestMatcherInterface
+ * @inheritDoc
*/
public function getMatcher(): RequestMatcherInterface
{
@@ -70,7 +83,7 @@ class Mock implements MockInterface
}
/**
- * @return \Psr\Http\Message\ResponseInterface|null
+ * @inheritDoc
*/
public function getResponse(): ?ResponseInterface
{
@@ -78,7 +91,7 @@ class Mock implements MockInterface
}
/**
- * @return \Throwable|null
+ * @inheritDoc
*/
public function getThrowable(): ?Throwable
{
diff --git a/src/MockInterface.php b/src/MockInterface.php
index 77d75d9..31e6833 100644
--- a/src/MockInterface.php
+++ b/src/MockInterface.php
@@ -22,18 +22,18 @@ use Throwable;
interface MockInterface
{
/**
- * Marks mock as already used.
+ * Registers a hit to the mock.
*
* @return \Pock\MockInterface
*/
- public function markAsFired(): MockInterface;
+ public function registerHit(): MockInterface;
/**
- * Returns true if mock was not used yet.
+ * Returns true if mock is still can be used.
*
* @return bool
*/
- public function isFired(): bool;
+ public function available(): bool;
/**
* Returns matcher for the request.
diff --git a/src/PockBuilder.php b/src/PockBuilder.php
index aaf1f95..075602d 100644
--- a/src/PockBuilder.php
+++ b/src/PockBuilder.php
@@ -35,6 +35,9 @@ class PockBuilder
/** @var \Throwable|null */
private $throwable;
+ /** @var int */
+ private $maxHits;
+
/** @var \Pock\MockInterface[] */
private $mocks;
@@ -54,7 +57,7 @@ class PockBuilder
*
* @param string $scheme
*
- * @return $this
+ * @return self
*/
public function matchScheme(string $scheme = RequestScheme::HTTP): PockBuilder
{
@@ -66,7 +69,7 @@ class PockBuilder
*
* @param string $host
*
- * @return $this
+ * @return self
*/
public function matchHost(string $host): PockBuilder
{
@@ -100,6 +103,23 @@ class PockBuilder
return $this;
}
+ /**
+ * Repeat this mock provided amount of times.
+ * For example, if you pass 2 as an argument mock will be able to handle two identical requests.
+ *
+ * @param int $hits
+ *
+ * @return $this
+ */
+ public function repeat(int $hits): PockBuilder
+ {
+ if ($hits > 0) {
+ $this->maxHits = $hits;
+ }
+
+ return $this;
+ }
+
/**
* Resets the builder.
*
@@ -110,6 +130,7 @@ class PockBuilder
$this->matcher = new MultipleMatcher();
$this->response = null;
$this->throwable = null;
+ $this->maxHits = 1;
$this->mocks = [];
return $this;
@@ -143,9 +164,11 @@ class PockBuilder
$this->matcher->addMatcher(new AnyRequestMatcher());
}
- $this->mocks[] = new Mock($this->matcher, $this->response, $this->throwable);
+ $this->mocks[] = new Mock($this->matcher, $this->response, $this->throwable, $this->maxHits);
+ $this->matcher = new MultipleMatcher();
$this->response = null;
$this->throwable = null;
+ $this->maxHits = 1;
}
}
}
diff --git a/src/Promise/HttpRejectedPromise.php b/src/Promise/HttpRejectedPromise.php
index c0b6b16..0a36c51 100644
--- a/src/Promise/HttpRejectedPromise.php
+++ b/src/Promise/HttpRejectedPromise.php
@@ -62,6 +62,8 @@ class HttpRejectedPromise implements Promise
/**
* @inheritDoc
* @throws \Throwable
+ *
+ * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*/
public function wait($unwrap = true): void
{
diff --git a/src/Serializer/JmsSerializerDecorator.php b/src/Serializer/JmsSerializerDecorator.php
new file mode 100644
index 0000000..565cca9
--- /dev/null
+++ b/src/Serializer/JmsSerializerDecorator.php
@@ -0,0 +1,48 @@
+serializer = $serializer;
+ $this->format = $format;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function serialize($data): string
+ {
+ if (method_exists($this->serializer, 'serialize')) {
+ return $this->serializer->serialize($data, $this->format);
+ }
+
+ return '';
+ }
+}
diff --git a/src/Serializer/SerializerInterface.php b/src/Serializer/SerializerInterface.php
new file mode 100644
index 0000000..7080a22
--- /dev/null
+++ b/src/Serializer/SerializerInterface.php
@@ -0,0 +1,28 @@
+add('Pock\\Tests', __DIR__ . '/src');
+$loader->add('Pock\\TestUtils', __DIR__ . '/utils');
diff --git a/tests/.gitkeep b/tests/src/.gitkeep
similarity index 100%
rename from tests/.gitkeep
rename to tests/src/.gitkeep
diff --git a/tests/src/Creator/JmsJsonSerializerCreatorTest.php b/tests/src/Creator/JmsJsonSerializerCreatorTest.php
new file mode 100644
index 0000000..b87474a
--- /dev/null
+++ b/tests/src/Creator/JmsJsonSerializerCreatorTest.php
@@ -0,0 +1,32 @@
+serialize(new SimpleObject()));
+ }
+}
diff --git a/tests/src/Creator/JmsXmlSerializerCreatorTest.php b/tests/src/Creator/JmsXmlSerializerCreatorTest.php
new file mode 100644
index 0000000..073569d
--- /dev/null
+++ b/tests/src/Creator/JmsXmlSerializerCreatorTest.php
@@ -0,0 +1,32 @@
+serialize(new SimpleObject()));
+ }
+}
diff --git a/tests/src/Factory/JsonSerializerFactoryTest.php b/tests/src/Factory/JsonSerializerFactoryTest.php
new file mode 100644
index 0000000..ead50b4
--- /dev/null
+++ b/tests/src/Factory/JsonSerializerFactoryTest.php
@@ -0,0 +1,32 @@
+serialize(new SimpleObject()));
+ }
+}
diff --git a/tests/src/Factory/XmlSerializerFactoryTest.php b/tests/src/Factory/XmlSerializerFactoryTest.php
new file mode 100644
index 0000000..3884abe
--- /dev/null
+++ b/tests/src/Factory/XmlSerializerFactoryTest.php
@@ -0,0 +1,32 @@
+serialize(new SimpleObject()));
+ }
+}
diff --git a/tests/src/Matchers/AbstractRequestMatcherTest.php b/tests/src/Matchers/AbstractRequestMatcherTest.php
new file mode 100644
index 0000000..f58d6e3
--- /dev/null
+++ b/tests/src/Matchers/AbstractRequestMatcherTest.php
@@ -0,0 +1,53 @@
+createRequest(static::TEST_METHOD, static::TEST_URI);
+ }
+
+ /**
+ * @return \Nyholm\Psr7\Factory\Psr17Factory
+ */
+ protected static function getPsr17Factory(): Psr17Factory
+ {
+ if (null === static::$psr17Factory) {
+ static::$psr17Factory = new Psr17Factory();
+ }
+
+ return static::$psr17Factory;
+ }
+}
diff --git a/tests/src/Matchers/AnyRequestMatcherTest.php b/tests/src/Matchers/AnyRequestMatcherTest.php
new file mode 100644
index 0000000..e0c6d02
--- /dev/null
+++ b/tests/src/Matchers/AnyRequestMatcherTest.php
@@ -0,0 +1,26 @@
+matches(static::getTestRequest()));
+ }
+}
diff --git a/tests/src/Matchers/HostMatcherTest.php b/tests/src/Matchers/HostMatcherTest.php
new file mode 100644
index 0000000..448fdcb
--- /dev/null
+++ b/tests/src/Matchers/HostMatcherTest.php
@@ -0,0 +1,26 @@
+matches(static::getTestRequest()));
+ }
+}
diff --git a/tests/src/Matchers/MultipleMatcherTest.php b/tests/src/Matchers/MultipleMatcherTest.php
new file mode 100644
index 0000000..7b792ba
--- /dev/null
+++ b/tests/src/Matchers/MultipleMatcherTest.php
@@ -0,0 +1,34 @@
+addMatcher(new AnyRequestMatcher());
+
+ self::assertTrue($matcher->matches(static::getTestRequest()));
+ self::assertFalse($matcher->matches(static::getPsr17Factory()
+ ->createRequest(RequestMethod::GET, 'https://test.com')));
+ }
+}
diff --git a/tests/src/Matchers/SchemeMatcherTest.php b/tests/src/Matchers/SchemeMatcherTest.php
new file mode 100644
index 0000000..327d018
--- /dev/null
+++ b/tests/src/Matchers/SchemeMatcherTest.php
@@ -0,0 +1,27 @@
+matches(static::getTestRequest()));
+ }
+}
diff --git a/tests/src/Matchers/UriMatcherTest.php b/tests/src/Matchers/UriMatcherTest.php
new file mode 100644
index 0000000..16c700e
--- /dev/null
+++ b/tests/src/Matchers/UriMatcherTest.php
@@ -0,0 +1,28 @@
+matches(static::getTestRequest()));
+ self::assertTrue((new UriMatcher(static::getPsr17Factory()->createUri(self::TEST_URI)))
+ ->matches(static::getTestRequest()));
+ }
+}
diff --git a/tests/utils/SimpleObject.php b/tests/utils/SimpleObject.php
new file mode 100644
index 0000000..26e23c3
--- /dev/null
+++ b/tests/utils/SimpleObject.php
@@ -0,0 +1,38 @@
+
+
+
+
+
+EOF;
+
+ /**
+ * @var string
+ *
+ * @JMS\Type("string")
+ * @JMS\SerializedName("field")
+ */
+ private $field = 'test';
+}