Completed
Push — master ( e75790...24066c )
by Joschi
02:49
created

ObjectTest.php ➔ flock()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
eloc 2
nc 2
nop 3
dl 0
loc 4
rs 10
1
<?php
2
3
/**
4
 * apparat-object
5
 *
6
 * @category    Apparat
7
 * @package     Apparat\Object
8
 * @subpackage  Apparat\Object\Infrastructure
9
 * @author      Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright   Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license     http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Apparat\Object\Tests {
38
39
    use Apparat\Object\Application\Factory\ObjectFactory;
40
    use Apparat\Object\Application\Model\Object\Article;
41
    use Apparat\Object\Domain\Factory\AuthorFactory;
42
    use Apparat\Object\Domain\Model\Author\ApparatAuthor;
43
    use Apparat\Object\Domain\Model\Object\AbstractObject;
44
    use Apparat\Object\Domain\Model\Object\Id;
45
    use Apparat\Object\Domain\Model\Object\ResourceInterface;
46
    use Apparat\Object\Domain\Model\Object\Revision;
47
    use Apparat\Object\Domain\Model\Object\Type;
48
    use Apparat\Object\Domain\Model\Path\RepositoryPath;
49
    use Apparat\Object\Domain\Model\Properties\SystemProperties;
50
    use Apparat\Object\Domain\Repository\Repository;
51
    use Apparat\Object\Infrastructure\Repository\FileAdapterStrategy;
52
    use Apparat\Object\Ports\Object;
53
    use Apparat\Object\Ports\Repository as RepositoryFactory;
54
55
    /**
56
     * Object tests
57
     *
58
     * @package Apparat\Object
59
     * @subpackage ApparatTest
60
     */
61
    class ObjectTest extends AbstractDisabledAutoconnectorTest
62
    {
63
        /**
64
         * Example object path
65
         *
66
         * @var string
67
         */
68
        const OBJECT_PATH = '/2015/12/21/1.article/1';
69
        /**
70
         * Test repository
71
         *
72
         * @var Repository
73
         */
74
        protected static $repository = null;
75
76
        /**
77
         * Setup
78
         */
79
        public static function setUpBeforeClass()
80
        {
81
            \Apparat\Object\Ports\Repository::register(
82
                getenv('REPOSITORY_URL'),
83
                [
84
                    'type' => FileAdapterStrategy::TYPE,
85
                    'root' => __DIR__.DIRECTORY_SEPARATOR.'Fixture',
86
                ]
87
            );
88
89
            self::$repository = \Apparat\Object\Ports\Repository::instance(getenv('REPOSITORY_URL'));
90
91
            \date_default_timezone_set('UTC');
92
        }
93
94
        /**
95
         * Tears down the fixture
96
         */
97
        public function tearDown()
98
        {
99
            putenv('MOCK_FLOCK');
100
            TestType::removeInvalidType();
101
            parent::tearDown();
102
        }
103
104
        /**
105
         * Test undefined object type
106
         *
107
         * @expectedException \Apparat\Object\Application\Factory\InvalidArgumentException
108
         * @expectedExceptionCode 1450905868
109
         */
110
        public function testUndefinedObjectType()
111
        {
112
            $resource = $this->getMock(ResourceInterface::class);
113
            $resource->method('getPropertyData')->willReturn([]);
114
            $repositoryPath = $this->getMockBuilder(RepositoryPath::class)->disableOriginalConstructor()->getMock();
115
116
            /** @var ResourceInterface $resource */
117
            /** @var RepositoryPath $repositoryPath */
118
            ObjectFactory::createFromResource($repositoryPath, $resource);
119
        }
120
121
        /**
122
         * Test invalid object type
123
         *
124
         * @expectedException \Apparat\Object\Domain\Model\Object\InvalidArgumentException
125
         * @expectedExceptionCode 1449871242
126
         */
127
        public function testInvalidObjectType()
128
        {
129
            $resource = $this->getMock(ResourceInterface::class);
130
            $resource->method('getPropertyData')->willReturn([SystemProperties::COLLECTION => ['type' => 'invalid']]);
131
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
132
133
            /** @var ResourceInterface $resource */
134
            ObjectFactory::createFromResource($articleObjectPath, $resource);
135
        }
136
137
        /**
138
         * Load an article object and test basic properties
139
         */
140
        public function testLoadArticleObject()
141
        {
142
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
143
            $articleObject = self::$repository->loadObject($articleObjectPath);
144
            $this->assertEquals(
145
                getenv('APPARAT_BASE_URL').getenv('REPOSITORY_URL').self::OBJECT_PATH,
146
                $articleObject->getAbsoluteUrl()
147
            );
148
        }
149
150
        /**
151
         * Load an article object and test its system properties
152
         */
153
        public function testLoadArticleObjectSystemProperties()
154
        {
155
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
156
            $articleObject = self::$repository->loadObject($articleObjectPath);
157
            $this->assertInstanceOf(Article::class, $articleObject);
158
            $this->assertEquals(new Id(1), $articleObject->getId());
159
            $this->assertEquals(new Type(Type::ARTICLE), $articleObject->getType());
160
            $this->assertEquals(new Revision(1), $articleObject->getRevision());
161
            $this->assertFalse($articleObject->isDraft());
162
            $this->assertEquals(new \DateTimeImmutable('2015-12-21T22:30:00'), $articleObject->getCreated());
163
            $this->assertEquals(new \DateTimeImmutable('2015-12-21T22:45:00'), $articleObject->getPublished());
164
            $this->assertEquals('a123456789012345678901234567890123456789', $articleObject->getHash());
165
            $this->assertEquals(
166
                "# Example article object\n\nThis file is an example for an object of type `\"article\"`.\n",
167
                $articleObject->getPayload()
168
            );
169
        }
170
171
        /**
172
         * Load an article object and test its meta properties
173
         */
174
        public function testLoadArticleObjectMetaProperties()
175
        {
176
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
177
            $articleObject = self::$repository->loadObject($articleObjectPath);
178
            $this->assertInstanceOf(Article::class, $articleObject);
179
            $this->assertEquals('Example article object', $articleObject->getDescription());
180
            $this->assertEquals(
181
                'Article objects feature a Markdown payload along with some custom properties',
182
                $articleObject->getAbstract()
183
            );
184
            $this->assertArrayEquals(['apparat', 'object', 'example', 'article'], $articleObject->getKeywords());
185
            $this->assertArrayEquals(['example', 'text'], $articleObject->getCategories());
186
187
            $authorCount = count($articleObject->getAuthors());
188
            $articleObject->addAuthor(AuthorFactory::createFromString(AuthorTest::GENERIC_AUTHOR));
189
            $this->assertEquals($authorCount + 1, count($articleObject->getAuthors()));
190
        }
191
192
        /**
193
         * Load an article object and test its domain properties
194
         *
195
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
196
         * @expectedExceptionCode 1450818168
197
         */
198
        public function testLoadArticleObjectDomainProperties()
199
        {
200
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
201
            $articleObject = self::$repository->loadObject($articleObjectPath);
202
            $this->assertEquals('/system/url', $articleObject->getDomainProperty('uid'));
203
            $this->assertEquals('value', $articleObject->getDomainProperty('group:single'));
204
            $articleObject->getDomainProperty('group:invalid');
205
        }
206
207
        /**
208
         * Load an article object and test an empty domain property name
209
         *
210
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
211
         * @expectedExceptionCode 1450817720
212
         */
213
        public function testLoadArticleObjectDomainEmptyProperty()
214
        {
215
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
216
            $articleObject = self::$repository->loadObject($articleObjectPath);
217
            $articleObject->getDomainProperty('');
218
        }
219
220
        /**
221
         * Test the object facade with an absolute object URL
222
         */
223
        public function testObjectFacadeAbsolute()
224
        {
225
            $object = Object::instance(getenv('APPARAT_BASE_URL').getenv('REPOSITORY_URL').self::OBJECT_PATH);
226
            $this->assertInstanceOf(Article::class, $object);
227
        }
228
229
        /**
230
         * Test the object facade with a relative object URL
231
         */
232
        public function testObjectFacadeRelative()
233
        {
234
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
235
            $this->assertInstanceOf(Article::class, $object);
236
            foreach ($object->getAuthors() as $author) {
237
                if ($author instanceof ApparatAuthor) {
238
//				echo $author->getId()->getId();
239
                }
240
            }
241
        }
242
243
        /**
244
         * Test the object facade with an invalid relative object URL
245
         *
246
         * @expectedException \Apparat\Resource\Infrastructure\Io\File\InvalidArgumentException
247
         * @expectedExceptionCode 1447616824
248
         */
249
        public function testObjectFacadeRelativeInvalid()
250
        {
251
            $object = Object::instance(getenv('REPOSITORY_URL').'/2015/12/21/2.article/2');
252
            $this->assertInstanceOf(Article::class, $object);
253
        }
254
255
        /**
256
         * Test with a missing object type class
257
         *
258
         * @expectedException \Apparat\Object\Application\Factory\InvalidArgumentException
259
         * @expectedExceptionCode 1450824842
260
         */
261
        public function testInvalidObjectTypeClass()
262
        {
263
            TestType::addInvalidType();
264
265
            $resource = $this->getMock(ResourceInterface::class);
266
            $resource->method('getPropertyData')->willReturn([SystemProperties::COLLECTION => ['type' => 'invalid']]);
267
            $articleObjectPath = new RepositoryPath(self::$repository, '/2016/02/16/5.invalid/5');
268
269
            /** @var ResourceInterface $resource */
270
            ObjectFactory::createFromResource($articleObjectPath, $resource);
271
        }
272
273
        /**
274
         * Test instantiation of object with invalid domain properties collection
275
         *
276
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
277
         * @expectedExceptionCode 1452288429
278
         */
279
        public function testInvalidDomainPropertyCollectionClass()
280
        {
281
            $this->getMockBuilder(AbstractObject::class)
282
                ->setConstructorArgs([new RepositoryPath(self::$repository, self::OBJECT_PATH)])
283
                ->getMock();
284
        }
285
286
        /**
287
         * Test the property data
288
         */
289
        public function testObjectPropertyData()
290
        {
291
//  $frontMarkResource = Resource::frontMark('file://'.__DIR__.DIRECTORY_SEPARATOR.'Fixture'.self::OBJECT_PATH.'.md');
292
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
293
            $this->assertTrue(is_array($object->getPropertyData()));
294
//        print_r($frontMarkResource->getData());
295
//        print_r($object->getPropertyData());
296
        }
297
298
        /**
299
         * Test mutation by altering metadata
300
         */
301
        public function testMetaDataMutation()
302
        {
303
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
304
            $this->assertTrue(is_array($object->getPropertyData()));
305
            $objectUrl = $object->getAbsoluteUrl();
306
            $objectRevision = $object->getRevision();
307
            $object->setTitle($object->getTitle().' (mutated)');
308
            $object->setSlug($object->getSlug().'-mutated');
309
            $object->setDescription($object->getDescription().' (mutated)');
310
            $object->setAbstract($object->getAbstract());
311
            $object->setKeywords(array_merge($object->getKeywords(), ['mutated']));
312
            $object->setCategories($object->getCategories());
313
            $this->assertEquals($objectUrl.'+', $object->getAbsoluteUrl());
314
            $this->assertEquals($objectRevision->getRevision() + 1, $object->getRevision()->getRevision());
315
            $this->assertTrue($object->isDirty());
316
            $this->assertTrue($object->isMutated());
317
        }
318
319
        /**
320
         * Test mutation by altering domain properties
321
         */
322
        public function testDomainPropertyMutation()
323
        {
324
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
325
            $this->assertTrue(is_array($object->getPropertyData()));
326
            $objectUrl = $object->getAbsoluteUrl();
327
            $objectRevision = $object->getRevision();
328
            $object->setDomainProperty('a:b:c', 'mutated');
329
            $this->assertEquals($objectUrl.'+', $object->getAbsoluteUrl());
330
            $this->assertEquals($objectRevision->getRevision() + 1, $object->getRevision()->getRevision());
331
            $this->assertTrue($object->isDirty());
332
            $this->assertTrue($object->isMutated());
333
        }
334
335
        /**
336
         * Test change by altering processing instructions
337
         */
338
        public function testProcessingInstructionChange()
339
        {
340
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
341
            $this->assertTrue(is_array($object->getPropertyData()));
342
            $objectUrl = $object->getAbsoluteUrl();
343
            $objectRevision = $object->getRevision();
344
            $object->setProcessingInstruction('css', 'other-style.css');
345
            $this->assertEquals($objectUrl, $object->getAbsoluteUrl());
346
            $this->assertEquals($objectRevision->getRevision(), $object->getRevision()->getRevision());
347
            $this->assertTrue($object->isDirty());
348
            $this->assertFalse($object->isMutated());
349
        }
350
351
        /**
352
         * Test change by altering relations
353
         */
354
        public function testRelationChange() {
355
            // TODO: Implement
356
        }
357
358
        /**
359
         * Test the creation and persisting of an article object
360
         */
361
        public function testCreateAndPublishArticleObject()
362
        {
363
            // Create a temporary repository
364
            $tempRepoDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'temp-repo';
365
            $fileRepository = RepositoryFactory::create(
366
                getenv('REPOSITORY_URL'),
367
                [
368
                    'type' => FileAdapterStrategy::TYPE,
369
                    'root' => $tempRepoDirectory,
370
                ]
371
            );
372
            $this->assertInstanceOf(Repository::class, $fileRepository);
373
            $this->assertEquals($fileRepository->getAdapterStrategy()->getRepositorySize(), 0);
0 ignored issues
show
Bug introduced by
The method getAdapterStrategy() does not seem to exist on object<Apparat\Object\Ports\Repository>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
374
375
            // Create a new article in the temporary repository
376
            $payload = 'Revision 1 draft';
377
            /** @var Article $article */
378
            $article = $fileRepository->createObject(Type::ARTICLE, $payload);
0 ignored issues
show
Bug introduced by
The method createObject() does not exist on Apparat\Object\Ports\Repository. Did you maybe mean create()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
379
            $this->assertInstanceOf(Article::class, $article);
380
            $this->assertEquals($payload, $article->getPayload());
381
            $this->assertFileExists($tempRepoDirectory.
382
                str_replace('/', DIRECTORY_SEPARATOR, $article->getRepositoryPath()
383
                    ->withExtension(getenv('OBJECT_RESOURCE_EXTENSION'))));
384
385
            // Alter and persist the object
386
            $article->setPayload('Revision 1 draft (updated)');
387
            $article->persist();
388
389
            // Publish and persist the first object revision
390
            $article->setPayload('Revision 1');
391
            $article->publish();
392
            $article->persist();
393
394
            // Draft a second object revision
395
            $article->setPayload('Revision 2 draft');
396
            $article->persist();
397
398
            // Publish and persist the second object revision
399
            $article->publish();
400
            $article->setPayload('Revision 2');
401
            $article->persist();
402
403
            // Delete temporary repository
404
//            $this->deleteRecursive($tempRepoDirectory);
405
        }
406
407
        /**
408
         * Test to persist an earlier revision
409
         */
410
        public function testPersistEarlierRevision() {
411
412
        }
413
414
        /**
415
         * Test the creation and persisting of an article object with failing file lock
416
         *
417
         * @expectedException \Apparat\Object\Domain\Repository\RuntimeException
418
         * @expectedExceptionCode 1461406873
419
         */
420
        public function testCreateArticleObjectLockingImpossible() {
421
            putenv('MOCK_FLOCK=1');
422
            $this->testCreateAndPublishArticleObject();
423
        }
424
425
        /**
426
         * Recursively register a directory and all nested files and directories for deletion on teardown
427
         *
428
         * @param string $directory Directory
429
         */
430
        protected function deleteRecursive($directory)
431
        {
432
            $this->tmpFiles[] = $directory;
433
            foreach (scandir($directory) as $item) {
434
                if (!preg_match('%^\.+$%', $item)) {
435
                    $path = $directory.DIRECTORY_SEPARATOR.$item;
436
                    if (is_dir($path)) {
437
                        $this->deleteRecursive($path);
438
                    } else {
439
                        $this->tmpFiles[] = $path;
440
                    }
441
                }
442
            }
443
        }
444
    }
445
}
446
447
namespace Apparat\Object\Infrastructure\Repository {
448
449
    /**
450
     * Mocked version of the native flock() function
451
     *
452
     * @param resource $handle An open file pointer.
453
     * @param int $operation Operation is one of the following: LOCK_SH to acquire a shared lock (reader).
454
     * @param int $wouldblock The optional third argument is set to true if the lock would block (EWOULDBLOCK errno
455
     *     condition).
456
     * @return bool True on success or False on failure.
457
     */
458
    function flock($handle, $operation, &$wouldblock = null)
459
    {
460
        return (getenv('MOCK_FLOCK') != 1) ? \flock($handle, $operation, $wouldblock) : false;
461
    }
462
}
463