Completed
Push — master ( e6ef7b...361fa3 )
by Joschi
07:11
created

testLoadArticleObjectDomainEmptyProperty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 9.4285
1
<?php
2
3
/**
4
 * apparat-object
5
 *
6
 * @category    Apparat
7
 * @package     Apparat\Object
8
 * @subpackage  Apparat\Object\Tests
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\Kernel\Ports\Kernel;
40
    use Apparat\Object\Application\Model\Object\Article;
41
    use Apparat\Object\Application\Service\TypeService;
42
    use Apparat\Object\Domain\Model\Object\Id;
43
    use Apparat\Object\Domain\Model\Object\Revision;
44
    use Apparat\Object\Domain\Model\Object\Type;
45
    use Apparat\Object\Domain\Model\Properties\MetaProperties;
46
    use Apparat\Object\Domain\Model\Uri\RepositoryLocator;
47
    use Apparat\Object\Ports\Types\Object as ObjectTypes;
48
49
    /**
50
     * Article object tests
51
     *
52
     * @package Apparat\Object
53
     * @subpackage Apparat\Object\Tests
54
     */
55
    class ArticleObjectTest extends AbstractObjectTest
56
    {
57
        /**
58
         * Example object locator
59
         *
60
         * @var string
61
         */
62
        const OBJECT_LOCATOR = '/2015/12/21/1-article/1';
63
        /**
64
         * Example hidden object locator
65
         *
66
         * @var string
67
         */
68
        const HIDDEN_OBJECT_LOCATOR = '/2016/05/26/6-article/6';
69
        /**
70
         * Default privacy
71
         *
72
         * @var string
73
         */
74
        protected static $defaultPrivacy;
75
76
        /**
77
         * Setup
78
         */
79
        public static function setUpBeforeClass()
80
        {
81
            parent::setUpBeforeClass();
82
            self::$defaultPrivacy = getenv('OBJECT_DEFAULT_PRIVACY');
83
84
            TypeService::enableType(Type::ARTICLE);
85
            TypeService::enableType(Type::CONTACT);
86
            TypeService::enableType(Type::GEO);
87
            TypeService::enableType(Type::IMAGE);
88
            TypeService::enableType(Type::NOTE);
89
        }
90
91
        /**
92
         * Tears down the fixture
93
         */
94
        public function tearDown()
95
        {
96
            putenv('MOCK_FLOCK');
97
            putenv('MOCK_RENAME');
98
            putenv('OBJECT_DEFAULT_PRIVACY='.self::$defaultPrivacy);
99
            parent::tearDown();
100
        }
101
102
        /**
103
         * Load an article object
104
         *
105
         * @expectedException \Apparat\Object\Domain\Model\Object\OutOfBoundsException
106
         * @expectedExceptionCode 1461619783
107
         */
108
        public function testLoadArticleObject()
109
        {
110
            $articleObjectLocator = new RepositoryLocator(self::$repository, self::OBJECT_LOCATOR);
111
            $articleObject = self::$repository->loadObject($articleObjectLocator);
112
            $this->assertEquals(
113
                getenv('APPARAT_BASE_URL').rtrim('/'.getenv('REPOSITORY_URL'), '/').self::OBJECT_LOCATOR,
114
                $articleObject->getAbsoluteUrl()
115
            );
116
            $this->assertFalse($articleObject->isDeleted());
117
            $this->assertFalse($articleObject->getRepositoryLocator()->isHidden());
118
119
            /** @var Revision $invalidRevision */
120
            $invalidRevision = Kernel::create(Revision::class, [99]);
121
            $articleObject->useRevision($invalidRevision);
122
        }
123
124
        /**
125
         * Load a hidden article object
126
         */
127
        public function testLoadHiddenArticleObject()
128
        {
129
            $articleObjectLocator = new RepositoryLocator(self::$repository, self::HIDDEN_OBJECT_LOCATOR);
130
            $articleObject = self::$repository->loadObject($articleObjectLocator);
131
            $this->assertTrue($articleObject->isDeleted());
132
            $this->assertTrue($articleObject->getRepositoryLocator()->isHidden());
133
        }
134
135
        /**
136
         * Load an article object and test its system properties
137
         */
138
        public function testLoadArticleObjectSystemProperties()
139
        {
140
            $articleObjectLocator = new RepositoryLocator(self::$repository, self::OBJECT_LOCATOR);
141
            $articleObject = self::$repository->loadObject($articleObjectLocator);
142
            $this->assertInstanceOf(Article::class, $articleObject);
143
            $this->assertEquals(new Id(1), $articleObject->getId());
144
            $this->assertEquals(Kernel::create(Type::class, [ObjectTypes::ARTICLE]), $articleObject->getObjectType());
145
            $this->assertEquals(new Revision(1), $articleObject->getRevision());
146
            $this->assertFalse($articleObject->isDraft());
147
            $this->assertTrue($articleObject->isPublished());
148
            $this->assertEquals(new \DateTimeImmutable('2015-12-21T22:30:00'), $articleObject->getCreated());
149
            $this->assertEquals(new \DateTimeImmutable('2015-12-21T22:45:00'), $articleObject->getPublished());
150
            $this->assertNull($articleObject->getDeleted());
151
            $this->assertEquals('en', $articleObject->getLanguage());
152
            $this->assertEquals(
153
                "# Example article object\n\nThis file is an example for an object of type `\"article\"`. ".
154
                "It has a link to [Joschi Kuphal's website](https://jkphl.is) and features his avatar:\n".
155
                "![Joschi Kuphal](https://jkphl.is/avatar.jpg)\n",
156
                $articleObject->getPayload()
157
            );
158
        }
159
160
        /**
161
         * Load an article object and test its meta properties
162
         */
163
        public function testLoadArticleObjectMetaProperties()
164
        {
165
            $articleObjectLocator = new RepositoryLocator(self::$repository, self::OBJECT_LOCATOR);
166
            $articleObject = self::$repository->loadObject($articleObjectLocator);
167
            $this->assertInstanceOf(Article::class, $articleObject);
168
            $this->assertEquals('Example article object', $articleObject->getDescription());
169
            $this->assertEquals(
170
                'Article objects feature a Markdown payload along with some custom properties',
171
                $articleObject->getAbstract()
172
            );
173
            $this->assertArrayEquals(['apparat', 'object', 'example', 'article'], $articleObject->getKeywords());
174
            $this->assertArrayEquals(['example', 'text'], $articleObject->getCategories());
175
176
            // TODO Replace with contributed-by relations
177
//            $authorCount = count($articleObject->getAuthors());
178
//            $articleObject->addAuthor(AuthorFactory::createFromString(AuthorTest::GENERIC_AUTHOR));
179
//            $this->assertEquals($authorCount + 1, count($articleObject->getAuthors()));
180
        }
181
182
        /**
183
         * Load an article object and test its domain properties
184
         *
185
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
186
         * @expectedExceptionCode 1450818168
187
         */
188
        public function testLoadArticleObjectDomainProperties()
189
        {
190
            $articleObjectLocator = new RepositoryLocator(self::$repository, self::OBJECT_LOCATOR);
191
            $articleObject = self::$repository->loadObject($articleObjectLocator);
192
            $this->assertEquals('/system/url', $articleObject->getDomain('uid'));
193
            $this->assertEquals('value', $articleObject->getDomain('group:single'));
194
            $articleObject->getDomain('group:invalid');
195
        }
196
197
        /**
198
         * Load an article object and test an empty domain property name
199
         *
200
         * @expectedException \Apparat\Object\Domain\Model\Properties\InvalidArgumentException
201
         * @expectedExceptionCode 1450817720
202
         */
203
        public function testLoadArticleObjectDomainEmptyProperty()
204
        {
205
            $articleObjectLocator = new RepositoryLocator(self::$repository, self::OBJECT_LOCATOR);
206
            $articleObject = self::$repository->loadObject($articleObjectLocator);
207
            $articleObject->getDomain('');
208
        }
209
210
        /**
211
         * Test the creation and persisting of an article object with failing file lock
212
         *
213
         * @expectedException \Apparat\Object\Domain\Repository\RuntimeException
214
         * @expectedExceptionCode 1461406873
215
         */
216
        public function testCreateArticleObjectLockingImpossible()
217
        {
218
            putenv('MOCK_FLOCK=1');
219
            $this->testCreateAndPublishArticleObject();
220
        }
221
222
        /**
223
         * Test the creation and persisting of an article object
224
         *
225
         * @expectedException \Apparat\Object\Domain\Model\Object\RuntimeException
226
         * @expectedExceptionCode 1462124874
227
         */
228
        public function testCreateAndPublishArticleObject()
229
        {
230
            putenv('OBJECT_DEFAULT_PRIVACY=public');
231
232
            // Create a temporary repository & article
233
            $tempRepoDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'temp-repo';
234
            $payload = 'Revision 1 draft';
235
            $creationDate = new \DateTimeImmutable('yesterday');
236
            $article = $this->createRepositoryAndArticleObject($tempRepoDirectory, $payload, $creationDate);
237
            $this->assertInstanceOf(Article::class, $article);
238
            $this->assertEquals(MetaProperties::PRIVACY_PUBLIC, $article->getPrivacy());
239
            $this->assertEquals($payload, $article->getPayload());
240
            $this->assertFileExists($tempRepoDirectory.
241
                str_replace('/', DIRECTORY_SEPARATOR, $article->getRepositoryLocator()
242
                    ->withExtension(getenv('OBJECT_RESOURCE_EXTENSION'))));
243
            $this->assertEquals($creationDate, $article->getCreated());
244
245
            // Alter and persist the object
246
            $article->setPayload('Revision 1 draft (updated)');
247
            $article->persist();
248
249
            // Publish and persist the first object revision
250
            $article->setPayload('Revision 1');
251
            $article->publish();
252
            $article->persist();
253
254
            // Draft a second object revision
255
            $article->setPayload('Revision 2 draft');
256
            $article->persist();
257
258
            // Publish and persist the second object revision
259
            $article->publish();
260
            $article->setPayload('Revision 2');
261
            $article->persist();
262
263
            // Modify and persist a third object draft revision
264
            $article->setPayload('Revision 3 draft');
265
            $article->persist();
266
267
            // Wait for 2 seconds, modify and re-persist the object
268
            $now = time();
269
            sleep(2);
270
            $article->setPayload('Revision 3 draft (delayed modification)');
271
            $article->persist();
272
            $this->assertGreaterThanOrEqual($now + 2, $article->getModified()->format('U'));
273
274
            // Iterate through all object revisions
275
            foreach ($article as $articleRevisionIndex => $articleRevision) {
276
                $this->assertInstanceOf(Article::class, $articleRevision);
277
                $this->assertInstanceOf(Revision::class, $articleRevisionIndex);
278
            }
279
280
            // Publish and persist a third object draft revision
281
            $article->publish()->persist();
282
283
            // Delete the object (and all it's revisions)
284
            $article->delete()->persist();
285
286
            // Undelete the object (and all it's revisions)
287
            $article->undelete()->persist();
288
289
            // Use the first revision
290
            $article->rewind();
291
292
            // Delete temporary repository
293
            $this->deleteRecursive($tempRepoDirectory);
294
295
            $article->persist();
296
        }
297
298
        /**
299
         * Create a temporary repository and article object
300
         *
301
         * @param string $tempRepoDirectory Repository directory
302
         * @param string $payload Article payload
303
         * @param \DateTimeInterface $creationDate Article creation date
304
         * @return Article Article object
305
         */
306
        protected function createRepositoryAndArticleObject(
307
            $tempRepoDirectory,
308
            $payload,
309
            \DateTimeInterface $creationDate = null
310
        ) {
311
            $fileRepository = $this->createRepository($tempRepoDirectory);
312
313
            // Create a new article in the temporary repository
314
            return $fileRepository->createObject(ObjectTypes::ARTICLE, $payload, [], $creationDate);
315
        }
316
317
        /**
318
         * Test the creation and persisting of an article object with failing file lock
319
         *
320
         * @expectedException \Apparat\Object\Infrastructure\Repository\RuntimeException
321
         * @expectedExceptionCode 1464269155
322
         */
323
        public function testDeleteArticleObjectImpossible()
324
        {
325
            putenv('MOCK_RENAME=1');
326
            $this->tmpFiles[] = $tempRepoDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'temp-repo';
327
            $article = $this->createRepositoryAndArticleObject($tempRepoDirectory, 'Revision 1 draft');
328
            $this->deleteRecursive($tempRepoDirectory);
329
            $article->delete()->persist();
330
        }
331
332
        /**
333
         * Test the creation and persisting of an article object with failing file lock
334
         *
335
         * @expectedException \Apparat\Object\Infrastructure\Repository\RuntimeException
336
         * @expectedExceptionCode 1464269179
337
         */
338
        public function testUndeleteArticleObjectImpossible()
339
        {
340
            $this->tmpFiles[] = $tempRepoDirectory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'temp-repo';
341
            $article = $this->createRepositoryAndArticleObject($tempRepoDirectory, 'Revision 1 draft');
342
            $article->getRepositoryLocator()->getRepository()->deleteObject($article);
343
            $this->deleteRecursive($tempRepoDirectory);
344
            putenv('MOCK_RENAME=1');
345
            $article->undelete()->persist();
346
        }
347
    }
348
}
349
350
namespace Apparat\Object\Infrastructure\Repository {
351
352
    /**
353
     * Mocked version of the native flock() function
354
     *
355
     * @param resource $handle An open file pointer.
356
     * @param int $operation Operation is one of the following: LOCK_SH to acquire a shared lock (reader).
357
     * @param int $wouldblock The optional third argument is set to true if the lock would block (EWOULDBLOCK errno
358
     *     condition).
359
     * @return bool True on success or False on failure.
360
     */
361
    function flock($handle, $operation, &$wouldblock = null)
362
    {
363
        return (getenv('MOCK_FLOCK') != 1) ? \flock($handle, $operation, $wouldblock) : false;
364
    }
365
366
    /**
367
     * Mocked version of the native rename() function
368
     *
369
     * @param string $oldname The old name. The wrapper used in oldname must match the wrapper used in newname.
370
     * @param string $newname The new name.
371
     * @return bool true on success or false on failure.
372
     */
373
    function rename($oldname, $newname)
374
    {
375
        return (getenv('MOCK_RENAME') != 1) ? \rename($oldname, $newname) : false;
376
    }
377
}
378