From 39819a85cbb234840595e87c69dea69758a10cf1 Mon Sep 17 00:00:00 2001 From: test Date: Sun, 20 Sep 2026 14:50:59 +0200 Subject: [PATCH] Expand profile gender options and cover country plus activity persistence. Allow the broader gender enum and add tests for country saves and discovery event recording. --- ...20000_expand_user_profiles_gender_enum.php | 47 +++++++++++++++++++ .../ProfileCountryPersistenceTest.php | 19 ++++++++ .../Discovery/ActivityEventRecordingTest.php | 46 ++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 database/migrations/2026_09_17_120000_expand_user_profiles_gender_enum.php diff --git a/database/migrations/2026_09_17_120000_expand_user_profiles_gender_enum.php b/database/migrations/2026_09_17_120000_expand_user_profiles_gender_enum.php new file mode 100644 index 00000000..b781ba13 --- /dev/null +++ b/database/migrations/2026_09_17_120000_expand_user_profiles_gender_enum.php @@ -0,0 +1,47 @@ +getDriverName(); + + if ($driver !== 'mysql') { + return; + } + + // Production/local imports may still be enum('M','F') while the app stores X + // for "prefer not to say" / non-binary. Expand safely without rewriting rows. + DB::statement("ALTER TABLE `user_profiles` MODIFY `gender` ENUM('M','F','X') NULL DEFAULT NULL"); + } + + public function down(): void + { + if (! Schema::hasTable('user_profiles') || ! Schema::hasColumn('user_profiles', 'gender')) { + return; + } + + $driver = Schema::getConnection()->getDriverName(); + + if ($driver !== 'mysql') { + return; + } + + // Map unsupported values before shrinking the enum. + DB::table('user_profiles') + ->where('gender', 'X') + ->update(['gender' => null]); + + DB::statement("ALTER TABLE `user_profiles` MODIFY `gender` ENUM('M','F') NULL DEFAULT NULL"); + } +}; diff --git a/tests/Feature/Countries/ProfileCountryPersistenceTest.php b/tests/Feature/Countries/ProfileCountryPersistenceTest.php index b2f2dabf..11f69a17 100644 --- a/tests/Feature/Countries/ProfileCountryPersistenceTest.php +++ b/tests/Feature/Countries/ProfileCountryPersistenceTest.php @@ -30,6 +30,25 @@ it('stores a selected country on the personal settings endpoint', function (): v expect($user->country_id)->toBe($country->id); expect(optional($user->profile)->country_code)->toBe('SI'); + expect(optional($user->profile)->gender)->toBe('M'); +}); + +it('stores prefer-not-to-say gender as X on the personal settings endpoint', function (): void { + $this->withoutMiddleware(ForumBotProtectionMiddleware::class); + + $user = User::factory()->create(); + + $response = $this->actingAs($user)->postJson('/settings/personal/update', [ + 'birthday' => '1991-05-10', + 'gender' => 'x', + ]); + + $response->assertOk(); + + $user->refresh(); + + expect(optional($user->profile)->gender)->toBe('X'); + expect(optional($user->profile)->birthdate?->toDateString())->toBe('1991-05-10'); }); it('rejects invalid country identifiers on the personal settings endpoint', function (): void { diff --git a/tests/Feature/Discovery/ActivityEventRecordingTest.php b/tests/Feature/Discovery/ActivityEventRecordingTest.php index c8e500e3..4ed5cea3 100644 --- a/tests/Feature/Discovery/ActivityEventRecordingTest.php +++ b/tests/Feature/Discovery/ActivityEventRecordingTest.php @@ -136,3 +136,49 @@ it('following tab shows only events from followed users', function () { expect($events)->toHaveCount(1); expect(data_get($events->first(), 'user.id'))->toBe($creator->id); }); + +it('comments and replies feeds do not query unrelated activity sources', function () { + foreach (['comments', 'replies'] as $filter) { + DB::flushQueryLog(); + DB::enableQueryLog(); + + $this->getJson('/api/activity?filter=' . $filter) + ->assertOk() + ->assertJsonPath('filter', $filter); + + $sql = collect(DB::getQueryLog()) + ->pluck('query') + ->map(fn (string $query): string => strtolower($query)) + ->implode("\n"); + + expect($sql) + ->not->toContain('activity_events') + ->not->toContain('comment_reactions') + ->not->toContain('user_mentions'); + } +}); + +it('my activity feed keeps only the authenticated actor activity', function () { + $user = User::factory()->create(); + $other = User::factory()->create(); + $artwork = Artwork::factory()->create(); + + ActivityEvent::record($user->id, ActivityEvent::TYPE_UPLOAD, ActivityEvent::TARGET_ARTWORK, $artwork->id); + ActivityEvent::record($other->id, ActivityEvent::TYPE_UPLOAD, ActivityEvent::TARGET_ARTWORK, $artwork->id); + + $response = $this->actingAs($user)->getJson('/api/activity?filter=my'); + + $response->assertOk(); + expect(collect($response->json('data'))) + ->toHaveCount(1) + ->and(data_get($response->json('data.0'), 'user.id'))->toBe($user->id); +}); + +it('guest activity feed is capped to the latest page', function () { + $response = $this->getJson('/api/activity?page=2&per_page=50'); + + $response->assertOk(); + expect($response->json('meta.current_page'))->toBe(1) + ->and($response->json('meta.per_page'))->toBe(20) + ->and(count($response->json('data')))->toBeLessThanOrEqual(20); +});