From 888fa1bc301313737706fd4a2cb00f53ddade1e5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 12 Sep 2026 00:25:10 +0930 Subject: [PATCH] Fix tests --- tests/Feature/ComposeControllerTest.php | 107 ++++----- tests/Feature/LoginTest.php | 18 +- tests/Feature/RemoteAuthServiceTest.php | 123 +++++----- tests/Feature/RemoteOidcTest.php | 265 ++++++++++------------ tests/Feature/SeasonalAggregationTest.php | 121 +++++----- tests/Feature/StatusDeleteCleanupTest.php | 207 ++++++++--------- tests/Unit/ExampleTest.php | 16 +- 7 files changed, 394 insertions(+), 463 deletions(-) diff --git a/tests/Feature/ComposeControllerTest.php b/tests/Feature/ComposeControllerTest.php index 6d297878a..a6e2c8ccc 100644 --- a/tests/Feature/ComposeControllerTest.php +++ b/tests/Feature/ComposeControllerTest.php @@ -1,74 +1,65 @@ create(); +it('can filter location search results by country', function () { + $user = User::factory()->create(); - $profile = ProfileFactory::new()->create([ - 'user_id' => $user->id, - ]); + $profile = ProfileFactory::new()->create([ + 'user_id' => $user->id, + ]); - $user->update([ - 'profile_id' => $profile->id, - ]); + $user->update([ + 'profile_id' => $profile->id, + ]); - $user->refresh(); + $user->refresh(); - DB::table('places')->insert([ - [ - 'id' => 1, - 'slug' => 'san-francisco-usa', - 'name' => 'San Francisco', - 'state' => 'California', - 'country' => 'USA', - 'aliases' => null, - 'lat' => 37.7749, - 'long' => -122.4194, - 'score' => 100, - 'created_at' => now(), - 'updated_at' => now(), - ], - [ - 'id' => 2, - 'slug' => 'san-francisco-philippines', - 'name' => 'San Francisco', - 'state' => 'Cebu', - 'country' => 'Philippines', - 'aliases' => null, - 'lat' => 10.3, - 'long' => 123.9, - 'score' => 50, - 'created_at' => now(), - 'updated_at' => now(), - ], - ]); + DB::table('places')->insert([ + [ + 'id' => 1, + 'slug' => 'san-francisco-usa', + 'name' => 'San Francisco', + 'state' => 'California', + 'country' => 'USA', + 'aliases' => null, + 'lat' => 37.7749, + 'long' => -122.4194, + 'score' => 100, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'id' => 2, + 'slug' => 'san-francisco-philippines', + 'name' => 'San Francisco', + 'state' => 'Cebu', + 'country' => 'Philippines', + 'aliases' => null, + 'lat' => 10.3, + 'long' => 123.9, + 'score' => 50, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); - $response = $this->actingAs($user, 'api') - ->get('/api/v1.1/compose/search/location?q=San%20Francisco%2C%20USA'); + $response = $this->actingAs($user, 'api') + ->get('/api/v1.1/compose/search/location?q=San%20Francisco%2C%20USA'); - $response->assertJsonCount(1); + $response->assertJsonCount(1); - $response->assertJsonFragment([ - 'name' => 'San Francisco', - 'country' => 'USA', - ]); + $response->assertJsonFragment([ + 'name' => 'San Francisco', + 'country' => 'USA', + ]); - $response->assertJsonMissing([ - 'country' => 'Philippines', - ]); - } -} + $response->assertJsonMissing([ + 'country' => 'Philippines', + ]); +}); diff --git a/tests/Feature/LoginTest.php b/tests/Feature/LoginTest.php index d7364ead8..0c6e02afa 100644 --- a/tests/Feature/LoginTest.php +++ b/tests/Feature/LoginTest.php @@ -1,17 +1,7 @@ get('login'); -use PHPUnit\Framework\Attributes\Test; -use Tests\TestCase; - -class LoginTest extends TestCase -{ - #[Test] - public function view_login_page() - { - $response = $this->get('login'); - - $response->assertSee('Forgot Password'); - } -} + $response->assertSee('Forgot Password'); +}); diff --git a/tests/Feature/RemoteAuthServiceTest.php b/tests/Feature/RemoteAuthServiceTest.php index 353e9cfc4..4b689a67a 100644 --- a/tests/Feature/RemoteAuthServiceTest.php +++ b/tests/Feature/RemoteAuthServiceTest.php @@ -1,99 +1,82 @@ $domain, - 'client_id' => 'cid', - 'client_secret' => 'secret', - 'redirect_uri' => url('/auth/mastodon/callback'), - 'active' => true, - 'banned' => false, - ]); - } +function activeRemoteAuthInstance(string $domain = 'mastodon.example'): RemoteAuthInstance +{ + return RemoteAuthInstance::create([ + 'domain' => $domain, + 'client_id' => 'cid', + 'client_secret' => 'secret', + 'redirect_uri' => url('/auth/mastodon/callback'), + 'active' => true, + 'banned' => false, + ]); +} - #[Test] - public function verify_credentials_returns_false_on_connection_failure() - { - $this->activeInstance(); +it('returns false on connection failure when verifying credentials', function () { + activeRemoteAuthInstance(); - Http::fake(function () { - throw new ConnectionException('timed out'); - }); + Http::fake(function () { + throw new ConnectionException('timed out'); + }); - $res = RemoteAuthService::getVerifyCredentials('mastodon.example', 'token'); + $res = RemoteAuthService::getVerifyCredentials('mastodon.example', 'token'); - $this->assertFalse($res); - } + expect($res)->toBeFalse(); +}); - #[Test] - public function verify_credentials_returns_false_on_server_error() - { - $this->activeInstance(); +it('returns false on server error when verifying credentials', function () { + activeRemoteAuthInstance(); - Http::fake([ - '*' => Http::response('nope', 500), - ]); + Http::fake([ + '*' => Http::response('nope', 500), + ]); - $res = RemoteAuthService::getVerifyCredentials('mastodon.example', 'token'); + $res = RemoteAuthService::getVerifyCredentials('mastodon.example', 'token'); - $this->assertFalse($res); - } + expect($res)->toBeFalse(); +}); - #[Test] - public function verify_credentials_returns_json_on_success() - { - $this->activeInstance(); +it('returns json on success when verifying credentials', function () { + activeRemoteAuthInstance(); - Http::fake([ - '*' => Http::response(['acct' => 'alice', 'id' => '1'], 200), - ]); + Http::fake([ + '*' => Http::response(['acct' => 'alice', 'id' => '1'], 200), + ]); - $res = RemoteAuthService::getVerifyCredentials('mastodon.example', 'token'); + $res = RemoteAuthService::getVerifyCredentials('mastodon.example', 'token'); - $this->assertIsArray($res); - $this->assertSame('alice', $res['acct']); - } + expect($res)->toBeArray(); + expect($res['acct'])->toBe('alice'); +}); - #[Test] - public function get_following_returns_false_on_connection_failure() - { - $this->activeInstance(); +it('returns false on connection failure when getting following', function () { + activeRemoteAuthInstance(); - Http::fake(function () { - throw new ConnectionException('timed out'); - }); + Http::fake(function () { + throw new ConnectionException('timed out'); + }); - $res = RemoteAuthService::getFollowing('mastodon.example', 'token', 42); + $res = RemoteAuthService::getFollowing('mastodon.example', 'token', 42); - $this->assertFalse($res); - } + expect($res)->toBeFalse(); +}); - #[Test] - public function get_token_returns_false_on_connection_failure() - { - $this->activeInstance(); +it('returns false on connection failure when getting a token', function () { + activeRemoteAuthInstance(); - Http::fake(function () { - throw new ConnectionException('timed out'); - }); + Http::fake(function () { + throw new ConnectionException('timed out'); + }); - $res = RemoteAuthService::getToken('mastodon.example', 'code'); + $res = RemoteAuthService::getToken('mastodon.example', 'code'); - $this->assertFalse($res); - } -} + expect($res)->toBeFalse(); +}); diff --git a/tests/Feature/RemoteOidcTest.php b/tests/Feature/RemoteOidcTest.php index b72d85fd0..3c3a151d5 100644 --- a/tests/Feature/RemoteOidcTest.php +++ b/tests/Feature/RemoteOidcTest.php @@ -1,7 +1,5 @@ true, - 'remote-auth.oidc.clientId' => 'fake', - 'remote-auth.oidc.clientSecret' => 'fakeSecret', - 'remote-auth.oidc.authorizeURL' => 'http://fakeserver.oidc/authorizeURL', - 'remote-auth.oidc.tokenURL' => 'http://fakeserver.oidc/tokenURL', - 'remote-auth.oidc.profileURL' => 'http://fakeserver.oidc/profile', - ]); - $response = $this->withoutExceptionHandling()->get('auth/oidc/start'); - - $state = session()->get('oauth2state'); - $callbackUrl = urlencode(url('auth/oidc/callback')); - - $response->assertRedirect("http://fakeserver.oidc/authorizeURL?scope=openid%20profile%20email&state={$state}&response_type=code&approval_prompt=auto&redirect_uri={$callbackUrl}&client_id=fake"); - } - // #[Test] - public function view_oidc_callback_new_user() - { - $originalUserCount = User::count(); - $this->assertDatabaseCount('users', $originalUserCount); +uses(LazilyRefreshDatabase::class); + +it('shows the oidc start redirect', function () { + config([ + 'remote-auth.oidc.enabled' => true, + 'remote-auth.oidc.clientId' => 'fake', + 'remote-auth.oidc.clientSecret' => 'fakeSecret', + 'remote-auth.oidc.authorizeURL' => 'http://fakeserver.oidc/authorizeURL', + 'remote-auth.oidc.tokenURL' => 'http://fakeserver.oidc/tokenURL', + 'remote-auth.oidc.profileURL' => 'http://fakeserver.oidc/profile', + ]); + $response = $this->withoutExceptionHandling()->get('auth/oidc/start'); + + $state = session()->get('oauth2state'); + $callbackUrl = urlencode(url('auth/oidc/callback')); + + $response->assertRedirect("http://fakeserver.oidc/authorizeURL?scope=openid%20profile%20email&state={$state}&response_type=code&approval_prompt=auto&redirect_uri={$callbackUrl}&client_id=fake"); +}); + +// it('creates a new user from the oidc callback', function () { +// $originalUserCount = User::count(); +// $this->assertDatabaseCount('users', $originalUserCount); +// +// config(['remote-auth.oidc.enabled' => true]); +// +// $oauthData = [ +// 'sub' => Str::random(10), +// 'preferred_username' => fake()->unique()->userName, +// 'email' => fake()->unique()->freeEmail, +// ]; +// +// $this->partialMock(UserOidcService::class, function (MockInterface $mock) use ($oauthData) { +// $mock->shouldReceive('getAccessToken')->once()->andReturn(new AccessToken(['access_token' => 'token'])); +// $mock->shouldReceive('getResourceOwner')->once()->andReturn(new GenericResourceOwner($oauthData, 'sub')); +// +// return $mock; +// }); +// +// $response = $this->withoutExceptionHandling()->withSession([ +// 'oauth2state' => 'abc123', +// ])->get('auth/oidc/callback?state=abc123&code=1'); +// +// $response->assertRedirect('/'); +// +// $mappedUser = UserOidcMapping::where('oidc_id', $oauthData['sub'])->first(); +// $this->assertNotNull($mappedUser, 'mapping is found'); +// $user = $mappedUser->user; +// $this->assertEquals($user->username, $oauthData['preferred_username']); +// $this->assertEquals($user->email, $oauthData['email']); +// $this->assertEquals(Auth::guard()->user()->id, $user->id); +// +// $this->assertDatabaseCount('users', $originalUserCount + 1); +// }); + +// it('maps the oidc callback to an existing user', function () { +// $user = User::create([ +// 'name' => fake()->name, +// 'username' => fake()->unique()->username, +// 'email' => fake()->unique()->freeEmail, +// ]); +// $originalUserCount = User::count(); +// $this->assertDatabaseCount('users', $originalUserCount); +// +// config(['remote-auth.oidc.enabled' => true]); +// +// $oauthData = [ +// 'sub' => Str::random(10), +// 'preferred_username' => $user->username, +// 'email' => $user->email, +// ]; +// +// UserOidcMapping::create([ +// 'oidc_id' => $oauthData['sub'], +// 'user_id' => $user->id, +// ]); +// +// $this->partialMock(UserOidcService::class, function (MockInterface $mock) use ($oauthData) { +// $mock->shouldReceive('getAccessToken')->once()->andReturn(new AccessToken(['access_token' => 'token'])); +// $mock->shouldReceive('getResourceOwner')->once()->andReturn(new GenericResourceOwner($oauthData, 'sub')); +// +// return $mock; +// }); +// +// $response = $this->withoutExceptionHandling()->withSession([ +// 'oauth2state' => 'abc123', +// ])->get('auth/oidc/callback?state=abc123&code=1'); +// +// $response->assertRedirect('/'); +// +// $mappedUser = UserOidcMapping::where('oidc_id', $oauthData['sub'])->first(); +// $this->assertNotNull($mappedUser, 'mapping is found'); +// $user = $mappedUser->user; +// $this->assertEquals($user->username, $oauthData['preferred_username']); +// $this->assertEquals($user->email, $oauthData['email']); +// $this->assertEquals(Auth::guard()->user()->id, $user->id); +// +// $this->assertDatabaseCount('users', $originalUserCount); +// }); + +it('ensures a valid username from the oidc callback', function () { + config(['remote-auth.oidc.enabled' => true]); + config(['remote-auth.oidc.field_username' => 'preferred_username']); + + $dataset = [ + 'john.doe@domain.com' => 'johndoe', + 'test+user@part1@domain.com' => 'testuser', + 'user!#$%^&*()_test' => 'user_test', + 'jean-luc.picard' => 'jeanlucpicard', + 'supercalifragilisticexpialidøcious@test.com' => 'supercalifragilisticexpialidci', + 'hélène_renåud' => 'hlne_renud', + '123456789' => '123456789', + ' user _ name ' => 'user_name', + 'foo+bar@sub.domain.co.uk' => 'foobar', + ]; + + foreach ($dataset as $input => $expected) { + Auth::logout(); + session()->flush(); - config(['remote-auth.oidc.enabled' => true]); + $originalUserCount = User::count(); $oauthData = [ 'sub' => Str::random(10), - 'preferred_username' => fake()->unique()->userName, - 'email' => fake()->unique()->freeEmail, - ]; - - $this->partialMock(UserOidcService::class, function (MockInterface $mock) use ($oauthData) { - $mock->shouldReceive('getAccessToken')->once()->andReturn(new AccessToken(['access_token' => 'token'])); - $mock->shouldReceive('getResourceOwner')->once()->andReturn(new GenericResourceOwner($oauthData, 'sub')); - - return $mock; - }); - - $response = $this->withoutExceptionHandling()->withSession([ - 'oauth2state' => 'abc123', - ])->get('auth/oidc/callback?state=abc123&code=1'); - - $response->assertRedirect('/'); - - $mappedUser = UserOidcMapping::where('oidc_id', $oauthData['sub'])->first(); - $this->assertNotNull($mappedUser, 'mapping is found'); - $user = $mappedUser->user; - $this->assertEquals($user->username, $oauthData['preferred_username']); - $this->assertEquals($user->email, $oauthData['email']); - $this->assertEquals(Auth::guard()->user()->id, $user->id); - - $this->assertDatabaseCount('users', $originalUserCount + 1); - } - - // #[Test] - public function view_oidc_callback_existing_user() - { - $user = User::create([ 'name' => fake()->name, - 'username' => fake()->unique()->username, + 'preferred_username' => $input, 'email' => fake()->unique()->freeEmail, - ]); - $originalUserCount = User::count(); - $this->assertDatabaseCount('users', $originalUserCount); - - config(['remote-auth.oidc.enabled' => true]); - - $oauthData = [ - 'sub' => Str::random(10), - 'preferred_username' => $user->username, - 'email' => $user->email, ]; - UserOidcMapping::create([ - 'oidc_id' => $oauthData['sub'], - 'user_id' => $user->id, - ]); - $this->partialMock(UserOidcService::class, function (MockInterface $mock) use ($oauthData) { $mock->shouldReceive('getAccessToken')->once()->andReturn(new AccessToken(['access_token' => 'token'])); $mock->shouldReceive('getResourceOwner')->once()->andReturn(new GenericResourceOwner($oauthData, 'sub')); - - return $mock; }); $response = $this->withoutExceptionHandling()->withSession([ @@ -114,62 +150,9 @@ class RemoteOidcTest extends TestCase $response->assertRedirect('/'); $mappedUser = UserOidcMapping::where('oidc_id', $oauthData['sub'])->first(); - $this->assertNotNull($mappedUser, 'mapping is found'); - $user = $mappedUser->user; - $this->assertEquals($user->username, $oauthData['preferred_username']); - $this->assertEquals($user->email, $oauthData['email']); - $this->assertEquals(Auth::guard()->user()->id, $user->id); - - $this->assertDatabaseCount('users', $originalUserCount); - } - #[Test] - public function view_oidc_callback_ensure_valid_username() - { - config(['remote-auth.oidc.enabled' => true]); - config(['remote-auth.oidc.field_username' => 'preferred_username']); - - $dataset = [ - 'john.doe@domain.com' => 'johndoe', - 'test+user@part1@domain.com' => 'testuser', - 'user!#$%^&*()_test' => 'user_test', - 'jean-luc.picard' => 'jeanlucpicard', - 'supercalifragilisticexpialidøcious@test.com' => 'supercalifragilisticexpialidci', - 'hélène_renåud' => 'hlne_renud', - '123456789' => '123456789', - ' user _ name ' => 'user_name', - 'foo+bar@sub.domain.co.uk' => 'foobar', - ]; - - foreach ($dataset as $input => $expected) { - Auth::logout(); - session()->flush(); - - $originalUserCount = User::count(); - - $oauthData = [ - 'sub' => Str::random(10), - 'name' => fake()->name, - 'preferred_username' => $input, - 'email' => fake()->unique()->freeEmail, - ]; - - $this->partialMock(UserOidcService::class, function (MockInterface $mock) use ($oauthData) { - $mock->shouldReceive('getAccessToken')->once()->andReturn(new AccessToken(['access_token' => 'token'])); - $mock->shouldReceive('getResourceOwner')->once()->andReturn(new GenericResourceOwner($oauthData, 'sub')); - }); - - $response = $this->withoutExceptionHandling()->withSession([ - 'oauth2state' => 'abc123', - ])->get('auth/oidc/callback?state=abc123&code=1'); - - $response->assertRedirect('/'); - - $mappedUser = UserOidcMapping::where('oidc_id', $oauthData['sub'])->first(); - - $this->assertNotNull($mappedUser, "Mapping not found for : {$input}"); - $this->assertEquals($expected, $mappedUser->user->username, "Username not valid : {$input}"); - $this->assertDatabaseCount('users', $originalUserCount + 1); - } + $this->assertNotNull($mappedUser, "Mapping not found for : {$input}"); + $this->assertEquals($expected, $mappedUser->user->username, "Username not valid : {$input}"); + $this->assertDatabaseCount('users', $originalUserCount + 1); } -} +}); diff --git a/tests/Feature/SeasonalAggregationTest.php b/tests/Feature/SeasonalAggregationTest.php index 2707c473b..6df137e17 100644 --- a/tests/Feature/SeasonalAggregationTest.php +++ b/tests/Feature/SeasonalAggregationTest.php @@ -1,12 +1,10 @@ fromSub( - Status::query() - ->whereNull('uri') - ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) - ->where('created_at', '>', $epochStart) - ->where('created_at', '<', $epochEnd) - ->groupBy('profile_id') - ->selectRaw('count(*) as count'), - 'per_profile' - )->avg('count'); - } + return (float) DB::query()->fromSub( + Status::query() + ->whereNull('uri') + ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) + ->where('created_at', '>', $epochStart) + ->where('created_at', '<', $epochEnd) + ->groupBy('profile_id') + ->selectRaw('count(*) as count'), + 'per_profile' + )->avg('count'); +} - #[Test] - public function it_averages_matching_posts_per_profile_in_sql() - { - $epochStart = '2020-01-01 00:00:00'; - $epochEnd = '2020-12-31 23:59:59'; - $inRange = '2020-06-01 12:00:00'; +it('averages matching posts per profile in sql', function () { + $epochStart = '2020-01-01 00:00:00'; + $epochEnd = '2020-12-31 23:59:59'; + $inRange = '2020-06-01 12:00:00'; - // Profile 1: 2 matching photos. Profile 2: 4 matching photos. - // Average per profile = 3. - Status::factory()->count(2)->photo()->create([ - 'profile_id' => 1001, - 'created_at' => $inRange, - ]); - Status::factory()->count(4)->photo()->create([ - 'profile_id' => 1002, - 'created_at' => $inRange, - ]); + // Profile 1: 2 matching photos. Profile 2: 4 matching photos. + // Average per profile = 3. + Status::factory()->count(2)->photo()->create([ + 'profile_id' => 1001, + 'created_at' => $inRange, + ]); + Status::factory()->count(4)->photo()->create([ + 'profile_id' => 1002, + 'created_at' => $inRange, + ]); - // Noise that must be excluded from the average: - // remote (uri set), wrong type, and out-of-range date. - Status::factory()->photo()->create([ - 'profile_id' => 1003, - 'uri' => 'https://remote.example/statuses/1', - 'created_at' => $inRange, - ]); - Status::factory()->create([ - 'profile_id' => 1003, - 'type' => 'text', - 'created_at' => $inRange, - ]); - Status::factory()->photo()->create([ - 'profile_id' => 1004, - 'created_at' => '2019-01-01 00:00:00', - ]); + // Noise that must be excluded from the average: + // remote (uri set), wrong type, and out-of-range date. + Status::factory()->photo()->create([ + 'profile_id' => 1003, + 'uri' => 'https://remote.example/statuses/1', + 'created_at' => $inRange, + ]); + Status::factory()->create([ + 'profile_id' => 1003, + 'type' => 'text', + 'created_at' => $inRange, + ]); + Status::factory()->photo()->create([ + 'profile_id' => 1004, + 'created_at' => '2019-01-01 00:00:00', + ]); - $this->assertSame(3.0, $this->averagePostsPerProfile($epochStart, $epochEnd)); - } + expect(averagePostsPerProfile($epochStart, $epochEnd))->toBe(3.0); +}); - #[Test] - public function it_returns_zero_when_no_posts_match() - { - $average = $this->averagePostsPerProfile('2020-01-01 00:00:00', '2020-12-31 23:59:59'); +it('returns zero when no posts match', function () { + $average = averagePostsPerProfile('2020-01-01 00:00:00', '2020-12-31 23:59:59'); - // No rows -> AVG returns null -> cast to 0.0 - $this->assertSame(0.0, $average); - } -} + // No rows -> AVG returns null -> cast to 0.0 + expect($average)->toBe(0.0); +}); diff --git a/tests/Feature/StatusDeleteCleanupTest.php b/tests/Feature/StatusDeleteCleanupTest.php index 29468189b..594fb38bb 100644 --- a/tests/Feature/StatusDeleteCleanupTest.php +++ b/tests/Feature/StatusDeleteCleanupTest.php @@ -1,7 +1,5 @@ create(); - $user->refresh(); - - $status = Status::factory()->create([ - 'profile_id' => $user->profile_id, - 'type' => 'photo', - ]); - - $dm = new DirectMessage; - $dm->to_id = $user->profile_id; - $dm->from_id = $user->profile_id; - $dm->status_id = $status->id; - $dm->save(); - - $notification = Notification::create([ - 'profile_id' => $user->profile_id, - 'actor_id' => $user->profile_id, - 'action' => 'dm', - 'item_type' => DirectMessage::class, - 'item_id' => $dm->id, - ]); - - (new StatusDelete($status))->handle(); - - $this->assertNull(DirectMessage::find($dm->id)); - $this->assertNull(Notification::find($notification->id)); - $this->assertNull(Status::find($status->id)); - } - - #[Test] - public function deleting_a_status_removes_associated_media_tags_and_their_notifications() - { - $user = User::factory()->create(); - $user->refresh(); - - $status = Status::factory()->create([ - 'profile_id' => $user->profile_id, - 'type' => 'photo', - ]); - - $tag = MediaTag::create([ - 'status_id' => $status->id, - 'media_id' => 1, - 'profile_id' => $user->profile_id, - 'tagged_username' => 'someone', - ]); - - $notification = Notification::create([ - 'profile_id' => $user->profile_id, - 'actor_id' => $user->profile_id, - 'action' => 'tagged', - 'item_type' => MediaTag::class, - 'item_id' => $tag->id, - ]); - - (new StatusDelete($status))->handle(); - - $this->assertNull(MediaTag::find($tag->id)); - $this->assertNull(Notification::find($notification->id)); - } - - #[Test] - public function deleting_a_status_without_dms_or_tags_still_deletes_the_status() - { - $user = User::factory()->create(); - $user->refresh(); - - $status = Status::factory()->create([ - 'profile_id' => $user->profile_id, - 'type' => 'photo', - ]); - - (new StatusDelete($status))->handle(); - - $this->assertNull(Status::find($status->id)); - } - - #[Test] - public function deleting_a_status_cleans_up_even_when_the_owning_profile_is_soft_deleted() - { - // Mirrors the account-deletion flow on AP-enabled instances: the - // owning profile is soft-deleted before the queued StatusDelete runs. - config(['federation.activitypub.enabled' => true]); - - $user = User::factory()->create(); - $user->refresh(); - - $status = Status::factory()->create([ - 'profile_id' => $user->profile_id, - 'type' => 'photo', - ]); - - // Soft-delete the owning profile, as DeleteAccountPipeline does. - $user->profile->delete(); - - (new StatusDelete($status))->handle(); - - $this->assertNull(Status::find($status->id)); - } -} +it('removes associated dms and their notifications when a status is deleted', function () { + $user = User::factory()->create(); + $user->refresh(); + + $status = Status::factory()->create([ + 'profile_id' => $user->profile_id, + 'type' => 'photo', + ]); + + $dm = new DirectMessage; + $dm->to_id = $user->profile_id; + $dm->from_id = $user->profile_id; + $dm->status_id = $status->id; + $dm->save(); + + $notification = Notification::create([ + 'profile_id' => $user->profile_id, + 'actor_id' => $user->profile_id, + 'action' => 'dm', + 'item_type' => DirectMessage::class, + 'item_id' => $dm->id, + ]); + + (new StatusDelete($status))->handle(); + + expect(DirectMessage::find($dm->id))->toBeNull(); + expect(Notification::find($notification->id))->toBeNull(); + expect(Status::find($status->id))->toBeNull(); +}); + +it('removes associated media tags and their notifications when a status is deleted', function () { + $user = User::factory()->create(); + $user->refresh(); + + $status = Status::factory()->create([ + 'profile_id' => $user->profile_id, + 'type' => 'photo', + ]); + + $tag = MediaTag::create([ + 'status_id' => $status->id, + 'media_id' => 1, + 'profile_id' => $user->profile_id, + 'tagged_username' => 'someone', + ]); + + $notification = Notification::create([ + 'profile_id' => $user->profile_id, + 'actor_id' => $user->profile_id, + 'action' => 'tagged', + 'item_type' => MediaTag::class, + 'item_id' => $tag->id, + ]); + + (new StatusDelete($status))->handle(); + + expect(MediaTag::find($tag->id))->toBeNull(); + expect(Notification::find($notification->id))->toBeNull(); +}); + +it('still deletes a status without dms or tags', function () { + $user = User::factory()->create(); + $user->refresh(); + + $status = Status::factory()->create([ + 'profile_id' => $user->profile_id, + 'type' => 'photo', + ]); + + (new StatusDelete($status))->handle(); + + expect(Status::find($status->id))->toBeNull(); +}); + +it('cleans up even when the owning profile is soft deleted', function () { + // Mirrors the account-deletion flow on AP-enabled instances: the + // owning profile is soft-deleted before the queued StatusDelete runs. + config(['federation.activitypub.enabled' => true]); + + $user = User::factory()->create(); + $user->refresh(); + + $status = Status::factory()->create([ + 'profile_id' => $user->profile_id, + 'type' => 'photo', + ]); + + // Soft-delete the owning profile, as DeleteAccountPipeline does. + $user->profile->delete(); + + (new StatusDelete($status))->handle(); + + expect(Status::find($status->id))->toBeNull(); +}); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 44a4f337a..36c1918a7 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -1,5 +1,15 @@ toBeTrue(); -}); +namespace Tests\Unit; + +use PHPUnit\Framework\Attributes\Test; +use Tests\TestCase; + +class ExampleTest extends TestCase +{ + #[Test] + public function that_true_is_true() + { + $this->assertTrue(true); + } +}