Completed
Pull Request — master (#106)
by Marco
02:15 queued 24s
created

DefaultSessionDataTest   A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 3
dl 0
loc 314
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license.
17
 */
18
19
declare(strict_types=1);
20
21
namespace PSR7SessionsTest\Storageless\Session;
22
23
use InvalidArgumentException;
24
use JsonSerializable;
25
use PHPUnit\Framework\TestCase;
26
use PSR7Sessions\Storageless\Session\DefaultSessionData;
27
use ReflectionClass;
28
use stdClass;
29
use function array_filter;
30
use function json_encode;
31
use const PHP_INT_MAX;
32
use const PHP_INT_MIN;
33
34
/**
35
 * @covers \PSR7Sessions\Storageless\Session\DefaultSessionData
36
 */
37
final class DefaultSessionDataTest extends TestCase
38
{
39
    public function testEqualityOfEmptySessions() : void
40
    {
41
        self::assertEquals(
42
            DefaultSessionData::fromTokenData([]),
43
            DefaultSessionData::newEmptySession()
44
        );
45
        self::assertEquals(
46
            DefaultSessionData::fromDecodedTokenData((object) []),
47
            DefaultSessionData::newEmptySession()
48
        );
49
    }
50
51
    public function testContainerIsEmptyWhenCreatedExplicitlyAsEmpty() : void
52
    {
53
        self::assertTrue(DefaultSessionData::newEmptySession()->isEmpty());
54
    }
55
56
    public function testContainerIsEmptyWhenCreatedWithoutData() : void
57
    {
58
        self::assertTrue(DefaultSessionData::fromTokenData([])->isEmpty());
59
    }
60
61
    public function testContainerIsNotEmptyWhenDataIsProvided() : void
62
    {
63
        self::assertFalse(DefaultSessionData::fromTokenData(['foo' => 'bar'])->isEmpty());
64
    }
65
66
    public function testContainerIsNotEmptyWhenDataIsPassedToItAfterwards() : void
67
    {
68
        $session = DefaultSessionData::newEmptySession();
69
70
        $session->set('foo', 'bar');
71
72
        self::assertFalse($session->isEmpty());
73
    }
74
75
    public function testContainerIsEmptyWhenDataIsRemovedFromIt() : void
76
    {
77
        $session = DefaultSessionData::fromTokenData(['foo' => 'bar']);
78
79
        $session->remove('foo');
80
81
        self::assertTrue($session->isEmpty());
82
    }
83
84
    public function testClearWillRemoveEverythingFromTheSessionContainer() : void
85
    {
86
        $session = DefaultSessionData::fromTokenData([
87
            'foo' => 'bar',
88
            'baz' => 'tab',
89
        ]);
90
91
        $session->clear();
92
93
        self::assertTrue($session->isEmpty());
94
        self::assertTrue($session->hasChanged());
95
        self::assertFalse($session->has('foo'));
96
        self::assertFalse($session->has('baz'));
97
    }
98
99
    public function testStorageKeysAreConvertedToStringKeys() : void
100
    {
101
        self::assertSame(
102
            '{"0":"a","1":"b","2":"c"}',
103
            json_encode(DefaultSessionData::fromTokenData(['a', 'b', 'c']))
104
        );
105
    }
106
107
    /**
108
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
109
     *
110
     * @dataProvider storageScalarDataProvider
111
     */
112
    public function testContainerDataIsStoredAndRetrieved(string $key, $value) : void
113
    {
114
        $session = DefaultSessionData::newEmptySession();
115
116
        $session->set($key, $value);
117
        self::assertSame($value, $session->get($key));
118
    }
119
120
    /**
121
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
122
     *
123
     * @dataProvider storageScalarDataProvider
124
     */
125
    public function testSettingDataInAContainerMarksTheContainerAsMutated(string $key, $value) : void
126
    {
127
        $session = DefaultSessionData::newEmptySession();
128
129
        $session->set($key, $value);
130
131
        self::assertTrue($session->hasChanged());
132
    }
133
134
    public function testChangingTheDataTypeOfAValueIsConsideredAsAChange() : void
135
    {
136
        $session = DefaultSessionData::fromDecodedTokenData((object) ['a' => 1]);
137
138
        self::assertFalse($session->hasChanged());
139
140
        $session->set('a', '1');
141
142
        self::assertTrue($session->hasChanged());
143
144
        $session->set('a', 1);
145
146
        self::assertFalse($session->hasChanged());
147
    }
148
149
    /**
150
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
151
     *
152
     * @dataProvider storageScalarDataProvider
153
     */
154
    public function testContainerIsNotChangedWhenScalarDataIsSetAndOverwrittenInIt(string $key, $value) : void
155
    {
156
        $session = DefaultSessionData::fromTokenData([$key => $value]);
157
158
        self::assertFalse($session->hasChanged());
159
160
        $session->set($key, $value);
161
162
        self::assertFalse($session->hasChanged());
163
    }
164
165
    /**
166
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $nonScalarValue
167
     *
168
     * @dataProvider storageNonScalarDataProvider
169
     */
170
    public function testContainerIsNotChangedWhenNonScalarDataIsSetAndOverwrittenInIt($nonScalarValue) : void
171
    {
172
        $session = DefaultSessionData::fromTokenData(['key' => $nonScalarValue]);
173
174
        self::assertFalse($session->hasChanged());
175
176
        $session->set('key', $nonScalarValue);
177
178
        self::assertFalse($session->hasChanged());
179
    }
180
181
    /**
182
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
183
     *
184
     * @dataProvider storageScalarDataProvider
185
     */
186
    public function testContainerBuiltWithDataContainsData(string $key, $value) : void
187
    {
188
        $session = DefaultSessionData::fromTokenData([$key => $value]);
189
190
        self::assertTrue($session->has($key));
191
        self::assertSame($value, $session->get($key));
192
    }
193
194
    /**
195
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
196
     *
197
     * @dataProvider storageScalarDataProvider
198
     */
199
    public function testContainerBuiltWithStdClassContainsData(string $key, $value) : void
200
    {
201
        if ($key === "\0" || $value === "\0" || $key === '') {
202
            self::markTestSkipped('Null bytes or empty keys are not supported by PHP\'s stdClass');
203
        }
204
205
        $session = DefaultSessionData::fromDecodedTokenData((object) [$key => $value]);
206
207
        self::assertTrue($session->has($key));
208
        self::assertSame($value, $session->get($key));
209
    }
210
211
    /**
212
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $nonScalar
213
     * @param int|bool|string|float|mixed[]|null                         $expectedScalar
214
     *
215
     * @dataProvider storageNonScalarDataProvider
216
     */
217
    public function testContainerStoresScalarValueFromNestedObjects($nonScalar, $expectedScalar) : void
218
    {
219
        $session = DefaultSessionData::fromTokenData(['key' => $nonScalar]);
220
221
        self::assertSame($expectedScalar, $session->get('key'));
222
223
        $session->set('otherKey', $nonScalar);
224
225
        self::assertSame($expectedScalar, $session->get('otherKey'));
226
    }
227
228
    /**
229
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
230
     *
231
     * @dataProvider storageScalarDataProvider
232
     */
233
    public function testGetWillReturnDefaultValueOnNonExistingKey(string $key, $value) : void
234
    {
235
        $session = DefaultSessionData::newEmptySession();
236
237
        self::assertFalse($session->has($key));
238
        self::assertSame($value, $session->get($key, $value));
239
    }
240
241
    /**
242
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $nonScalar
243
     * @param int|bool|string|float|mixed[]|null                         $expectedScalar
244
     *
245
     * @dataProvider storageNonScalarDataProvider
246
     */
247
    public function testGetWillReturnScalarCastDefaultValueOnNonExistingKey($nonScalar, $expectedScalar) : void
248
    {
249
        self::assertSame($expectedScalar, DefaultSessionData::newEmptySession()->get('key', $nonScalar));
250
    }
251
252
    public function testAllMethodsAreCoveredByAnInterfacedMethod() : void
253
    {
254
        $reflection = new ReflectionClass(DefaultSessionData::class);
255
        $interfaces = $reflection->getInterfaces();
256
257
        foreach ($reflection->getMethods() as $method) {
258
            if ($method->isConstructor() || $method->isStatic() || ! $method->isPublic()) {
259
                continue;
260
            }
261
262
            self::assertNotEmpty(array_filter(
263
                $interfaces,
264
                static function (ReflectionClass $interface) use ($method) {
265
                    return $interface->hasMethod($method->getName());
266
                }
267
            ), $method->getName());
268
        }
269
    }
270
271
    public function testRejectsNonJsonSerializableData() : void
272
    {
273
        $this->expectException(InvalidArgumentException::class);
274
        $this->expectExceptionMessage("Could not serialise given value '\x80' due to Malformed UTF-8 characters, possibly incorrectly encoded (5)");
275
276
        DefaultSessionData::fromTokenData(['foo' => "\x80"]);
277
    }
278
279
    /** @return (int|bool|string|float|mixed[]|object|JsonSerializable|null)[][] */
280
    public function storageNonScalarDataProvider() : array
281
    {
282
        return [
283
            'class' => [
284
                new class
285
                {
286
                    public string $foo = 'bar';
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
287
                },
288
                ['foo' => 'bar'],
289
            ],
290
            'object' => [
291
                (object) ['baz' => [(object) ['tab' => 'taz']]],
292
                ['baz' => [['tab' => 'taz']]],
293
            ],
294
            'array'  => [
295
                [(object) ['tar' => 'tan']],
296
                [['tar' => 'tan']],
297
            ],
298
            'array with numeric keys'  => [
299
                [['a', 'b', 'c']],
300
                [['a', 'b', 'c']],
301
            ],
302
            'jsonSerializable' => [
303
                new class implements JsonSerializable
304
                {
305
                    public function jsonSerialize() : object
306
                    {
307
                        $object = new stdClass();
308
309
                        $object->war = 'zip';
310
311
                        return $object;
312
                    }
313
                },
314
                ['war' => 'zip'],
315
            ],
316
            'emptyObject' => [
317
                new stdClass(),
318
                [],
319
            ],
320
        ];
321
    }
322
323
    /** @return (int|bool|string|float|mixed[]|null)[][] */
324
    public function storageScalarDataProvider() : array
325
    {
326
        return [
327
            'string'             => ['foo', 'bar'],
328
            'empty string'       => ['foo', ''],
329
            'null-ish string'    => ['foo', 'null'],
330
            'null'               => ['foo', null],
331
            'empty string key'   => ['', 'bar'],
332
            '0-ish string'       => ['foo', '0'],
333
            'empty array string' => ['foo', '[]'],
334
            'null byte'          => ['foo', "\0"],
335
            'null byte key'      => ["\0", 'bar'],
336
            'zero'               => ['foo', 0],
337
            'integer'            => ['foo', 1],
338
            'negative integer'   => ['foo', -1],
339
            'large integer'      => ['foo', PHP_INT_MAX],
340
            'small integer'      => ['foo', PHP_INT_MIN],
341
            'float'              => ['foo', 0.1],
342
            'float zero'         => ['foo', 0.0],
343
            'empty array'        => ['foo', []],
344
            '0-indexed-array'    => ['foo', ['bar', 'baz']],
345
            'map'                => ['foo', ['bar' => 'baz']],
346
            'nested array'       => ['foo', ['bar' => []]],
347
        ];
348
    }
349
}
350