From 27768cd69cfe2a28de89abf88e3c122305ff25e6 Mon Sep 17 00:00:00 2001 From: Shlee Date: Sat, 29 Aug 2026 22:53:23 +0930 Subject: [PATCH] Create HashtagCollationTest.php --- tests/Feature/HashtagCollationTest.php | 108 +++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/Feature/HashtagCollationTest.php diff --git a/tests/Feature/HashtagCollationTest.php b/tests/Feature/HashtagCollationTest.php new file mode 100644 index 000000000..bde6b4af4 --- /dev/null +++ b/tests/Feature/HashtagCollationTest.php @@ -0,0 +1,108 @@ += 0x10000). +| +*/ + +it('migration runs without error regardless of database driver', function () { + // The test suite uses sqlite by default. The migration detects the driver + // and no-ops gracefully rather than attempting unsupported ALTER syntax. + Artisan::call('migrate', [ + '--path' => 'database/migrations/2025_07_31_164635_change_hashtags_collation.php', + '--force' => true, + ]); + + // If we reach here without exception the no-op path worked. + expect(true)->toBeTrue(); +}); + +it('migration rollback runs without error regardless of database driver', function () { + Artisan::call('migrate', [ + '--path' => 'database/migrations/2025_07_31_164635_change_hashtags_collation.php', + '--force' => true, + ]); + + Artisan::call('migrate:rollback', [ + '--path' => 'database/migrations/2025_07_31_164635_change_hashtags_collation.php', + '--force' => true, + ]); + + expect(true)->toBeTrue(); +}); + +it('distinct BMP-outside hashtags do not collide on the unique index', function () { + // This test is only meaningful on MySQL/MariaDB where the collation fix matters. + if (! in_array(DB::connection()->getDriverName(), ['mysql', 'mariadb'], true)) { + $this->markTestSkipped('Collation behavior is MySQL/MariaDB-specific.'); + } + + // Ensure the migration has been applied. + Artisan::call('migrate', [ + '--path' => 'database/migrations/2025_07_31_164635_change_hashtags_collation.php', + '--force' => true, + ]); + + // Two distinct 5-character hashtags using characters outside the BMP. + // Under the old utf8mb4_unicode_ci collation these were considered equal. + $shavian = '𐑖𐑱𐑝𐑾𐑯'; // Shavian script + $cuneiform = 'π’†³π’†π’€­π’Šπ’† '; // Cuneiform script + + $tag1 = Hashtag::create(['name' => $shavian, 'slug' => $shavian]); + $tag2 = Hashtag::create(['name' => $cuneiform, 'slug' => $cuneiform]); + + // Both must coexist as separate rows with distinct IDs. + expect($tag1->id)->not->toBe($tag2->id); + expect(Hashtag::where('slug', $shavian)->first()->id)->toBe($tag1->id); + expect(Hashtag::where('slug', $cuneiform)->first()->id)->toBe($tag2->id); +}); + +it('same-script hashtags with the same slug still correctly deduplicate', function () { + // Sanity check: two identical hashtags should NOT create duplicates. + if (! in_array(DB::connection()->getDriverName(), ['mysql', 'mariadb'], true)) { + $this->markTestSkipped('Collation behavior is MySQL/MariaDB-specific.'); + } + + Artisan::call('migrate', [ + '--path' => 'database/migrations/2025_07_31_164635_change_hashtags_collation.php', + '--force' => true, + ]); + + $tag = Hashtag::firstOrCreate(['slug' => 'hello'], ['name' => 'hello']); + $same = Hashtag::firstOrCreate(['slug' => 'hello'], ['name' => 'hello']); + + expect($tag->id)->toBe($same->id); + expect(Hashtag::where('slug', 'hello')->count())->toBe(1); +}); + +it('case-insensitivity is preserved after collation change', function () { + // utf8mb4_unicode_520_ci is still case-insensitive, so #Hello == #hello. + if (! in_array(DB::connection()->getDriverName(), ['mysql', 'mariadb'], true)) { + $this->markTestSkipped('Collation behavior is MySQL/MariaDB-specific.'); + } + + Artisan::call('migrate', [ + '--path' => 'database/migrations/2025_07_31_164635_change_hashtags_collation.php', + '--force' => true, + ]); + + Hashtag::create(['name' => 'Pixelfed', 'slug' => 'pixelfed']); + + // Case-insensitive lookup should find it with different casing. + $found = Hashtag::where('slug', 'PIXELFED')->first(); + expect($found)->not->toBeNull(); + expect($found->slug)->toBe('pixelfed'); +});