From 0f3820e7bfa988091a28874bcb2cb598402d1966 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 27 Aug 2026 17:11:07 +0930 Subject: [PATCH] test: add notification, search, compose, report, and collection tests (332 total) Api/NotificationTest: notification isolation, correct user filtering Api/SearchTest: v2 search auth, structure, account lookup Api/CollectionTest: self/user collections, auth requirement Compose/ComposeTest: page access, settings, media validation, autocomplete Account/ReportTest: report creation, type validation, auth requirement --- tests/Feature/Account/ReportTest.php | 68 +++++++++++++++++ tests/Feature/Api/CollectionTest.php | 49 ++++++++++++ tests/Feature/Api/NotificationTest.php | 74 ++++++++++++++++++ tests/Feature/Api/SearchTest.php | 54 +++++++++++++ tests/Feature/Compose/ComposeTest.php | 102 +++++++++++++++++++++++++ 5 files changed, 347 insertions(+) create mode 100644 tests/Feature/Account/ReportTest.php create mode 100644 tests/Feature/Api/CollectionTest.php create mode 100644 tests/Feature/Api/NotificationTest.php create mode 100644 tests/Feature/Api/SearchTest.php create mode 100644 tests/Feature/Compose/ComposeTest.php diff --git a/tests/Feature/Account/ReportTest.php b/tests/Feature/Account/ReportTest.php new file mode 100644 index 000000000..24f8af740 --- /dev/null +++ b/tests/Feature/Account/ReportTest.php @@ -0,0 +1,68 @@ +create(); + $user->refresh(); + $other = User::factory()->create(); + $other->refresh(); + $status = Status::factory()->create([ + 'profile_id' => $other->profile_id, + 'type' => 'photo', + ]); + + $this->actingAs($user) + ->postJson('/i/report', [ + 'report' => 'spam', + 'type' => 'post', + 'id' => $status->id, + 'msg' => 'This is spam content', + ]) + ->assertOk(); + + expect(Report::where('profile_id', $user->profile_id)->exists())->toBeTrue(); + }); + + it('rejects report with invalid type', function () { + $user = User::factory()->create(); + $user->refresh(); + + $this->actingAs($user) + ->postJson('/i/report', [ + 'report' => 'invalid_type', + 'type' => 'post', + 'id' => 1, + ]) + ->assertStatus(400); + }); + + it('validates required fields', function () { + $user = User::factory()->create(); + $user->refresh(); + + $this->actingAs($user) + ->postJson('/i/report', []) + ->assertUnprocessable(); + }); + + it('requires authentication', function () { + $this->postJson('/i/report', [ + 'report' => 'spam', + 'type' => 'post', + 'id' => 1, + ])->assertUnauthorized(); + }); +}); diff --git a/tests/Feature/Api/CollectionTest.php b/tests/Feature/Api/CollectionTest.php new file mode 100644 index 000000000..6820bee77 --- /dev/null +++ b/tests/Feature/Api/CollectionTest.php @@ -0,0 +1,49 @@ +create(); + $user->refresh(); + Passport::actingAs($user, ['read']); + + $this->getJson('/api/pixelfed/v1/collections/self') + ->assertOk() + ->assertJsonIsArray(); + }); + + it('lists user collections by account id', function () { + $user = User::factory()->create(); + $user->refresh(); + Passport::actingAs($user, ['read']); + + $this->getJson("/api/pixelfed/v1/collections/accounts/{$user->profile_id}") + ->assertOk() + ->assertJsonIsArray(); + }); + + it('returns 404 for non-existent collection', function () { + $user = User::factory()->create(); + $user->refresh(); + Passport::actingAs($user, ['read']); + + $this->getJson('/api/pixelfed/v1/collections/view/999999') + ->assertNotFound(); + }); + + it('requires authentication for self collections', function () { + $this->getJson('/api/pixelfed/v1/collections/self') + ->assertUnauthorized(); + }); +}); diff --git a/tests/Feature/Api/NotificationTest.php b/tests/Feature/Api/NotificationTest.php new file mode 100644 index 000000000..c23e1da47 --- /dev/null +++ b/tests/Feature/Api/NotificationTest.php @@ -0,0 +1,74 @@ +create(); + $user->refresh(); + Passport::actingAs($user, ['read']); + + $this->getJson('/api/v1/notifications') + ->assertOk() + ->assertJson([]); + }); + + it('returns notifications for authenticated user', function () { + $user = User::factory()->create(); + $user->refresh(); + $other = User::factory()->create(); + $other->refresh(); + $status = Status::factory()->create([ + 'profile_id' => $user->profile_id, + 'type' => 'photo', + ]); + + Notification::create([ + 'profile_id' => $user->profile_id, + 'actor_id' => $other->profile_id, + 'action' => 'like', + 'item_id' => $status->id, + 'item_type' => Status::class, + ]); + + Passport::actingAs($user, ['read']); + + $this->getJson('/api/v1/notifications') + ->assertOk() + ->assertJsonIsArray(); + }); + + it('does not return other users notifications', function () { + $user = User::factory()->create(); + $user->refresh(); + $other = User::factory()->create(); + $other->refresh(); + + Notification::create([ + 'profile_id' => $other->profile_id, + 'actor_id' => $user->profile_id, + 'action' => 'follow', + 'item_id' => $other->profile_id, + 'item_type' => \App\Profile::class, + ]); + + Passport::actingAs($user, ['read']); + + $this->getJson('/api/v1/notifications') + ->assertOk() + ->assertJson([]); + }); +}); diff --git a/tests/Feature/Api/SearchTest.php b/tests/Feature/Api/SearchTest.php new file mode 100644 index 000000000..b0d0ffe9e --- /dev/null +++ b/tests/Feature/Api/SearchTest.php @@ -0,0 +1,54 @@ +getJson('/api/v2/search?q=test') + ->assertUnauthorized(); + }); + + it('returns results structure for a query', function () { + $user = User::factory()->create(); + $user->refresh(); + Passport::actingAs($user, ['read']); + + $this->getJson('/api/v2/search?q=test') + ->assertOk() + ->assertJsonStructure(['accounts', 'statuses', 'hashtags']); + }); + + it('rejects empty query', function () { + $user = User::factory()->create(); + $user->refresh(); + Passport::actingAs($user, ['read']); + + $this->getJson('/api/v2/search?q=') + ->assertStatus(422); + }); + + it('finds local accounts by username', function () { + $target = User::factory()->create(['username' => 'searchable']); + $target->refresh(); + $user = User::factory()->create(); + $user->refresh(); + Passport::actingAs($user, ['read']); + + $response = $this->getJson('/api/v2/search?q=searchable&type=accounts'); + $response->assertOk(); + + $accounts = $response->json('accounts'); + $usernames = collect($accounts)->pluck('username')->toArray(); + expect($usernames)->toContain('searchable'); + }); +}); diff --git a/tests/Feature/Compose/ComposeTest.php b/tests/Feature/Compose/ComposeTest.php new file mode 100644 index 000000000..8c1a224a4 --- /dev/null +++ b/tests/Feature/Compose/ComposeTest.php @@ -0,0 +1,102 @@ +create(); + $user->refresh(); + + $this->actingAs($user) + ->get('/i/compose') + ->assertOk(); + }); + + it('requires authentication', function () { + $this->get('/i/compose') + ->assertStatus(403); + }); +}); + +describe('compose settings', function () { + it('returns compose settings for authenticated user', function () { + $user = User::factory()->create(); + $user->refresh(); + + $this->actingAs($user) + ->getJson('/api/compose/v0/settings') + ->assertOk() + ->assertJsonStructure(['max_altext_length']); + }); +}); + +describe('media upload', function () { + it('rejects upload without file', function () { + $user = User::factory()->create(); + $user->refresh(); + + $this->actingAs($user) + ->postJson('/api/compose/v0/media/upload', []) + ->assertUnprocessable(); + }); + + it('rejects file exceeding max size', function () { + $user = User::factory()->create(); + $user->refresh(); + $maxSize = (int) config('pixelfed.max_photo_size', 15000); + + // Create a file larger than max + $file = UploadedFile::fake()->create('large.jpg', $maxSize + 1000, 'image/jpeg'); + + $this->actingAs($user) + ->postJson('/api/compose/v0/media/upload', [ + 'file' => $file, + ]) + ->assertUnprocessable(); + }); + + it('rejects unsupported file types', function () { + $user = User::factory()->create(); + $user->refresh(); + + $file = UploadedFile::fake()->create('document.pdf', 100, 'application/pdf'); + + $this->actingAs($user) + ->postJson('/api/compose/v0/media/upload', [ + 'file' => $file, + ]) + ->assertUnprocessable(); + }); +}); + +describe('hashtag autocomplete', function () { + it('returns results for hashtag search', function () { + $user = User::factory()->create(); + $user->refresh(); + + $this->actingAs($user) + ->getJson('/api/compose/v0/search/tag?q=pixel') + ->assertOk() + ->assertJsonIsArray(); + }); + + it('rejects empty query', function () { + $user = User::factory()->create(); + $user->refresh(); + + $this->actingAs($user) + ->getJson('/api/compose/v0/search/tag?q=') + ->assertStatus(422); + }); +});