1616use Light \Blog \Entity \Author ;
1717use Light \Blog \Entity \Category ;
1818use Light \Blog \Entity \Post ;
19- use Light \Blog \Enum \PostStatusEnum ;
2019use LightTest \Unit \UnitTest ;
2120use PHPUnit \Framework \MockObject \Exception ;
2221use PHPUnit \Framework \MockObject \MockObject ;
23- use RuntimeException ;
2422
25- use function bin2hex ;
23+ use function array_count_values ;
2624use 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-
25+ use function file_get_contents ;
26+ use function json_decode ;
27+ use function preg_quote ;
28+ use function preg_replace ;
29+ use function strtolower ;
30+ use function trim ;
31+
32+ /**
33+ * Runs PostLoader against the real articles_cleaned.json, using the
34+ * "Autologin using Cookie / Remember Me in Dotkernel" article that sets post_slug.
35+ */
4036class PostLoaderTest extends UnitTest
4137{
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- }
38+ private const string TITLE = 'Autologin using Cookie / Remember Me in Dotkernel ' ;
39+ private const string CUSTOM_SLUG = 'autologin-cookie-remember-me-feature ' ;
40+ private const string TITLE_SLUG = 'autologin-using-cookie-remember-me-in-dotkernel ' ;
7741
7842 /**
7943 * @throws Exception
8044 */
8145 public function testLoadCreatesPostWithCustomSlugWhenNothingMatchesInDb (): void
8246 {
83- $ this ->writeArticles ([
84- $ this ->article ('A post ' , ['post_slug ' => ' custom-slug ' ]),
85- ]);
86-
8747 $ lookups = [];
8848 $ manager = $ this ->createEntityManager ($ this ->createRepository ([], $ lookups ));
8949
90- $ persisted = null ;
91- $ manager ->expects ($ this ->once ())->method ('persist ' )
92- ->willReturnCallback (function (Post $ post ) use (&$ persisted ): void {
93- $ persisted = $ post ;
50+ $ persistedSlugs = [] ;
51+ $ manager ->expects ($ this ->atLeastOnce ())->method ('persist ' )
52+ ->willReturnCallback (function (Post $ post ) use (&$ persistedSlugs ): void {
53+ $ persistedSlugs [ $ post -> getTitle ()] = $ post-> getSlug () ;
9454 });
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 ' );
16355
164- $ this ->expectOutputString ( " CREATE: A post \n CREATE: A post \n" );
56+ $ this ->expectOutputRegex ( ' / CREATE: ' . preg_quote ( self :: TITLE , ' / ' ) . ' \n/ ' );
16557 $ this ->runLoader ($ manager );
16658
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 );
59+ $ this ->assertSame (self ::CUSTOM_SLUG , $ persistedSlugs [self ::TITLE ]);
60+ $ this ->assertNotContains (self ::TITLE_SLUG , $ persistedSlugs );
19761
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 ());
62+ // Not found by post_slug, so the title-based slug is tried before creating.
63+ $ counts = array_count_values ($ lookups );
64+ $ this ->assertSame (1 , $ counts [self ::CUSTOM_SLUG ]);
65+ $ this ->assertSame (1 , $ counts [self ::TITLE_SLUG ]);
20866 }
20967
21068 /**
21169 * @throws Exception
21270 */
213- public function testLoadMapsPostStatuses (): void
71+ public function testLoadUpdatesPostFoundByCustomSlug (): void
21472 {
215- $ this ->writeArticles ([
216- $ this ->article ('Private post ' , ['post_status ' => 'private ' ]),
217- $ this ->article ('Draft post ' , ['post_status ' => 'unknown ' ]),
218- ]);
73+ $ existing = $ this ->existingPost (self ::CUSTOM_SLUG , 'Old title ' );
21974
22075 $ 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- });
76+ $ manager = $ this ->createEntityManager ($ this ->createRepository ([self ::CUSTOM_SLUG => $ existing ], $ lookups ));
77+ $ manager ->expects ($ this ->atLeastOnce ())->method ('flush ' );
22878
229- $ this ->expectOutputString ( " CREATE: Private post \n CREATE: Draft post \n" );
79+ $ this ->expectOutputRegex ( ' /UPDATE: ' . preg_quote ( self :: TITLE , ' / ' ) . ' \n/ ' );
23080 $ this ->runLoader ($ manager );
23181
232- $ this ->assertSame ([PostStatusEnum::Private, PostStatusEnum::Draft], $ statuses );
82+ $ this ->assertSame (self ::CUSTOM_SLUG , $ existing ->getSlug ());
83+ $ this ->assertSame (self ::TITLE , $ existing ->getTitle ());
84+ $ this ->assertNotContains (self ::TITLE_SLUG , $ lookups );
23385 }
23486
23587 /**
23688 * @throws Exception
23789 */
238- public function testLoadSkipsArticleWithUnknownAuthor (): void
90+ public function testLoadRenamesPostImportedUnderTitleSlugToCustomSlug (): void
23991 {
240- $ this ->writeArticles ([
241- $ this ->article ('A post ' , ['author ' => ['display_name ' => 'nobody ' ]]),
242- ]);
92+ // Imported before post_slug was added to the JSON.
93+ $ existing = $ this ->existingPost (self ::TITLE_SLUG , self ::TITLE );
24394
24495 $ lookups = [];
245- $ manager = $ this ->createEntityManager ($ this ->createRepository ([], $ lookups ));
246- $ manager ->expects ($ this ->never ())->method ('persist ' );
96+ $ manager = $ this ->createEntityManager ($ this ->createRepository ([self :: TITLE_SLUG => $ existing ], $ lookups ));
97+ $ manager ->expects ($ this ->atLeastOnce ())->method ('flush ' );
24798
248- $ this ->expectOutputString ( " SKIP (no author): A post \n" );
99+ $ this ->expectOutputRegex ( ' /UPDATE: ' . preg_quote ( self :: TITLE , ' / ' ) . ' \n/ ' );
249100 $ this ->runLoader ($ manager );
250101
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 );
102+ $ this ->assertSame (self ::CUSTOM_SLUG , $ existing ->getSlug ());
265103 }
266104
267105 public function testDependenciesAndOrder (): void
268106 {
269- $ loader = new PostLoader ($ this -> jsonFile );
107+ $ loader = new PostLoader ();
270108
271109 $ this ->assertSame ([AuthorLoader::class, CategoryLoader::class], $ loader ->getDependencies ());
272110 $ this ->assertSame (3 , $ loader ->getOrder ());
273111 }
274112
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-
292113 private function existingPost (string $ slug , string $ title ): Post
293114 {
294115 $ post = new Post ();
295116 $ post ->setSlug ($ slug );
296117 $ 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 );
118+ $ post ->setPostDate (new DateTimeImmutable ());
119+ $ post ->setCategory (new Category ());
120+ $ post ->setAuthor (new Author ());
121+ $ post ->setExcerpt ('' );
306122
307123 return $ post ;
308124 }
309125
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-
320126 /**
321127 * @param array<string, Post> $postsBySlug
322128 * @param list<string> $lookups
@@ -336,17 +142,41 @@ function (array $criteria) use ($postsBySlug, &$lookups): ?Post {
336142 return $ repository ;
337143 }
338144
145+ /**
146+ * Registers the category and author references the real articles_cleaned.json needs.
147+ */
339148 private function runLoader (EntityManagerInterface $ manager ): void
340149 {
150+ $ contents = file_get_contents (dirname (__DIR__ , 4 ) . '/src/App/src/Fixture/articles_cleaned.json ' );
151+ $ this ->assertIsString ($ contents );
152+
153+ /** @var list<array{slug: string, articles: list<array{author?: array{display_name?: string}}>}> $categories */
154+ $ categories = json_decode ($ contents , true );
155+
341156 $ referenceRepository = new ReferenceRepository ($ manager );
342- $ referenceRepository ->addReference ('category_category ' , $ this ->category );
343- $ referenceRepository ->addReference ('author_arhimede ' , $ this ->author );
157+ foreach ($ categories as $ category ) {
158+ $ referenceRepository ->setReference ('category_ ' . $ category ['slug ' ], new Category ());
159+
160+ foreach ($ category ['articles ' ] as $ article ) {
161+ $ authorName = $ article ['author ' ]['display_name ' ] ?? null ;
162+ if ($ authorName !== null ) {
163+ $ referenceRepository ->setReference ('author_ ' . $ this ->slugify ($ authorName ), new Author ());
164+ }
165+ }
166+ }
344167
345- $ loader = new PostLoader ($ this -> jsonFile );
168+ $ loader = new PostLoader ();
346169 $ loader ->setReferenceRepository ($ referenceRepository );
347170 $ loader ->load ($ manager );
348171 }
349172
173+ private function slugify (string $ text ): string
174+ {
175+ $ text = strtolower (trim ($ text ));
176+ $ text = preg_replace ('/[^a-z0-9]+/ ' , '- ' , $ text ) ?? '' ;
177+ return trim ($ text , '- ' );
178+ }
179+
350180 /**
351181 * @param EntityRepository<Post> $repository
352182 * @throws Exception
0 commit comments