123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- <?php
- declare(strict_types=1);
- /**
- * This file is part of Hyperf.
- *
- * @link https://www.hyperf.io
- * @document https://hyperf.wiki
- * @contact group@hyperf.io
- * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
- */
- namespace Hyperf\Testing;
- use ArrayAccess;
- use Closure;
- use Countable;
- use Hyperf\Collection\Arr;
- use Hyperf\Contract\Jsonable;
- use Hyperf\Stringable\Str;
- use Hyperf\Testing\Assert as PHPUnit;
- use JsonSerializable;
- use function Hyperf\Collection\data_get;
- class AssertableJsonString implements ArrayAccess, Countable
- {
- /**
- * The original encoded json.
- *
- * @var array|Jsonable|JsonSerializable|string
- */
- public $json;
- /**
- * The decoded json contents.
- *
- * @var null|array
- */
- protected $decoded;
- /**
- * Create a new assertable JSON string instance.
- *
- * @param array|Jsonable|JsonSerializable|string $jsonable
- */
- public function __construct($jsonable)
- {
- $this->json = $jsonable;
- if ($jsonable instanceof JsonSerializable) {
- $this->decoded = $jsonable->jsonSerialize();
- } elseif ($jsonable instanceof Jsonable) {
- $this->decoded = json_decode($jsonable->__toString(), true);
- } elseif (is_array($jsonable)) {
- $this->decoded = $jsonable;
- } else {
- $this->decoded = json_decode($jsonable, true);
- }
- }
- /**
- * Validate and return the decoded response JSON.
- *
- * @param null|string $key
- * @return mixed
- */
- public function json($key = null)
- {
- return data_get($this->decoded, $key);
- }
- /**
- * Assert that the response JSON has the expected count of items at the given key.
- *
- * @param null|string $key
- * @return $this
- */
- public function assertCount(int $count, $key = null)
- {
- if (! is_null($key)) {
- PHPUnit::assertCount(
- $count,
- data_get($this->decoded, $key),
- "Failed to assert that the response count matched the expected {$count}"
- );
- return $this;
- }
- PHPUnit::assertCount(
- $count,
- $this->decoded,
- "Failed to assert that the response count matched the expected {$count}"
- );
- return $this;
- }
- /**
- * Assert that the response has the exact given JSON.
- *
- * @return $this
- */
- public function assertExact(array $data)
- {
- $actual = $this->reorderAssocKeys((array) $this->decoded);
- $expected = $this->reorderAssocKeys($data);
- PHPUnit::assertEquals(
- json_encode($expected, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
- json_encode($actual, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
- );
- return $this;
- }
- /**
- * Assert that the response has the similar JSON as given.
- *
- * @return $this
- */
- public function assertSimilar(array $data)
- {
- $actual = json_encode(
- Arr::sortRecursive((array) $this->decoded),
- JSON_UNESCAPED_UNICODE
- );
- PHPUnit::assertEquals(json_encode(Arr::sortRecursive($data), JSON_UNESCAPED_UNICODE), $actual);
- return $this;
- }
- /**
- * Assert that the response contains the given JSON fragment.
- *
- * @return $this
- */
- public function assertFragment(array $data)
- {
- $actual = json_encode(
- Arr::sortRecursive((array) $this->decoded),
- JSON_UNESCAPED_UNICODE
- );
- foreach (Arr::sortRecursive($data) as $key => $value) {
- $expected = $this->jsonSearchStrings($key, $value);
- PHPUnit::assertTrue(
- Str::contains($actual, $expected),
- 'Unable to find JSON fragment: ' . PHP_EOL . PHP_EOL .
- '[' . json_encode([$key => $value], JSON_UNESCAPED_UNICODE) . ']' . PHP_EOL . PHP_EOL .
- 'within' . PHP_EOL . PHP_EOL .
- "[{$actual}]."
- );
- }
- return $this;
- }
- /**
- * Assert that the response does not contain the given JSON fragment.
- *
- * @param bool $exact
- * @return $this
- */
- public function assertMissing(array $data, $exact = false)
- {
- if ($exact) {
- return $this->assertMissingExact($data);
- }
- $actual = json_encode(
- Arr::sortRecursive((array) $this->decoded),
- JSON_UNESCAPED_UNICODE
- );
- foreach (Arr::sortRecursive($data) as $key => $value) {
- $unexpected = $this->jsonSearchStrings($key, $value);
- PHPUnit::assertFalse(
- Str::contains($actual, $unexpected),
- 'Found unexpected JSON fragment: ' . PHP_EOL . PHP_EOL .
- '[' . json_encode([$key => $value], JSON_UNESCAPED_UNICODE) . ']' . PHP_EOL . PHP_EOL .
- 'within' . PHP_EOL . PHP_EOL .
- "[{$actual}]."
- );
- }
- return $this;
- }
- /**
- * Assert that the response does not contain the exact JSON fragment.
- *
- * @return $this
- */
- public function assertMissingExact(array $data)
- {
- $actual = json_encode(
- Arr::sortRecursive((array) $this->decoded),
- JSON_UNESCAPED_UNICODE
- );
- foreach (Arr::sortRecursive($data) as $key => $value) {
- $unexpected = $this->jsonSearchStrings($key, $value);
- if (! Str::contains($actual, $unexpected)) {
- return $this;
- }
- }
- PHPUnit::fail(
- 'Found unexpected JSON fragment: ' . PHP_EOL . PHP_EOL .
- '[' . json_encode($data, JSON_UNESCAPED_UNICODE) . ']' . PHP_EOL . PHP_EOL .
- 'within' . PHP_EOL . PHP_EOL .
- "[{$actual}]."
- );
- }
- /**
- * Assert that the response does not contain the given path.
- *
- * @param string $path
- * @return $this
- */
- public function assertMissingPath($path)
- {
- PHPUnit::assertFalse(Arr::has($this->json(), $path));
- return $this;
- }
- /**
- * Assert that the expected value and type exists at the given path in the response.
- *
- * @param string $path
- * @param mixed $expect
- * @return $this
- */
- public function assertPath($path, $expect)
- {
- if ($expect instanceof Closure) {
- PHPUnit::assertTrue($expect($this->json($path)));
- } else {
- PHPUnit::assertSame($expect, $this->json($path));
- }
- return $this;
- }
- /**
- * Assert that the response has a given JSON structure.
- *
- * @param null|array $responseData
- * @return $this
- */
- public function assertStructure(?array $structure = null, $responseData = null)
- {
- if (is_null($structure)) {
- return $this->assertSimilar($this->decoded);
- }
- if (! is_null($responseData)) {
- return (new static($responseData))->assertStructure($structure);
- }
- foreach ($structure as $key => $value) {
- if (is_array($value) && $key === '*') {
- PHPUnit::assertIsArray($this->decoded);
- foreach ($this->decoded as $responseDataItem) {
- $this->assertStructure($structure['*'], $responseDataItem);
- }
- } elseif (is_array($value)) {
- PHPUnit::assertArrayHasKey($key, $this->decoded);
- $this->assertStructure($structure[$key], $this->decoded[$key]);
- } else {
- PHPUnit::assertArrayHasKey($value, $this->decoded);
- }
- }
- return $this;
- }
- /**
- * Assert that the response is a superset of the given JSON.
- *
- * @param bool $strict
- * @return $this
- */
- public function assertSubset(array $data, $strict = false)
- {
- PHPUnit::assertArraySubset(
- $data,
- $this->decoded,
- $strict,
- $this->assertJsonMessage($data)
- );
- return $this;
- }
- /**
- * Get the total number of items in the underlying JSON array.
- */
- public function count(): int
- {
- return count($this->decoded);
- }
- /**
- * Determine whether an offset exists.
- *
- * @param mixed $offset
- */
- public function offsetExists($offset): bool
- {
- return isset($this->decoded[$offset]);
- }
- /**
- * Get the value at the given offset.
- *
- * @param string $offset
- */
- public function offsetGet($offset): mixed
- {
- return $this->decoded[$offset];
- }
- /**
- * Set the value at the given offset.
- *
- * @param string $offset
- * @param mixed $value
- */
- public function offsetSet($offset, $value): void
- {
- $this->decoded[$offset] = $value;
- }
- /**
- * Unset the value at the given offset.
- *
- * @param string $offset
- */
- public function offsetUnset($offset): void
- {
- unset($this->decoded[$offset]);
- }
- /**
- * Reorder associative array keys to make it easy to compare arrays.
- *
- * @return array
- */
- protected function reorderAssocKeys(array $data)
- {
- $data = Arr::dot($data);
- ksort($data);
- $result = [];
- foreach ($data as $key => $value) {
- Arr::set($result, $key, $value);
- }
- return $result;
- }
- /**
- * Get the assertion message for assertJson.
- *
- * @return string
- */
- protected function assertJsonMessage(array $data)
- {
- $expected = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
- $actual = json_encode($this->decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
- return 'Unable to find JSON: ' . PHP_EOL . PHP_EOL .
- "[{$expected}]" . PHP_EOL . PHP_EOL .
- 'within response JSON:' . PHP_EOL . PHP_EOL .
- "[{$actual}]." . PHP_EOL . PHP_EOL;
- }
- /**
- * Get the strings we need to search for when examining the JSON.
- *
- * @param string $key
- * @param string $value
- * @return array
- */
- protected function jsonSearchStrings($key, $value)
- {
- $needle = Str::substr(json_encode([$key => $value], JSON_UNESCAPED_UNICODE), 1, -1);
- return [
- $needle . ']',
- $needle . '}',
- $needle . ',',
- ];
- }
- }
|