Test Setup Failed
Pull Request — master (#79)
by Marco
31:16
created

DefaultSessionDataTest::testFromFromTokenDataBuildsADataContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 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 stdClass;
28
use const PHP_INT_MAX;
29
use const PHP_INT_MIN;
30
use function array_filter;
31
use function json_encode;
32
33
/**
34
 * @covers \PSR7Sessions\Storageless\Session\DefaultSessionData
35
 */
36
final class DefaultSessionDataTest extends TestCase
37
{
38
    public function testEqualityOfEmptySessions() : void
39
    {
40
        self::assertEquals(
41
            DefaultSessionData::fromTokenData([]),
42
            DefaultSessionData::newEmptySession()
43
        );
44
        self::assertEquals(
45
            DefaultSessionData::fromDecodedTokenData((object) []),
46
            DefaultSessionData::newEmptySession()
47
        );
48
    }
49
50
    public function testContainerIsEmptyWhenCreatedExplicitlyAsEmpty() : void
51
    {
52
        self::assertTrue(DefaultSessionData::newEmptySession()->isEmpty());
53
    }
54
55
    public function testContainerIsEmptyWhenCreatedWithoutData() : void
56
    {
57
        self::assertTrue(DefaultSessionData::fromTokenData([])->isEmpty());
58
    }
59
60
    public function testContainerIsNotEmptyWhenDataIsProvided() : void
61
    {
62
        self::assertFalse(DefaultSessionData::fromTokenData(['foo' => 'bar'])->isEmpty());
63
    }
64
65
    public function testContainerIsNotEmptyWhenDataIsPassedToItAfterwards() : void
66
    {
67
        $session = DefaultSessionData::newEmptySession();
68
69
        $session->set('foo', 'bar');
70
71
        self::assertFalse($session->isEmpty());
72
    }
73
74
    public function testContainerIsEmptyWhenDataIsRemovedFromIt() : void
75
    {
76
        $session = DefaultSessionData::fromTokenData(['foo' => 'bar']);
77
78
        $session->remove('foo');
79
80
        self::assertTrue($session->isEmpty());
81
    }
82
83
    public function testClearWillRemoveEverythingFromTheSessionContainer() : void
84
    {
85
        $session = DefaultSessionData::fromTokenData([
86
            'foo' => 'bar',
87
            'baz' => 'tab',
88
        ]);
89
90
        $session->clear();
91
92
        self::assertTrue($session->isEmpty());
93
        self::assertTrue($session->hasChanged());
94
        self::assertFalse($session->has('foo'));
95
        self::assertFalse($session->has('baz'));
96
    }
97
98
    public function testStorageKeysAreConvertedToStringKeys() : void
99
    {
100
        self::assertSame(
101
            '{"0":"a","1":"b","2":"c"}',
102
            json_encode(DefaultSessionData::fromTokenData(['a', 'b', 'c']))
103
        );
104
    }
105
106
    /**
107
     * @dataProvider storageScalarDataProvider
108
     *
109
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
110
     */
111
    public function testContainerDataIsStoredAndRetrieved(string $key, $value) : void
112
    {
113
        $session = DefaultSessionData::newEmptySession();
114
115
        $session->set($key, $value);
116
        self::assertSame($value, $session->get($key));
117
    }
118
119
    /**
120
     * @dataProvider storageScalarDataProvider
121
     *
122
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
123
     */
124
    public function testSettingDataInAContainerMarksTheContainerAsMutated(string $key, $value) : void
125
    {
126
        $session = DefaultSessionData::newEmptySession();
127
128
        $session->set($key, $value);
129
130
        self::assertTrue($session->hasChanged());
131
    }
132
133
    public function testChangingTheDataTypeOfAValueIsConsideredAsAChange() : void
134
    {
135
        $session = DefaultSessionData::fromDecodedTokenData((object) ['a' => 1]);
136
137
        self::assertFalse($session->hasChanged());
138
139
        $session->set('a', '1');
140
141
        self::assertTrue($session->hasChanged());
142
143
        $session->set('a', 1);
144
145
        self::assertFalse($session->hasChanged());
146
    }
147
148
    /**
149
     * @dataProvider storageScalarDataProvider
150
     *
151
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
152
     */
153
    public function testContainerIsNotChangedWhenScalarDataIsSetAndOverwrittenInIt(string $key, $value) : void
154
    {
155
        $session = DefaultSessionData::fromTokenData([$key => $value]);
156
157
        self::assertFalse($session->hasChanged());
158
159
        $session->set($key, $value);
160
161
        self::assertFalse($session->hasChanged());
162
    }
163
164
    /**
165
     * @dataProvider storageNonScalarDataProvider
166
     *
167
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $nonScalarValue
168
     */
169
    public function testContainerIsNotChangedWhenNonScalarDataIsSetAndOverwrittenInIt($nonScalarValue) : void
170
    {
171
        $session = DefaultSessionData::fromTokenData(['key' => $nonScalarValue]);
172
173
        self::assertFalse($session->hasChanged());
174
175
        $session->set('key', $nonScalarValue);
176
177
        self::assertFalse($session->hasChanged());
178
    }
179
180
    /**
181
     * @dataProvider storageScalarDataProvider
182
     *
183
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
184
     */
185
    public function testContainerBuiltWithDataContainsData(string $key, $value) : void
186
    {
187
        $session = DefaultSessionData::fromTokenData([$key => $value]);
188
189
        self::assertTrue($session->has($key));
190
        self::assertSame($value, $session->get($key));
191
    }
192
193
    /**
194
     * @dataProvider storageScalarDataProvider
195
     *
196
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
197
     */
198
    public function testContainerBuiltWithStdClassContainsData(string $key, $value) : void
199
    {
200
        if ($key === "\0" || $value === "\0" || $key === '') {
201
            self::markTestSkipped('Null bytes or empty keys are not supported by PHP\'s stdClass');
202
        }
203
204
        $session = DefaultSessionData::fromDecodedTokenData((object) [$key => $value]);
205
206
        self::assertTrue($session->has($key));
207
        self::assertSame($value, $session->get($key));
208
    }
209
210
    /**
211
     * @dataProvider storageNonScalarDataProvider
212
     *
213
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $nonScalar
214
     * @param int|bool|string|float|mixed[]|null                         $expectedScalar
215
     */
216
    public function testContainerStoresScalarValueFromNestedObjects($nonScalar, $expectedScalar) : void
217
    {
218
        $session = DefaultSessionData::fromTokenData(['key' => $nonScalar]);
219
220
        self::assertSame($expectedScalar, $session->get('key'));
221
222
        $session->set('otherKey', $nonScalar);
223
224
        self::assertSame($expectedScalar, $session->get('otherKey'));
225
    }
226
227
    /**
228
     * @dataProvider storageScalarDataProvider
229
     *
230
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $value
231
     */
232
    public function testGetWillReturnDefaultValueOnNonExistingKey(string $key, $value) : void
233
    {
234
        $session = DefaultSessionData::newEmptySession();
235
236
        self::assertFalse($session->has($key));
237
        self::assertSame($value, $session->get($key, $value));
238
    }
239
240
    /**
241
     * @dataProvider storageNonScalarDataProvider
242
     *
243
     * @param int|bool|string|float|mixed[]|object|JsonSerializable|null $nonScalar
244
     * @param int|bool|string|float|mixed[]|null                         $expectedScalar
245
     */
246
    public function testGetWillReturnScalarCastDefaultValueOnNonExistingKey($nonScalar, $expectedScalar) : void
247
    {
248
        self::assertSame($expectedScalar, DefaultSessionData::newEmptySession()->get('key', $nonScalar));
249
    }
250
251
    public function testAllMethodsAreCoveredByAnInterfacedMethod() : void
252
    {
253
        $reflection = new \ReflectionClass(DefaultSessionData::class);
254
        $interfaces = $reflection->getInterfaces();
255
256
        foreach ($reflection->getMethods() as $method) {
257
            if ($method->isConstructor() || $method->isStatic() || ! $method->isPublic()) {
258
                continue;
259
            }
260
261
            self::assertNotEmpty(array_filter(
262
                $interfaces,
263
                function (\ReflectionClass $interface) use ($method) {
264
                    return $interface->hasMethod($method->getName());
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
265
                }
266
            ), $method->getName());
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
267
        }
268
    }
269
270
    public function testRejectsNonJsonSerializableData() : void
271
    {
272
        $this->expectException(InvalidArgumentException::class);
273
        $this->expectExceptionMessage("Could not serialise given value '\x80' due to Malformed UTF-8 characters, possibly incorrectly encoded (5)");
274
275
        DefaultSessionData::fromTokenData(['foo' => "\x80"]);
276
    }
277
278
    /** @return (int|bool|string|float|mixed[]|object|JsonSerializable|null)[][] */
279
    public function storageNonScalarDataProvider() : array
280
    {
281
        return [
282
            'class' => [
283
                new class
284
                {
285
                    /** @var string */
286
                    public $foo = 'bar';
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