Completed
Push — master ( 3bf76e...9f9c26 )
by Joschi
04:22
created

ObjectTest::setUpBeforeClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 1
Metric Value
c 7
b 0
f 1
dl 0
loc 14
rs 9.4285
cc 1
eloc 7
nc 1
nop 0
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\Model\Object\AbstractObject;
42
    use Apparat\Object\Domain\Model\Object\Id;
43
    use Apparat\Object\Domain\Model\Object\ResourceInterface;
44
    use Apparat\Object\Domain\Model\Object\Revision;
45
    use Apparat\Object\Domain\Model\Object\Type;
46
    use Apparat\Object\Domain\Model\Path\RepositoryPath;
47
    use Apparat\Object\Domain\Model\Properties\SystemProperties;
48
    use Apparat\Object\Domain\Repository\Repository;
49
    use Apparat\Object\Infrastructure\Repository\FileAdapterStrategy;
50
    use Apparat\Object\Ports\Object;
51
    use Apparat\Object\Ports\Repository as RepositoryFactory;
52
53
    /**
54
     * Object tests
55
     *
56
     * @package Apparat\Object
57
     * @subpackage Apparat\Object\Test
58
     */
59
    class ObjectTest extends AbstractRepositoryEnabledTest
60
    {
61
        /**
62
         * Example object path
63
         *
64
         * @var string
65
         */
66
        const OBJECT_PATH = '/2015/12/21/1.article/1';
67
68
        /**
69
         * Tears down the fixture
70
         */
71
        public function tearDown()
72
        {
73
            putenv('MOCK_FLOCK');
74
            TestType::removeInvalidType();
75
            parent::tearDown();
76
        }
77
78
        /**
79
         * Test undefined object type
80
         *
81
         * @expectedException \Apparat\Object\Application\Factory\InvalidArgumentException
82
         * @expectedExceptionCode 1450905868
83
         */
84
        public function testUndefinedObjectType()
85
        {
86
            $resource = $this->getMock(ResourceInterface::class);
87
            $resource->method('getPropertyData')->willReturn([]);
88
            $repositoryPath = $this->getMockBuilder(RepositoryPath::class)->disableOriginalConstructor()->getMock();
89
90
            /** @var ResourceInterface $resource */
91
            /** @var RepositoryPath $repositoryPath */
92
            ObjectFactory::createFromResource($repositoryPath, $resource);
93
        }
94
95
        /**
96
         * Test invalid object type
97
         *
98
         * @expectedException \Apparat\Object\Domain\Model\Object\InvalidArgumentException
99
         * @expectedExceptionCode 1449871242
100
         */
101
        public function testInvalidObjectType()
102
        {
103
            $resource = $this->getMock(ResourceInterface::class);
104
            $resource->method('getPropertyData')->willReturn([SystemProperties::COLLECTION => ['type' => 'invalid']]);
105
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
106
107
            /** @var ResourceInterface $resource */
108
            ObjectFactory::createFromResource($articleObjectPath, $resource);
109
        }
110
111
        /**
112
         * Load an article object and test basic properties
113
         */
114
        public function testLoadArticleObject()
115
        {
116
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
117
            $articleObject = self::$repository->loadObject($articleObjectPath);
118
            $this->assertEquals(
119
                getenv('APPARAT_BASE_URL').getenv('REPOSITORY_URL').self::OBJECT_PATH,
120
                $articleObject->getAbsoluteUrl()
121
            );
122
        }
123
124
        /**
125
         * Load an article object and test its system properties
126
         */
127
        public function testLoadArticleObjectSystemProperties()
128
        {
129
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
130
            $articleObject = self::$repository->loadObject($articleObjectPath);
131
            $this->assertInstanceOf(Article::class, $articleObject);
132
            $this->assertEquals(new Id(1), $articleObject->getId());
133
            $this->assertEquals(new Type(Type::ARTICLE), $articleObject->getType());
134
            $this->assertEquals(new Revision(1), $articleObject->getRevision());
135
            $this->assertFalse($articleObject->isDraft());
136
            $this->assertEquals(new \DateTimeImmutable('2015-12-21T22:30:00'), $articleObject->getCreated());
137
            $this->assertEquals(new \DateTimeImmutable('2015-12-21T22:45:00'), $articleObject->getPublished());
138
            $this->assertEquals('a123456789012345678901234567890123456789', $articleObject->getHash());
139
            $this->assertEquals(
140
                "# Example article object\n\nThis file is an example for an object of type `\"article\"`.\n",
141
                $articleObject->getPayload()
142
            );
143
        }
144
145
        /**
146
         * Load an article object and test its meta properties
147
         */
148
        public function testLoadArticleObjectMetaProperties()
149
        {
150
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
151
            $articleObject = self::$repository->loadObject($articleObjectPath);
152
            $this->assertInstanceOf(Article::class, $articleObject);
153
            $this->assertEquals('Example article object', $articleObject->getDescription());
154
            $this->assertEquals(
155
                'Article objects feature a Markdown payload along with some custom properties',
156
                $articleObject->getAbstract()
157
            );
158
            $this->assertArrayEquals(['apparat', 'object', 'example', 'article'], $articleObject->getKeywords());
159
            $this->assertArrayEquals(['example', 'text'], $articleObject->getCategories());
160
161
            // TODO Replace with contributed-by relations
162
//            $authorCount = count($articleObject->getAuthors());
163
//            $articleObject->addAuthor(AuthorFactory::createFromString(AuthorTest::GENERIC_AUTHOR));
164
//            $this->assertEquals($authorCount + 1, count($articleObject->getAuthors()));
165
        }
166
167
        /**
168
         * Load an article object and test its domain properties
169
         *
170
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
171
         * @expectedExceptionCode 1450818168
172
         */
173
        public function testLoadArticleObjectDomainProperties()
174
        {
175
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
176
            $articleObject = self::$repository->loadObject($articleObjectPath);
177
            $this->assertEquals('/system/url', $articleObject->getDomainProperty('uid'));
178
            $this->assertEquals('value', $articleObject->getDomainProperty('group:single'));
179
            $articleObject->getDomainProperty('group:invalid');
180
        }
181
182
        /**
183
         * Load an article object and test an empty domain property name
184
         *
185
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
186
         * @expectedExceptionCode 1450817720
187
         */
188
        public function testLoadArticleObjectDomainEmptyProperty()
189
        {
190
            $articleObjectPath = new RepositoryPath(self::$repository, self::OBJECT_PATH);
191
            $articleObject = self::$repository->loadObject($articleObjectPath);
192
            $articleObject->getDomainProperty('');
193
        }
194
195
        /**
196
         * Test the object facade with an absolute object URL
197
         */
198
        public function testObjectFacadeAbsolute()
199
        {
200
            $object = Object::instance(getenv('APPARAT_BASE_URL').getenv('REPOSITORY_URL').self::OBJECT_PATH);
201
            $this->assertInstanceOf(Article::class, $object);
202
        }
203
204
        /**
205
         * Test the object facade with a relative object URL
206
         */
207
        public function testObjectFacadeRelative()
208
        {
209
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
210
            $this->assertInstanceOf(Article::class, $object);
211
        }
212
213
        /**
214
         * Test the object facade with an invalid relative object URL
215
         *
216
         * @expectedException \Apparat\Resource\Infrastructure\Io\File\InvalidArgumentException
217
         * @expectedExceptionCode 1447616824
218
         */
219
        public function testObjectFacadeRelativeInvalid()
220
        {
221
            $object = Object::instance(getenv('REPOSITORY_URL').'/2015/12/21/2.article/2');
222
            $this->assertInstanceOf(Article::class, $object);
223
        }
224
225
        /**
226
         * Test with a missing object type class
227
         *
228
         * @expectedException \Apparat\Object\Application\Factory\InvalidArgumentException
229
         * @expectedExceptionCode 1450824842
230
         */
231
        public function testInvalidObjectTypeClass()
232
        {
233
            TestType::addInvalidType();
234
235
            $resource = $this->getMock(ResourceInterface::class);
236
            $resource->method('getPropertyData')->willReturn([SystemProperties::COLLECTION => ['type' => 'invalid']]);
237
            $articleObjectPath = new RepositoryPath(self::$repository, '/2016/02/16/5.invalid/5');
238
239
            /** @var ResourceInterface $resource */
240
            ObjectFactory::createFromResource($articleObjectPath, $resource);
241
        }
242
243
        /**
244
         * Test instantiation of object with invalid domain properties collection
245
         *
246
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
247
         * @expectedExceptionCode 1452288429
248
         */
249
        public function testInvalidDomainPropertyCollectionClass()
250
        {
251
            $this->getMockBuilder(AbstractObject::class)
252
                ->setConstructorArgs([new RepositoryPath(self::$repository, self::OBJECT_PATH)])
253
                ->getMock();
254
        }
255
256
        /**
257
         * Test the property data
258
         */
259
        public function testObjectPropertyData()
260
        {
261
//  $frontMarkResource = Resource::frontMark('file://'.__DIR__.DIRECTORY_SEPARATOR.'Fixture'.self::OBJECT_PATH.'.md');
262
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
263
            $this->assertTrue(is_array($object->getPropertyData()));
264
//        print_r($frontMarkResource->getData());
265
//        print_r($object->getPropertyData());
266
        }
267
268
        /**
269
         * Test mutation by altering metadata
270
         *
271
         * @expectedException \Apparat\Object\Domain\Model\Properties\OutOfBoundsException
272
         * @expectedExceptionCode 1462632083
273
         */
274
        public function testMetaDataMutation()
275
        {
276
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
277
            $this->assertTrue(is_array($object->getPropertyData()));
278
            $objectUrl = $object->getAbsoluteUrl();
279
            $objectRevision = $object->getRevision();
280
            $object->setTitle($object->getTitle().' (mutated)');
281
            $object->setSlug($object->getSlug().'-mutated');
282
            $object->setDescription($object->getDescription().' (mutated)');
283
            $object->setAbstract($object->getAbstract());
284
            $object->setLicense(ltrim($object->getLicense().', ', ', ').'MIT');
285
            $object->setKeywords(array_merge($object->getKeywords(), ['mutated']));
286
            $object->setCategories($object->getCategories());
287
            $this->assertEquals($objectUrl.'+', $object->getAbsoluteUrl());
288
            $this->assertEquals($objectRevision->getRevision() + 1, $object->getRevision()->getRevision());
289
            $this->assertTrue($object->isDirty());
290
            $this->assertTrue($object->isMutated());
291
            $this->assertEquals('MIT', $object->getLicense());
292
            $this->assertEquals(Object::PRIVACY_PRIVATE, $object->getPrivacy());
293
            $this->assertEquals(Object::PRIVACY_PUBLIC, $object->setPrivacy(Object::PRIVACY_PUBLIC)->getPrivacy());
294
            $object->setPrivacy('invalid');
295
        }
296
297
        /**
298
         * Test mutation by altering domain properties
299
         */
300
        public function testDomainPropertyMutation()
301
        {
302
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
303
            $this->assertTrue(is_array($object->getPropertyData()));
304
            $objectUrl = $object->getAbsoluteUrl();
305
            $objectRevision = $object->getRevision();
306
            $object->setDomainProperty('a:b:c', 'mutated');
307
            $this->assertEquals($objectUrl.'+', $object->getAbsoluteUrl());
308
            $this->assertEquals($objectRevision->getRevision() + 1, $object->getRevision()->getRevision());
309
            $this->assertTrue($object->isDirty());
310
            $this->assertTrue($object->isMutated());
311
        }
312
313
        /**
314
         * Test change by altering processing instructions
315
         */
316
        public function testProcessingInstructionChange()
317
        {
318
            $object = Object::instance(getenv('REPOSITORY_URL').self::OBJECT_PATH);
319
            $this->assertTrue(is_array($object->getPropertyData()));
320
            $objectUrl = $object->getAbsoluteUrl();
321
            $objectRevision = $object->getRevision();
322
            $object->setProcessingInstruction('css', 'other-style.css');
323
            $this->assertEquals($objectUrl, $object->getAbsoluteUrl());
324
            $this->assertEquals($objectRevision->getRevision(), $object->getRevision()->getRevision());
325
            $this->assertTrue($object->isDirty());
326
            $this->assertFalse($object->isMutated());
327
        }
328
329
        /**
330
         * Test change by altering relations
331
         */
332
        public function testRelationChange()
333
        {
334
            // TODO: Implement
335
        }
336
337
        /**
338
         * Test to persist an earlier revision
339
         */
340
        public function testPersistEarlierRevision()
341
        {
342
            // TODO
343
        }
344
345
        /**
346
         * Test the creation and persisting of an article object with failing file lock
347
         *
348
         * @expectedException \Apparat\Object\Domain\Repository\RuntimeException
349
         * @expectedExceptionCode 1461406873
350
         */
351
        public function testCreateArticleObjectLockingImpossible()
352
        {
353
            putenv('MOCK_FLOCK=1');
354
            $this->testCreateAndPublishArticleObject();
355
        }
356
357
        /**
358
         * Test the creation and persisting of an article object
359
         */
360
        public function testCreateAndPublishArticleObject()
361
        {
362
            // Create a temporary repository
363
            $tempRepoDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'temp-repo';
364
            $fileRepository = RepositoryFactory::create(
365
                getenv('REPOSITORY_URL'),
366
                [
367
                    'type' => FileAdapterStrategy::TYPE,
368
                    'root' => $tempRepoDirectory,
369
                ]
370
            );
371
            $this->assertInstanceOf(Repository::class, $fileRepository);
372
            $this->assertEquals($fileRepository->getAdapterStrategy()->getRepositorySize(), 0);
373
374
            // Create a new article in the temporary repository
375
            $payload = 'Revision 1 draft';
376
            /** @var Article $article */
377
            $article = $fileRepository->createObject(Type::ARTICLE, $payload);
378
            $this->assertInstanceOf(Article::class, $article);
379
            $this->assertEquals($payload, $article->getPayload());
380
            $this->assertFileExists($tempRepoDirectory.
381
                str_replace('/', DIRECTORY_SEPARATOR, $article->getRepositoryPath()
382
                    ->withExtension(getenv('OBJECT_RESOURCE_EXTENSION'))));
383
384
            // Alter and persist the object
385
            $article->setPayload('Revision 1 draft (updated)');
386
            $article->persist();
387
388
            // Publish and persist the first object revision
389
            $article->setPayload('Revision 1');
390
            $article->publish();
391
            $article->persist();
392
393
            // Draft a second object revision
394
            $article->setPayload('Revision 2 draft');
395
            $article->persist();
396
397
            // Publish and persist the second object revision
398
            $article->publish();
399
            $article->setPayload('Revision 2');
400
            $article->persist();
401
402
            // Delete temporary repository
403
//            $this->deleteRecursive($tempRepoDirectory);
404
        }
405
406
        /**
407
         * Recursively register a directory and all nested files and directories for deletion on teardown
408
         *
409
         * @param string $directory Directory
410
         */
411
        protected function deleteRecursive($directory)
412
        {
413
            $this->tmpFiles[] = $directory;
414
            foreach (scandir($directory) as $item) {
415
                if (!preg_match('%^\.+$%', $item)) {
416
                    $path = $directory.DIRECTORY_SEPARATOR.$item;
417
                    if (is_dir($path)) {
418
                        $this->deleteRecursive($path);
419
                        continue;
420
                    }
421
422
                    $this->tmpFiles[] = $path;
423
                }
424
            }
425
        }
426
    }
427
}
428
429
namespace Apparat\Object\Infrastructure\Repository {
430
431
    /**
432
     * Mocked version of the native flock() function
433
     *
434
     * @param resource $handle An open file pointer.
435
     * @param int $operation Operation is one of the following: LOCK_SH to acquire a shared lock (reader).
436
     * @param int $wouldblock The optional third argument is set to true if the lock would block (EWOULDBLOCK errno
437
     *     condition).
438
     * @return bool True on success or False on failure.
439
     */
440
    function flock($handle, $operation, &$wouldblock = null)
441
    {
442
        return (getenv('MOCK_FLOCK') != 1) ? \flock($handle, $operation, $wouldblock) : false;
443
    }
444
}
445