Skip to content

Commit 21db1e4

Browse files
committed
add test
1 parent ee97099 commit 21db1e4

1 file changed

Lines changed: 372 additions & 0 deletions

File tree

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LightTest\Unit\App\Fixture;
6+
7+
use DateTimeImmutable;
8+
use Doctrine\Common\DataFixtures\ReferenceRepository;
9+
use Doctrine\ORM\EntityManagerInterface;
10+
use Doctrine\ORM\EntityRepository;
11+
use Doctrine\ORM\Mapping\ClassMetadata;
12+
use Doctrine\ORM\UnitOfWork;
13+
use Light\App\Fixture\AuthorLoader;
14+
use Light\App\Fixture\CategoryLoader;
15+
use Light\App\Fixture\PostLoader;
16+
use Light\Blog\Entity\Author;
17+
use Light\Blog\Entity\Category;
18+
use Light\Blog\Entity\Post;
19+
use Light\Blog\Enum\PostStatusEnum;
20+
use LightTest\Unit\UnitTest;
21+
use PHPUnit\Framework\MockObject\Exception;
22+
use PHPUnit\Framework\MockObject\MockObject;
23+
use RuntimeException;
24+
25+
use function bin2hex;
26+
use function dirname;
27+
use function file_put_contents;
28+
use function is_dir;
29+
use function is_file;
30+
use function json_encode;
31+
use function mkdir;
32+
use function random_bytes;
33+
use function rmdir;
34+
use function sprintf;
35+
use function sys_get_temp_dir;
36+
use function unlink;
37+
38+
use const DIRECTORY_SEPARATOR;
39+
40+
class PostLoaderTest extends UnitTest
41+
{
42+
private string $jsonFile;
43+
private Author $author;
44+
private Category $category;
45+
46+
protected function setUp(): void
47+
{
48+
parent::setUp();
49+
50+
$this->jsonFile = sprintf(
51+
'%s%slight-post-loader-%s%sarticles_cleaned.json',
52+
sys_get_temp_dir(),
53+
DIRECTORY_SEPARATOR,
54+
bin2hex(random_bytes(8)),
55+
DIRECTORY_SEPARATOR
56+
);
57+
58+
mkdir(dirname($this->jsonFile), 0775, true);
59+
60+
$this->author = new Author();
61+
$this->category = new Category();
62+
}
63+
64+
protected function tearDown(): void
65+
{
66+
if (is_file($this->jsonFile)) {
67+
unlink($this->jsonFile);
68+
}
69+
70+
$directory = dirname($this->jsonFile);
71+
if (is_dir($directory)) {
72+
rmdir($directory);
73+
}
74+
75+
parent::tearDown();
76+
}
77+
78+
/**
79+
* @throws Exception
80+
*/
81+
public function testLoadCreatesPostWithCustomSlugWhenNothingMatchesInDb(): void
82+
{
83+
$this->writeArticles([
84+
$this->article('A post', ['post_slug' => ' custom-slug ']),
85+
]);
86+
87+
$lookups = [];
88+
$manager = $this->createEntityManager($this->createRepository([], $lookups));
89+
90+
$persisted = null;
91+
$manager->expects($this->once())->method('persist')
92+
->willReturnCallback(function (Post $post) use (&$persisted): void {
93+
$persisted = $post;
94+
});
95+
$manager->expects($this->once())->method('flush');
96+
97+
$this->expectOutputString("CREATE: A post\n");
98+
$this->runLoader($manager);
99+
100+
$this->assertInstanceOf(Post::class, $persisted);
101+
$this->assertSame('custom-slug', $persisted->getSlug());
102+
$this->assertSame(['custom-slug', 'a-post'], $lookups);
103+
}
104+
105+
/**
106+
* @throws Exception
107+
*/
108+
public function testLoadUpdatesExistingPostFoundByCustomSlug(): void
109+
{
110+
$this->writeArticles([
111+
$this->article('A post', ['post_slug' => 'custom-slug']),
112+
]);
113+
114+
$existing = $this->existingPost('custom-slug', 'A post');
115+
116+
$lookups = [];
117+
$manager = $this->createEntityManager($this->createRepository(['custom-slug' => $existing], $lookups));
118+
$manager->expects($this->never())->method('persist');
119+
120+
$this->expectOutputString("UNCHANGED: A post\n");
121+
$this->runLoader($manager);
122+
123+
$this->assertSame('custom-slug', $existing->getSlug());
124+
$this->assertSame(['custom-slug'], $lookups);
125+
}
126+
127+
/**
128+
* @throws Exception
129+
*/
130+
public function testLoadRenamesExistingPostFoundByTitleSlugToCustomSlug(): void
131+
{
132+
$this->writeArticles([
133+
$this->article('A post', ['post_slug' => 'custom-slug']),
134+
]);
135+
136+
// Imported before post_slug was added to the JSON.
137+
$existing = $this->existingPost('a-post', 'A post');
138+
139+
$lookups = [];
140+
$manager = $this->createEntityManager($this->createRepository(['a-post' => $existing], $lookups));
141+
$manager->expects($this->never())->method('persist');
142+
143+
$this->expectOutputString("UPDATE: A post\n");
144+
$this->runLoader($manager);
145+
146+
$this->assertSame('custom-slug', $existing->getSlug());
147+
$this->assertSame(['custom-slug', 'a-post'], $lookups);
148+
}
149+
150+
/**
151+
* @throws Exception
152+
*/
153+
public function testLoadFallsBackToTitleSlugWhenPostSlugIsMissingOrEmpty(): void
154+
{
155+
$this->writeArticles([
156+
$this->article('A post'),
157+
$this->article('A post', ['post_slug' => '']),
158+
]);
159+
160+
$lookups = [];
161+
$manager = $this->createEntityManager($this->createRepository([], $lookups));
162+
$manager->expects($this->exactly(2))->method('persist');
163+
164+
$this->expectOutputString("CREATE: A post\nCREATE: A post\n");
165+
$this->runLoader($manager);
166+
167+
$this->assertSame(['a-post', 'a-post-2'], $lookups);
168+
}
169+
170+
/**
171+
* @throws Exception
172+
*/
173+
public function testLoadUpdatesEveryChangedFieldOnExistingPost(): void
174+
{
175+
$this->writeArticles([
176+
$this->article('A post', [
177+
'post_status' => 'archived',
178+
'post_date' => '0000-00-00 00:00:00',
179+
'excerpt' => 'New excerpt',
180+
'tl_dr' => 'New TL;DR',
181+
'isObsolete' => true,
182+
'isTwig' => true,
183+
'opengraph_img' => 'og.png',
184+
]),
185+
]);
186+
187+
$existing = $this->existingPost('a-post', 'Old title');
188+
$existing->setCategory(new Category());
189+
$existing->setAuthor(new Author());
190+
191+
$lookups = [];
192+
$manager = $this->createEntityManager($this->createRepository(['a-post' => $existing], $lookups));
193+
$manager->expects($this->never())->method('persist');
194+
195+
$this->expectOutputString("UPDATE: A post\n");
196+
$this->runLoader($manager);
197+
198+
$this->assertSame('A post', $existing->getTitle());
199+
$this->assertSame(PostStatusEnum::Archived, $existing->getStatus());
200+
$this->assertNotSame('2022-07-18 09:12:59', $existing->getPostDate()->format('Y-m-d H:i:s'));
201+
$this->assertSame($this->category, $existing->getCategory());
202+
$this->assertSame($this->author, $existing->getAuthor());
203+
$this->assertSame('New excerpt', $existing->getExcerpt());
204+
$this->assertSame('New TL;DR', $existing->getTlDr());
205+
$this->assertTrue($existing->isObsolete());
206+
$this->assertTrue($existing->isTwig());
207+
$this->assertSame('og.png', $existing->getOpenGraphImage());
208+
}
209+
210+
/**
211+
* @throws Exception
212+
*/
213+
public function testLoadMapsPostStatuses(): void
214+
{
215+
$this->writeArticles([
216+
$this->article('Private post', ['post_status' => 'private']),
217+
$this->article('Draft post', ['post_status' => 'unknown']),
218+
]);
219+
220+
$lookups = [];
221+
$manager = $this->createEntityManager($this->createRepository([], $lookups));
222+
223+
$statuses = [];
224+
$manager->expects($this->exactly(2))->method('persist')
225+
->willReturnCallback(function (Post $post) use (&$statuses): void {
226+
$statuses[] = $post->getStatus();
227+
});
228+
229+
$this->expectOutputString("CREATE: Private post\nCREATE: Draft post\n");
230+
$this->runLoader($manager);
231+
232+
$this->assertSame([PostStatusEnum::Private, PostStatusEnum::Draft], $statuses);
233+
}
234+
235+
/**
236+
* @throws Exception
237+
*/
238+
public function testLoadSkipsArticleWithUnknownAuthor(): void
239+
{
240+
$this->writeArticles([
241+
$this->article('A post', ['author' => ['display_name' => 'nobody']]),
242+
]);
243+
244+
$lookups = [];
245+
$manager = $this->createEntityManager($this->createRepository([], $lookups));
246+
$manager->expects($this->never())->method('persist');
247+
248+
$this->expectOutputString("SKIP (no author): A post\n");
249+
$this->runLoader($manager);
250+
251+
$this->assertSame([], $lookups);
252+
}
253+
254+
/**
255+
* @throws Exception
256+
*/
257+
public function testLoadThrowsWhenJsonFileCannotBeRead(): void
258+
{
259+
$manager = $this->createStub(EntityManagerInterface::class);
260+
261+
$this->expectException(RuntimeException::class);
262+
$this->expectExceptionMessage('Unable to read file');
263+
264+
@$this->runLoader($manager);
265+
}
266+
267+
public function testDependenciesAndOrder(): void
268+
{
269+
$loader = new PostLoader($this->jsonFile);
270+
271+
$this->assertSame([AuthorLoader::class, CategoryLoader::class], $loader->getDependencies());
272+
$this->assertSame(3, $loader->getOrder());
273+
}
274+
275+
/**
276+
* @param array<string, mixed> $extra
277+
* @return array<string, mixed>
278+
*/
279+
private function article(string $title, array $extra = []): array
280+
{
281+
return $extra + [
282+
'post_title' => $title,
283+
'post_date' => '2022-07-18 09:12:59',
284+
'post_status' => 'published',
285+
'author' => ['display_name' => 'arhimede'],
286+
'excerpt' => 'Excerpt',
287+
'tl_dr' => 'TL;DR',
288+
'tags' => [],
289+
];
290+
}
291+
292+
private function existingPost(string $slug, string $title): Post
293+
{
294+
$post = new Post();
295+
$post->setSlug($slug);
296+
$post->setTitle($title);
297+
$post->setPostDate(new DateTimeImmutable('2022-07-18 09:12:59'));
298+
$post->setStatus(PostStatusEnum::Published);
299+
$post->setCategory($this->category);
300+
$post->setAuthor($this->author);
301+
$post->setExcerpt('Excerpt');
302+
$post->setTldr('TL;DR');
303+
$post->setObsolete(false);
304+
$post->setTwig(false);
305+
$post->setOpenGraphImage(null);
306+
307+
return $post;
308+
}
309+
310+
/**
311+
* @param list<array<string, mixed>> $articles
312+
*/
313+
private function writeArticles(array $articles): void
314+
{
315+
file_put_contents($this->jsonFile, json_encode([
316+
['slug' => 'category', 'articles' => $articles],
317+
]));
318+
}
319+
320+
/**
321+
* @param array<string, Post> $postsBySlug
322+
* @param list<string> $lookups
323+
* @return EntityRepository<Post>
324+
* @throws Exception
325+
*/
326+
private function createRepository(array $postsBySlug, array &$lookups): EntityRepository
327+
{
328+
$repository = $this->createStub(EntityRepository::class);
329+
$repository->method('findOneBy')->willReturnCallback(
330+
function (array $criteria) use ($postsBySlug, &$lookups): ?Post {
331+
$lookups[] = $criteria['slug'];
332+
return $postsBySlug[$criteria['slug']] ?? null;
333+
}
334+
);
335+
336+
return $repository;
337+
}
338+
339+
private function runLoader(EntityManagerInterface $manager): void
340+
{
341+
$referenceRepository = new ReferenceRepository($manager);
342+
$referenceRepository->addReference('category_category', $this->category);
343+
$referenceRepository->addReference('author_arhimede', $this->author);
344+
345+
$loader = new PostLoader($this->jsonFile);
346+
$loader->setReferenceRepository($referenceRepository);
347+
$loader->load($manager);
348+
}
349+
350+
/**
351+
* @param EntityRepository<Post> $repository
352+
* @throws Exception
353+
*/
354+
private function createEntityManager(EntityRepository $repository): EntityManagerInterface&MockObject
355+
{
356+
$manager = $this->createMock(EntityManagerInterface::class);
357+
$manager->method('getRepository')->willReturn($repository);
358+
$manager->method('contains')->willReturn(true);
359+
360+
$unitOfWork = $this->createStub(UnitOfWork::class);
361+
$unitOfWork->method('isInIdentityMap')->willReturn(false);
362+
$manager->method('getUnitOfWork')->willReturn($unitOfWork);
363+
364+
$manager->method('getClassMetadata')->willReturnCallback(function (string $class) {
365+
$metadata = $this->createStub(ClassMetadata::class);
366+
$metadata->method('getName')->willReturn($class);
367+
return $metadata;
368+
});
369+
370+
return $manager;
371+
}
372+
}

0 commit comments

Comments
 (0)