From 48d1c9a00b3bfada4dcb9ccb1fd13bc9af712356 Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Sat, 14 May 2022 17:44:14 +0100 Subject: [PATCH] Improve tests --- app/Http/Middleware/Authenticate.php | 3 + .../Middleware/RedirectIfAuthenticated.php | 3 + app/Http/Middleware/TrustHosts.php | 3 + app/Jobs/ProcessLike.php | 6 +- app/Providers/AppServiceProvider.php | 3 + app/Providers/HorizonServiceProvider.php | 4 +- app/Providers/RouteServiceProvider.php | 2 + app/Services/BookmarkService.php | 3 +- database/factories/WebMentionFactory.php | 2 +- tests/Feature/ActivityStreamTest.php | 13 ++++ tests/Feature/Admin/AdminTest.php | 48 +++++++++++++++ tests/Feature/FrontPageTest.php | 33 ++++++++++ tests/Feature/HorizonTest.php | 28 +++++++++ tests/Feature/LikesTest.php | 38 ++++++++++++ tests/Feature/MicropubMediaTest.php | 60 +++++++++++++++++++ tests/Unit/ArticlesTest.php | 7 +++ tests/Unit/Jobs/ProcessWebMentionJobTest.php | 14 +++-- tests/Unit/LikesTest.php | 11 ++++ 18 files changed, 267 insertions(+), 14 deletions(-) create mode 100644 tests/Feature/FrontPageTest.php create mode 100644 tests/Feature/HorizonTest.php diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index a4be5c58..81e6fd40 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -4,6 +4,9 @@ namespace App\Http\Middleware; use Illuminate\Auth\Middleware\Authenticate as Middleware; +/** + * @codeCoverageIgnore + */ class Authenticate extends Middleware { /** diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 362b48b0..4e0fd5c4 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -7,6 +7,9 @@ use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +/** + * @codeCoverageIgnore + */ class RedirectIfAuthenticated { /** diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php index b0550cfc..79426404 100644 --- a/app/Http/Middleware/TrustHosts.php +++ b/app/Http/Middleware/TrustHosts.php @@ -4,6 +4,9 @@ namespace App\Http\Middleware; use Illuminate\Http\Middleware\TrustHosts as Middleware; +/** + * @codeCoverageIgnore + */ class TrustHosts extends Middleware { /** diff --git a/app/Jobs/ProcessLike.php b/app/Jobs/ProcessLike.php index 976ad010..1975fef7 100644 --- a/app/Jobs/ProcessLike.php +++ b/app/Jobs/ProcessLike.php @@ -60,7 +60,7 @@ class ProcessLike implements ShouldQueue //POSSE like try { - $response = $client->request( + $client->request( 'POST', 'https://brid.gy/publish/webmention', [ @@ -70,8 +70,8 @@ class ProcessLike implements ShouldQueue ], ] ); - } catch (RequestException $exception) { - //no biggie + } catch (RequestException) { + return 0; } return 0; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 73269ee7..1de324c9 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -38,6 +38,8 @@ class AppServiceProvider extends ServiceProvider }); // Bind the Codebird client + // Codebird gets mocked in tests + // @codeCoverageIgnoreStart $this->app->bind('Codebird\Codebird', function () { Codebird::setConsumerKey( env('TWITTER_CONSUMER_KEY'), @@ -53,6 +55,7 @@ class AppServiceProvider extends ServiceProvider return $cb; }); + // @codeCoverageIgnoreEnd /** * Paginate a standard Laravel Collection. diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index 0db3c49c..89a3c7b4 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -34,9 +34,7 @@ class HorizonServiceProvider extends HorizonApplicationServiceProvider protected function gate() { Gate::define('viewHorizon', function ($user) { - return in_array($user->name, [ - 'jonny', - ]); + return $user->name === 'jonny'; }); } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 1eb1b5af..3f8af660 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -44,6 +44,8 @@ class RouteServiceProvider extends ServiceProvider * Configure the rate limiters for the application. * * @return void + * + * @codeCoverageIgnore */ protected function configureRateLimiting() { diff --git a/app/Services/BookmarkService.php b/app/Services/BookmarkService.php index 871bf078..ccbeaf43 100644 --- a/app/Services/BookmarkService.php +++ b/app/Services/BookmarkService.php @@ -79,11 +79,12 @@ class BookmarkService } /** - * Given a URL, use browsershot to save an image of the page. + * Given a URL, use `browsershot` to save an image of the page. * * @param string $url * @return string The uuid for the screenshot * @throws CouldNotTakeBrowsershot + * @codeCoverageIgnore */ public function saveScreenshot(string $url): string { diff --git a/database/factories/WebMentionFactory.php b/database/factories/WebMentionFactory.php index bb26f0da..b9a97fef 100644 --- a/database/factories/WebMentionFactory.php +++ b/database/factories/WebMentionFactory.php @@ -24,7 +24,7 @@ class WebMentionFactory extends Factory return [ 'source' => $this->faker->url, 'target' => url('notes/1'), - 'type' => 'reply', + 'type' => 'in-reply-to', 'content' => $this->faker->paragraph, ]; } diff --git a/tests/Feature/ActivityStreamTest.php b/tests/Feature/ActivityStreamTest.php index 3453f220..5e618992 100644 --- a/tests/Feature/ActivityStreamTest.php +++ b/tests/Feature/ActivityStreamTest.php @@ -25,6 +25,19 @@ class ActivityStreamTest extends TestCase ]); } + /** @test */ + public function notesPageContainsAuthorActivityStreamData(): void + { + $response = $this->get('/notes', ['Accept' => 'application/activity+json']); + $response->assertHeader('Content-Type', 'application/activity+json'); + $response->assertJson([ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => config('app.url'), + 'type' => 'Person', + 'name' => config('user.displayname'), + ]); + } + /** @test */ public function requestForNoteIncludesActivityStreamData(): void { diff --git a/tests/Feature/Admin/AdminTest.php b/tests/Feature/Admin/AdminTest.php index 4ae91055..2d3f8f70 100644 --- a/tests/Feature/Admin/AdminTest.php +++ b/tests/Feature/Admin/AdminTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Tests\Feature\Admin; +use App\Models\User; use Tests\TestCase; class AdminTest extends TestCase @@ -31,4 +32,51 @@ class AdminTest extends TestCase ]); $response->assertRedirect('/login'); } + + /** @test */ + public function loginSucceeds(): void + { + User::factory([ + 'name' => 'admin', + 'password' => bcrypt('password'), + ])->create(); + + $response = $this->post('/login', [ + 'name' => 'admin', + 'password' => 'password', + ]); + + $response->assertRedirect('/'); + } + + /** @test */ + public function whenLoggedInRedirectsToAdminPage(): void + { + $user = User::factory()->create(); + $response = $this->actingAs($user)->get('/login'); + $response->assertRedirect('/'); + } + + /** @test */ + public function loggedOutUsersSimplyRedirected(): void + { + $response = $this->get('/logout'); + $response->assertRedirect('/'); + } + + /** @test */ + public function loggedInUsersShownLogoutForm(): void + { + $user = User::factory()->create(); + $response = $this->actingAs($user)->get('/logout'); + $response->assertViewIs('logout'); + } + + /** @test */ + public function loggedInUsersCanLogout(): void + { + $user = User::factory()->create(); + $response = $this->actingAs($user)->post('/logout'); + $response->assertRedirect('/'); + } } diff --git a/tests/Feature/FrontPageTest.php b/tests/Feature/FrontPageTest.php new file mode 100644 index 00000000..6f24a851 --- /dev/null +++ b/tests/Feature/FrontPageTest.php @@ -0,0 +1,33 @@ +create(['note' => 'Note 1']); + Article::factory()->create(['title' => 'Article 1']); + Bookmark::factory()->create(['url' => 'https://example.com']); + Like::factory()->create([ + 'url' => 'https://example.org/1', + 'content' => 'Like 1', + ]); + + $this->get('/') + ->assertSee('Note 1') + ->assertSee('Article 1') + ->assertSee('https://example.com') + ->assertSee('Like 1'); + } +} diff --git a/tests/Feature/HorizonTest.php b/tests/Feature/HorizonTest.php new file mode 100644 index 00000000..fe264981 --- /dev/null +++ b/tests/Feature/HorizonTest.php @@ -0,0 +1,28 @@ +create([ + 'name' => 'jonny', + ]); + + $response = $this->actingAs($user)->get('/horizon'); + + $response->assertStatus(200); + } +} diff --git a/tests/Feature/LikesTest.php b/tests/Feature/LikesTest.php index 7e56f6b2..b521f957 100644 --- a/tests/Feature/LikesTest.php +++ b/tests/Feature/LikesTest.php @@ -233,6 +233,44 @@ class LikesTest extends TestCase $this->assertEquals('Jonny Barnes', Like::find($id)->author_name); } + /** @test */ + public function noErrorForFailureToPosseWithBridgy(): void + { + $like = new Like(); + $like->url = 'https://twitter.com/jonnybarnes/status/1050823255123251200'; + $like->save(); + $id = $like->id; + + $job = new ProcessLike($like); + + $mock = new MockHandler([ + new Response(404, [], 'Not found'), + ]); + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->app->bind(Client::class, function () use ($client) { + return $client; + }); + + $info = (object) [ + 'author_name' => 'Jonny Barnes', + 'author_url' => 'https://twitter.com/jonnybarnes', + 'html' => '
HTML of the tweet embed
', + ]; + $codebirdMock = $this->getMockBuilder(Codebird::class) + ->addMethods(['statuses_oembed']) + ->getMock(); + $codebirdMock->method('statuses_oembed') + ->willReturn($info); + $this->app->instance(Codebird::class, $codebirdMock); + + $authorship = new Authorship(); + + $job->handle($client, $authorship); + + $this->assertEquals('Jonny Barnes', Like::find($id)->author_name); + } + /** @test */ public function unknownLikeGivesNotFoundResponse(): void { diff --git a/tests/Feature/MicropubMediaTest.php b/tests/Feature/MicropubMediaTest.php index 6ae6972e..0cff5a2b 100644 --- a/tests/Feature/MicropubMediaTest.php +++ b/tests/Feature/MicropubMediaTest.php @@ -34,6 +34,50 @@ class MicropubMediaTest extends TestCase $response->assertJson(['url' => null]); } + /** @test */ + public function getRequestWithInvalidTokenReturnsErrorResponse(): void + { + $response = $this->get( + '/api/media?q=last', + ['HTTP_Authorization' => 'Bearer abc123'] + ); + $response->assertStatus(400); + $response->assertJsonFragment(['error_description' => 'The provided token did not pass validation']); + } + + /** @test */ + public function getRequestWithTokenWithoutScopeReturnsErrorResponse(): void + { + $response = $this->get( + '/api/media?q=last', + ['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithNoScope()] + ); + $response->assertStatus(400); + $response->assertJsonFragment(['error_description' => 'The provided token has no scopes']); + } + + /** @test */ + public function getRequestWithTokenWithInsufficientScopeReturnsErrorResponse(): void + { + $response = $this->get( + '/api/media?q=last', + ['HTTP_Authorization' => 'Bearer ' . $this->getTokenWithIncorrectScope()] + ); + $response->assertStatus(401); + $response->assertJsonFragment(['error_description' => 'The token’s scope does not have the necessary requirements.']); + } + + /** @test */ + public function emptyGetRequestWithTokenReceivesOkResponse(): void + { + $response = $this->get( + '/api/media', + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $response->assertStatus(200); + $response->assertJson(['status' => 'OK']); + } + /** @test */ public function clientCanListLastUpload(): void { @@ -124,6 +168,22 @@ class MicropubMediaTest extends TestCase unlink(storage_path('app/') . $filename); } + /** @test */ + public function mediaEndpointUploadRequiresFile(): void + { + $response = $this->post( + '/api/media', + [], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $response->assertStatus(400); + $response->assertJson([ + 'response' => 'error', + 'error' => 'invalid_request', + 'error_description' => 'No file was sent with the request', + ]); + } + /** @test */ public function errorResponseForUnknownQValue(): void { diff --git a/tests/Unit/ArticlesTest.php b/tests/Unit/ArticlesTest.php index fe9111ac..b8eba302 100644 --- a/tests/Unit/ArticlesTest.php +++ b/tests/Unit/ArticlesTest.php @@ -79,5 +79,12 @@ class ArticlesTest extends TestCase $emptyScope = Article::date()->get(); $this->assertCount(2, $emptyScope); + + // Check the December case + $article = Article::factory()->create([ + 'created_at' => Carbon::now()->setMonth(12)->toDateTimeString(), + 'updated_at' => Carbon::now()->setMonth(12)->toDateTimeString(), + ]); + $this->assertCount(1, Article::date(date('Y'), 12)->get()); } } diff --git a/tests/Unit/Jobs/ProcessWebMentionJobTest.php b/tests/Unit/Jobs/ProcessWebMentionJobTest.php index b753fb40..4d76a4fb 100644 --- a/tests/Unit/Jobs/ProcessWebMentionJobTest.php +++ b/tests/Unit/Jobs/ProcessWebMentionJobTest.php @@ -89,23 +89,25 @@ class ProcessWebMentionJobTest extends TestCase Queue::fake(); $parser = new Parser(); + $note = Note::factory()->create(); + $source = 'https://aaronpk.localhost/reply/1'; + WebMention::factory()->create([ + 'source' => $source, + 'target' => $note->longurl, + ]); $html = << -

In reply to a note

+

In reply to a note

Updated reply
HTML; - $html = str_replace('href="', 'href="' . config('app.url'), $html); $mock = new MockHandler([ new Response(200, [], $html), ]); $handler = HandlerStack::create($mock); $client = new Client(['handler' => $handler]); - $note = Note::factory()->create(); - $source = 'https://aaronpk.localhost/reply/1'; - $job = new ProcessWebMention($note, $source); $job->handle($parser, $client); @@ -114,7 +116,7 @@ class ProcessWebMentionJobTest extends TestCase 'source' => $source, 'type' => 'in-reply-to', // phpcs:ignore Generic.Files.LineLength.TooLong - 'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"content": [{"html": "Updated reply", "value": "Updated reply"}], "in-reply-to": ["' . config('app.url') . '/notes/E"]}}], "rel-urls": []}', + 'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"content": [{"html": "Updated reply", "value": "Updated reply"}], "in-reply-to": ["' . $note->longurl . '"]}}], "rel-urls": []}', ]); } diff --git a/tests/Unit/LikesTest.php b/tests/Unit/LikesTest.php index 32687829..3952b1e7 100644 --- a/tests/Unit/LikesTest.php +++ b/tests/Unit/LikesTest.php @@ -31,6 +31,17 @@ class LikesTest extends TestCase $this->assertEquals('some plaintext content', $like->content); } + /** @test */ + public function weCanHandleBlankContent(): void + { + $like = new Like(); + $like->url = 'https://example.org/post/123'; + $like->content = null; + $like->save(); + + $this->assertNull($like->content); + } + /** @test */ public function htmlLikeContentIsFiltered(): void {