Passed
Push — master ( 0b999e...3d12f2 )
by Baptiste
43s
created

Context::the_json_path_should_be()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php declare(strict_types=1);
2
namespace Behapi\Json;
3
4
use stdClass;
5
use DateTimeImmutable;
6
7
use Throwable;
8
use InvalidArgumentException;
9
10
use Behat\Gherkin\Node\PyStringNode;
11
use Behat\Behat\Context\Context as BehatContext;
12
13
use Symfony\Component\PropertyAccess\PropertyAccess;
14
use Symfony\Component\PropertyAccess\PropertyAccessor;
15
16
use Webmozart\Assert\Assert;
17
18
use Behapi\HttpHistory\History as HttpHistory;
19
20
use function sprintf;
21
use function preg_match;
22
23
use function json_decode;
24
use function json_last_error;
25
use function json_last_error_msg;
26
27
use const JSON_ERROR_NONE;
28
use const PREG_OFFSET_CAPTURE;
29
30
class Context implements BehatContext
31
{
32
    use CountTrait;
33
    use RegexTrait;
34
    use ComparisonTrait;
35
    use EachInCollectionTrait;
36
37
    /** @var HttpHistory */
38
    private $history;
39
40
    /** @var string[] */
41
    private $contentTypes;
42
43
    /** @var PropertyAccessor */
44
    private $accessor;
45
46
    public function __construct(HttpHistory $history, array $contentTypes = ['application/json'])
47
    {
48
        $this->accessor = PropertyAccess::createPropertyAccessor();
49
50
        $this->history = $history;
51
        $this->contentTypes = $contentTypes;
52
    }
53
54
    /** @return mixed */
55
    protected function getValue(?string $path)
56
    {
57
        $json = json_decode((string) $this->history->getLastResponse()->getBody());
58
59
        return $path === null ? $json : $this->accessor->getValue($json, $path);
60
    }
61
62
    /** @Then :path should be accessible in the latest json response */
63
    final public function path_should_be_readable(string $path): void
64
    {
65
        Assert::true($this->accessor->isReadable($this->getValue(null), $path), "The path $path should be a valid path");
66
    }
67
68
    /** @Then :path should not exist in the latest json response */
69
    final public function path_should_not_be_readable(string $path): void
70
    {
71
        Assert::false($this->accessor->isReadable($this->getValue(null), $path), "The path $path should not be a valid path");
72
    }
73
74
    /**
75
     * @Then in the json, :path should be equal to :expected
76
     * @Then in the json, :path should :not be equal to :expected
77
     */
78
    final public function the_json_path_should_be_equal_to(string $path, ?string $not = null, $expected): void
79
    {
80
        $assert = [Assert::class, $not !== null ? 'notEq' : 'eq'];
81
        assert(is_callable($assert));
82
83
        $assert($this->getValue($path), $expected);
84
    }
85
86
    /** @Then in the json, :path should be: */
87
    final public function the_json_path_should_be_py_string(string $path, PyStringNode $expected): void
88
    {
89
        Assert::same($this->getValue($path), $expected->getRaw());
90
    }
91
92
    /**
93
     * @Then /^in the json, "(?P<path>(?:[^"]|\\")*)" should be (?P<expected>true|false)$/
94
     * @Then /^in the json, "(?P<path>(?:[^"]|\\")*)" should :not be (?P<expected>true|false)$/
95
     */
96
    final public function the_json_path_should_be(string $path, ?string $not = null, string $expected): void
97
    {
98
        $assert = [Assert::class, $not !== null ? 'notSame' : 'same'];
99
        assert(is_callable($assert));
100
101
        $assert($this->getValue($path), 'true' === $expected);
102
    }
103
104
    /**
105
     * @Then in the json, :path should be null
106
     * @Then in the json, :path should :not be null
107
     */
108
    final public function the_json_path_should_be_null(string $path, ?string $not = null): void
109
    {
110
        $assert = [Assert::class, $not !== null ? 'notNull' : 'null'];
111
        assert(is_callable($assert));
112
113
        $assert($this->getValue($path));
114
    }
115
116
    /**
117
     * @Then in the json, :path should be empty
118
     * @Then in the json, :path should :not be empty
119
     */
120
    final public function the_json_path_should_be_empty(string $path, ?string $not = null): void
121
    {
122
        $assert = [Assert::class, $not !== null ? 'notEmpty' : 'isEmpty'];
123
        assert(is_callable($assert));
124
125
        $assert($this->getValue($path));
126
    }
127
128
    /**
129
     * @Then in the json, :path should contain :expected
130
     * @Then in the json, :path should :not contain :expected
131
     */
132
    final public function the_json_path_contains(string $path, ?string $not = null, $expected): void
133
    {
134
        $assert = [Assert::class, $not !== null ? 'notContains' : 'contains'];
135
        assert(is_callable($assert));
136
137
        $assert($this->getValue($path), $expected);
138
    }
139
140
    /** @Then in the json, :path collection should contain an element with :value equal to :expected */
141
    final public function the_json_path_collection_contains(string $path, string $value, $expected): void
142
    {
143
        $collection = $this->getValue($path);
144
        Assert::isIterable($collection);
145
146
        foreach ($collection as $element) {
147
            if ($expected === $this->accessor->getValue($element, $value)) {
148
                return;
149
            }
150
        }
151
152
        throw new InvalidArgumentException("$path collection does not contain an element with $value equal to $expected");
153
    }
154
155
    /** @Then in the json, :path should be a valid date(time) */
156
    final public function the_json_path_should_be_a_valid_date(string $path): void
157
    {
158
        try {
159
            new DateTimeImmutable($this->getValue($path));
160
        } catch (Throwable $t) {
161
            throw new InvalidArgumentException("$path does not contain a valid date");
162
        }
163
    }
164
165
    /**
166
     * @Then in the json, the root should be an array
167
     * @Then in the json, :path should be an array
168
     */
169
    final public function should_be_an_array(?string $path = null): void
170
    {
171
        Assert::isArray($this->getValue($path));
172
    }
173
174
    /** @Then in the json, :path should be a valid json encoded string */
175
    final public function the_json_path_should_be_a_valid_json_encoded_string(string $path): void
176
    {
177
        json_decode($this->getValue($path));
178
        Assert::same(json_last_error(), JSON_ERROR_NONE);
179
    }
180
181
    /**
182
     * @Then the response should be a valid json response
183
     *
184
     * ---
185
     *
186
     * This method is built-on the default php's json extension. You should
187
     * overwrite it if you want to add supplementary checks or use something
188
     * else instead (such as Seldaek's JsonLint package).
189
     */
190
    public function response_should_be_a_valid_json_response()
191
    {
192
        $this->getValue(null);
193
194
        [$contentType,] = explode(';', $this->history->getLastResponse()->getHeaderLine('Content-Type'), 2);
195
196
        Assert::same(JSON_ERROR_NONE, json_last_error(), sprintf('The response is not a valid json (%s)', json_last_error_msg()));
197
        Assert::oneOf($contentType, $this->contentTypes, 'The response should have a valid content-type (expected one of %2$s, got %1$s)');
198
    }
199
}
200