diff --git a/routes/web.php b/routes/web.php
index 8ab10f7e..115cf04b 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -31,26 +31,26 @@ Route::group(['domain' => config('url.longurl')], function () {
Route::post('login', 'AuthController@login');
//Admin pages grouped for filter
- Route::group(['middleware' => 'myauth'], function () {
+ Route::group(['middleware' => 'myauth', 'namespace' => 'Admin'], function () {
Route::get('admin', 'AdminController@showWelcome');
//Articles
- Route::get('admin/blog/new', 'ArticlesAdminController@newArticle');
- Route::get('admin/blog/edit', 'ArticlesAdminController@listArticles');
- Route::get('admin/blog/edit/{id}', 'ArticlesAdminController@editArticle');
- Route::get('admin/blog/delete/{id}', 'ArticlesAdminController@deleteArticle');
- Route::post('admin/blog/new', 'ArticlesAdminController@postNewArticle');
- Route::post('admin/blog/edit/{id}', 'ArticlesAdminController@postEditArticle');
- Route::post('admin/blog/delete/{id}', 'ArticlesAdminController@postDeleteArticle');
+ Route::get('admin/blog/new', 'ArticlesAdminController@create');
+ Route::get('admin/blog/edit', 'ArticlesAdminController@index');
+ Route::get('admin/blog/edit/{id}', 'ArticlesAdminController@edit');
+ Route::get('admin/blog/delete/{id}', 'ArticlesAdminController@delete');
+ Route::post('admin/blog/new', 'ArticlesAdminController@store');
+ Route::post('admin/blog/edit/{id}', 'ArticlesAdminController@update');
+ Route::post('admin/blog/delete/{id}', 'ArticlesAdminController@detroy');
//Notes
- Route::get('admin/note/new', 'NotesAdminController@newNotePage');
- Route::get('admin/note/edit', 'NotesAdminController@listNotesPage');
- Route::get('admin/note/edit/{id}', 'NotesAdminController@editNotePage');
- Route::get('admin/note/delete/{id}', 'NotesAdminController@deleteNotePage');
- Route::post('admin/note/new', 'NotesAdminController@createNote');
- Route::post('admin/note/edit/{id}', 'NotesAdminController@editNote');
- Route::post('admin/note/delete/{id}', 'NotesAdminController@deleteNote');
+ Route::get('admin/note/edit', 'NotesAdminController@index');
+ Route::get('admin/note/new', 'NotesAdminController@create');
+ Route::get('admin/note/edit/{id}', 'NotesAdminController@edit');
+ Route::get('admin/note/delete/{id}', 'NotesAdminController@delete');
+ Route::post('admin/note/new', 'NotesAdminController@store');
+ Route::post('admin/note/edit/{id}', 'NotesAdminController@update');
+ Route::post('admin/note/delete/{id}', 'NotesAdminController@destroy');
//Tokens
Route::get('admin/tokens', 'TokensController@showTokens');
@@ -58,28 +58,28 @@ Route::group(['domain' => config('url.longurl')], function () {
Route::post('admin/tokens/delete/{id}', 'TokensController@postDeleteToken');
//Micropub Clients
- Route::get('admin/clients', 'ClientsAdminController@listClients');
- Route::get('admin/clients/new', 'ClientsAdminController@newClient');
- Route::get('admin/clients/edit/{id}', 'ClientsAdminController@editClient');
- Route::post('admin/clients/new', 'ClientsAdminController@postNewClient');
- Route::post('admin/clients/edit/{id}', 'ClientsAdminController@postEditClient');
+ Route::get('admin/clients', 'ClientsAdminController@index');
+ Route::get('admin/clients/new', 'ClientsAdminController@create');
+ Route::get('admin/clients/edit/{id}', 'ClientsAdminController@edit');
+ Route::post('admin/clients/new', 'ClientsAdminController@store');
+ Route::post('admin/clients/edit/{id}', 'ClientsAdminController@update');
//Contacts
- Route::get('admin/contacts/new', 'ContactsAdminController@newContact');
- Route::get('admin/contacts/edit', 'ContactsAdminController@listContacts');
- Route::get('admin/contacts/edit/{id}', 'ContactsAdminController@editContact');
+ Route::get('admin/contacts/edit', 'ContactsAdminController@index');
+ Route::get('admin/contacts/new', 'ContactsAdminController@create');
+ Route::get('admin/contacts/edit/{id}', 'ContactsAdminController@edit');
+ Route::get('admin/contacts/delete/{id}', 'ContactsAdminController@delete');
+ Route::post('admin/contacts/new', 'ContactsAdminController@store');
+ Route::post('admin/contacts/edit/{id}', 'ContactsAdminController@update');
+ Route::post('admin/contacts/delete/{id}', 'ContactsAdminController@destroy');
Route::get('admin/contacts/edit/{id}/getavatar', 'ContactsAdminController@getAvatar');
- Route::get('admin/contacts/delete/{id}', 'ContactsAdminController@deleteContact');
- Route::post('admin/contacts/new', 'ContactsAdminController@postNewContact');
- Route::post('admin/contacts/edit/{id}', 'ContactsAdminController@postEditContact');
- Route::post('admin/contacts/delete/{id}', 'ContactsAdminController@postDeleteContact');
//Places
- Route::get('admin/places/new', 'PlacesAdminController@newPlacePage');
- Route::get('admin/places/edit', 'PlacesAdminController@listPlacesPage');
- Route::get('admin/places/edit/{id}', 'PlacesAdminController@editPlacePage');
- Route::post('admin/places/new', 'PlacesAdminController@createPlace');
- Route::post('admin/places/edit/{id}', 'PlacesAdminController@editPlace');
+ Route::get('admin/places/edit', 'PlacesAdminController@index');
+ Route::get('admin/places/new', 'PlacesAdminController@create');
+ Route::get('admin/places/edit/{id}', 'PlacesAdminController@edit');
+ Route::post('admin/places/new', 'PlacesAdminController@store');
+ Route::post('admin/places/edit/{id}', 'PlacesAdminController@update');
});
//Blog pages using ArticlesController
diff --git a/tests/ArticlesTest.php b/tests/ArticlesTest.php
deleted file mode 100644
index 850adeee..00000000
--- a/tests/ArticlesTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-appurl = config('app.url');
- }
-
- /**
- * Test the `/blog` page returns the article, this
- * means the database is being hit.
- *
- * @return void
- */
- public function testArticlesPage()
- {
- $this->visit($this->appurl . '/blog')
- ->see('My New Blog');
- }
-
- /**
- * Test the `/blog/{year}` page returns the article, this
- * means the database is being hit.
- *
- * @return void
- */
- public function testArticlesYearPage()
- {
- $this->visit($this->appurl . '/blog/2016')
- ->see('My New Blog');
- }
-
- /**
- * Test the `/blog/{year}/{month}` page returns the article,
- * this means the database is being hit.
- *
- * @return void
- */
- public function testArticlesMonthPage()
- {
- $this->visit($this->appurl . '/blog/2016/01')
- ->see('My New Blog');
- }
-
- /**
- * Test a single article page.
- *
- * @return void
- */
- public function testSingleArticlePage()
- {
- $this->visit($this->appurl . '/blog/2016/01/my-new-blog')
- ->see('My New Blog');
- }
-
- /**
- * Test the RSS feed.
- *
- * @return void
- */
- public function testRSSFeed()
- {
- $response = $this->call('GET', $this->appurl . '/feed');
-
- $this->assertEquals('application/rss+xml', $response->headers->get('Content-Type'));
- }
-}
diff --git a/tests/Browser/ArticlesTest.php b/tests/Browser/ArticlesTest.php
new file mode 100644
index 00000000..2bb7cba2
--- /dev/null
+++ b/tests/Browser/ArticlesTest.php
@@ -0,0 +1,61 @@
+browse(function ($browser) {
+ $browser->visit('/blog')
+ ->assertSee('My New Blog');
+ });
+ }
+
+ /**
+ * Test the `/blog` page with a year scoping results.
+ *
+ * @return void
+ */
+ public function test_articles_page_with_specified_year()
+ {
+ $this->browse(function ($browser) {
+ $browser->visit('/blog/2016')
+ ->assertSee('My New Blog');
+ });
+ }
+
+ /**
+ * Test the `/blog` page with a year and month scoping results.
+ *
+ * @return void
+ */
+ public function test_articles_page_with_specified_year_and_month()
+ {
+ $this->browse(function ($browser) {
+ $browser->visit('/blog/2016/01')
+ ->assertSee('My New Blog');
+ });
+ }
+
+ /**
+ * Test a single article page.
+ *
+ * @return void
+ */
+ public function test_single_article_page()
+ {
+ $this->browse(function ($browser) {
+ $browser->visit('/blog/2016/01/my-new-blog')
+ ->assertSee('My New Blog');
+ });
+ }
+}
diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php
new file mode 100644
index 00000000..5430fc54
--- /dev/null
+++ b/tests/Browser/ExampleTest.php
@@ -0,0 +1,23 @@
+browse(function (Browser $browser) {
+ $browser->visit('/')
+ ->assertSee('Built with love');
+ });
+ }
+}
diff --git a/tests/Browser/MicropubClientTest.php b/tests/Browser/MicropubClientTest.php
new file mode 100644
index 00000000..25cece44
--- /dev/null
+++ b/tests/Browser/MicropubClientTest.php
@@ -0,0 +1,47 @@
+browse(function ($browser) {
+ $browser->visit(route('micropub-client'))
+ ->assertSee('You are authenticated');
+ });
+ }
+
+ public function test_client_page_creates_new_note()
+ {
+ $faker = \Faker\Factory::create();
+ $note = 'Fake note from #LaravelDusk: ' . $faker->text;
+ $this->browse(function ($browser) use ($note) {
+ $browser->visit(route('micropub-client'))
+ ->type('content', $note)
+ ->press('Submit');
+ });
+ $this->assertDatabaseHas('notes', ['note' => $note]);
+ $newNote = \App\Note::where('note', $note)->first();
+ $newNote->forceDelete();
+ }
+
+ public function test_client_page_updates_syndication()
+ {
+ $this->browse(function ($browser) {
+ $browser->visit(route('micropub-client'))
+ ->assertDontSee('jonnybarnes on Twitter')
+ ->clickLink('Refresh Syndication Targets')
+ ->pause(5000)
+ ->assertSee('jonnybarnes on Twitter');
+ });
+ }
+}
diff --git a/tests/Browser/NotesTest.php b/tests/Browser/NotesTest.php
new file mode 100644
index 00000000..81a59b3f
--- /dev/null
+++ b/tests/Browser/NotesTest.php
@@ -0,0 +1,35 @@
+browse(function ($browser) {
+ $browser->visit('/notes/D')
+ ->assertSee('JBL5');
+ });
+ }
+
+ /**
+ * Look for the client URL after the note.
+ *
+ * @return void
+ */
+ public function test_client_url_displayed()
+ {
+ $this->browse(function ($browser) {
+ $browser->visit('/notes/E')
+ ->assertSee('quill.p3k.io');
+ });
+ }
+}
diff --git a/tests/Browser/Pages/HomePage.php b/tests/Browser/Pages/HomePage.php
new file mode 100644
index 00000000..c04a7f0a
--- /dev/null
+++ b/tests/Browser/Pages/HomePage.php
@@ -0,0 +1,40 @@
+ '#selector',
+ ];
+ }
+}
diff --git a/tests/Browser/Pages/Page.php b/tests/Browser/Pages/Page.php
new file mode 100644
index 00000000..f8d76222
--- /dev/null
+++ b/tests/Browser/Pages/Page.php
@@ -0,0 +1,20 @@
+ '#selector',
+ ];
+ }
+}
diff --git a/tests/Browser/screenshots/.gitignore b/tests/Browser/screenshots/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/tests/Browser/screenshots/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/tests/ContactsTest.php b/tests/ContactsTest.php
deleted file mode 100644
index 6dddbfae..00000000
--- a/tests/ContactsTest.php
+++ /dev/null
@@ -1,52 +0,0 @@
-appurl = config('app.url');
- }
-
- /**
- * Test the `/contacts` page and see if response is OK.
- *
- * @return void
- */
- public function testContactsPage()
- {
- $this->visit($this->appurl . '/contacts')
- ->assertResponseOK();
- }
-
- /**
- * Test an individual contact page with default profile image.
- *
- * @return void
- */
- public function testContactPageWithDefaultPic()
- {
- $this->visit($this->appurl . '/contacts/tantek')
- ->see('

');
- }
-
- /**
- * Test an individual contact page with a specific profile image.
- *
- * @return void
- */
- public function testContactPageWithSpecificPic()
- {
- $this->visit($this->appurl . '/contacts/aaron')
- ->see('

');
- }
-}
diff --git a/tests/BrowserKitTest.php b/tests/CreatesApplication.php
similarity index 56%
rename from tests/BrowserKitTest.php
rename to tests/CreatesApplication.php
index a2429b4f..a2e89522 100644
--- a/tests/BrowserKitTest.php
+++ b/tests/CreatesApplication.php
@@ -1,17 +1,11 @@
make(Kernel::class)->bootstrap();
-
return $app;
}
}
diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php
new file mode 100644
index 00000000..6b71b600
--- /dev/null
+++ b/tests/DuskTestCase.php
@@ -0,0 +1,35 @@
+get('/feed');
+ $response->assertHeader('Content-Type', 'application/rss+xml');
+ }
+}
diff --git a/tests/Feature/ContactsTest.php b/tests/Feature/ContactsTest.php
new file mode 100644
index 00000000..643d37dc
--- /dev/null
+++ b/tests/Feature/ContactsTest.php
@@ -0,0 +1,44 @@
+get('/contacts');
+ $response->assertStatus(200);
+ }
+
+ /**
+ * Test an individual contact page with default profile image.
+ *
+ * @return void
+ */
+ public function test_contact_page_with_default_pic()
+ {
+ $response = $this->get('/contacts/tantek');
+ $response->assertViewHas('image', '/assets/profile-images/default-image');
+ }
+
+ /**
+ * Test an individual contact page with a specific profile image.
+ *
+ * @return void
+ */
+ public function test_contact_page_with_specific_pic()
+ {
+ $response = $this->get('/contacts/aaron');
+ $response->assertViewHas('image', '/assets/profile-images/aaronparecki.com/image');
+ }
+}
diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php
new file mode 100644
index 00000000..ed0d1cef
--- /dev/null
+++ b/tests/Feature/ExampleTest.php
@@ -0,0 +1,22 @@
+get('/');
+ $response->assertStatus(200);
+ }
+}
diff --git a/tests/Feature/IndieAuthControllerTest.php b/tests/Feature/IndieAuthControllerTest.php
new file mode 100644
index 00000000..c5ed4de1
--- /dev/null
+++ b/tests/Feature/IndieAuthControllerTest.php
@@ -0,0 +1,51 @@
+call('GET', '/indieauth/start', ['me' => 'http://example.org']);
+ $this->assertSame(config('app.url') . '/micropub/create', $response->headers->get('Location'));
+ }
+
+ /**
+ * Now we test the `start` method as a whole.
+ *
+ * @return void
+ */
+ public function test_indieauthcontroller_begin_auth_redirects_to_endpoint()
+ {
+ $response = $this->call('GET', '/indieauth/start', ['me' => config('app.url')]);
+ $this->assertSame(
+ 'https://indieauth.com/auth?me=',
+ substr($response->headers->get('Location'), 0, 30)
+ );
+ }
+
+ /**
+ * Test the `callback` method.
+ *
+ * @return void
+ */
+ public function test_indieauthcontroller_callback_method_gives_error_with_mismatched_state()
+ {
+ $response = $this->withSession(['state' => 'state-session'])
+ ->call(
+ 'GET',
+ 'indieauth/callback',
+ ['me', config('app.url'), 'state' => 'request-session']
+ );
+ $response->assertSessionHasErrors();
+ }
+}
diff --git a/tests/Feature/MicropubControllerTest.php b/tests/Feature/MicropubControllerTest.php
new file mode 100644
index 00000000..536e08f1
--- /dev/null
+++ b/tests/Feature/MicropubControllerTest.php
@@ -0,0 +1,316 @@
+get('/api/post');
+ $response->assertStatus(400);
+ $response->assertJsonFragment(['error_description' => 'No token provided with request']);
+ }
+
+ /**
+ * Test a GET request for the micropub endpoint without a valid token gives
+ * a 400 response. Also check the error message.
+ *
+ * @return void
+ */
+ public function test_micropub_request_without_valid_token_returns_400_response()
+ {
+ $response = $this->call('GET', '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer abc123']);
+ $response->assertStatus(400);
+ $response->assertJsonFragment(['error_description' => 'The provided token did not pass validation']);
+ }
+
+ /**
+ * Test a GET request for the micropub endpoint with a valid token gives a
+ * 200 response. Check token information is returned in the response.
+ *
+ * @return void
+ */
+ public function test_micropub_request_with_valid_token_returns_200_response()
+ {
+ $response = $this->call('GET', '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
+ $response->assertStatus(200);
+ $response->assertJsonFragment(['response' => 'token']);
+ }
+
+ /**
+ * Test a GET request for syndication targets.
+ *
+ * @return void
+ */
+ public function test_micropub_request_for_syndication_targets()
+ {
+ $response = $this->call('GET', '/api/post', ['q' => 'syndicate-to'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
+ $response->assertJsonFragment(['uid' => 'https://twitter.com/jonnybarnes']);
+ }
+
+ /**
+ * Test a request for places.
+ *
+ * @return void
+ */
+ public function test_micropub_request_for_nearby_places()
+ {
+ $response = $this->call('GET', '/api/post', ['q' => 'geo:53.5,-2.38'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
+ $response->assertJson(['places' => [['slug' =>'the-bridgewater-pub']]]);
+ }
+
+ /**
+ * Test a request for places, this time with an uncertainty parameter.
+ *
+ * @return void
+ */
+ public function test_micropub_request_for_nearby_places_with_uncertainty_parameter()
+ {
+ $response = $this->call('GET', '/api/post', ['q' => 'geo:53.5,-2.38;u=35'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
+ $response->assertJson(['places' => [['slug' => 'the-bridgewater-pub']]]);
+ }
+
+ /**
+ * Test a request for places, where there will be an “empty” response.
+ *
+ * @return void
+ */
+ public function test_micropub_request_for_nearby_places_where_non_exist()
+ {
+ $response = $this->call('GET', '/api/post', ['q' => 'geo:1.23,4.56'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
+ $response->assertJson(['places' => []]);
+ }
+
+ /**
+ * Test a request for the micropub config.
+ *
+ * @return void
+ */
+ public function test_micropub_request_for_config()
+ {
+ $response = $this->call('GET', '/api/post', ['q' => 'config'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
+ $response->assertJsonFragment(['uid' => 'https://twitter.com/jonnybarnes']);
+ }
+
+ /**
+ * Test a valid micropub requests creates a new note.
+ *
+ * @return void
+ */
+ public function test_micropub_request_creates_new_note()
+ {
+ $faker = \Faker\Factory::create();
+ $note = $faker->text;
+ $response = $this->call(
+ 'POST',
+ '/api/post',
+ [
+ 'h' => 'entry',
+ 'content' => $note
+ ],
+ [],
+ [],
+ ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
+ );
+ $response->assertJson(['response' => 'created']);
+ $this->assertDatabaseHas('notes', ['note' => $note]);
+ }
+
+ /**
+ * Test a valid micropub requests creates a new place.
+ *
+ * @return void
+ */
+ public function test_micropub_request_creates_new_place()
+ {
+ $response = $this->call(
+ 'POST',
+ '/api/post',
+ [
+ 'h' => 'card',
+ 'name' => 'The Barton Arms',
+ 'geo' => 'geo:53.4974,-2.3768'
+ ],
+ [],
+ [],
+ ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
+ );
+ $response->assertJson(['response' => 'created']);
+ $this->assertDatabaseHas('places', ['slug' => 'the-barton-arms']);
+ }
+
+ /**
+ * Test a valid micropub requests using JSON syntax creates a new note.
+ *
+ * @return void
+ */
+ public function test_micropub_request_with_json_syntax_creates_new_note()
+ {
+ $faker = \Faker\Factory::create();
+ $note = $faker->text;
+ $response = $this->json(
+ 'POST',
+ '/api/post',
+ [
+ 'type' => ['h-entry'],
+ 'properties' => [
+ 'content' => [$note],
+ ],
+ ],
+ ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
+ );
+ $response
+ ->assertStatus(201)
+ ->assertJson(['response' => 'created']);
+ }
+
+ /**
+ * Test a micropub requests using JSON syntax without a token returns an
+ * error. Also check the message.
+ *
+ * @return void
+ */
+ public function test_micropub_request_with_json_syntax_without_token_returns_error()
+ {
+ $faker = \Faker\Factory::create();
+ $note = $faker->text;
+ $response = $this->json(
+ 'POST',
+ '/api/post',
+ [
+ 'type' => ['h-entry'],
+ 'properties' => [
+ 'content' => [$note],
+ ],
+ ]
+ );
+ $response
+ ->assertJson([
+ 'response' => 'error',
+ 'error' => 'no_token'
+ ])
+ ->assertStatus(400);
+ }
+
+ /**
+ * Test a micropub requests using JSON syntax without a valis token returns
+ * an error. Also check the message.
+ *
+ * @return void
+ */
+ public function test_micropub_request_with_json_syntax_with_invalid_token_returns_error()
+ {
+ $faker = \Faker\Factory::create();
+ $note = $faker->text;
+ $response = $this->json(
+ 'POST',
+ '/api/post',
+ [
+ 'type' => ['h-entry'],
+ 'properties' => [
+ 'content' => [$note],
+ ],
+ ],
+ ['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()]
+ );
+ $response
+ ->assertJson([
+ 'response' => 'error',
+ 'error' => 'invalid_token'
+ ])
+ ->assertStatus(400);
+ }
+
+ public function test_micropub_request_with_json_syntax_creates_new_place()
+ {
+ $faker = \Faker\Factory::create();
+ $response = $this->json(
+ 'POST',
+ '/api/post',
+ [
+ 'type' => ['h-card'],
+ 'properties' => [
+ 'name' => $faker->name,
+ 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude
+ ],
+ ],
+ ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
+ );
+ $response
+ ->assertJson(['response' => 'created'])
+ ->assertStatus(201);
+ }
+
+ public function test_micropub_request_with_json_syntax_and_uncertainty_parameter_creates_new_place()
+ {
+ $faker = \Faker\Factory::create();
+ $response = $this->json(
+ 'POST',
+ '/api/post',
+ [
+ 'type' => ['h-card'],
+ 'properties' => [
+ 'name' => $faker->name,
+ 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude . ';u=35'
+ ],
+ ],
+ ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
+ );
+ $response
+ ->assertJson(['response' => 'created'])
+ ->assertStatus(201);
+ }
+
+ /**
+ * Generate a valid token to be used in the tests.
+ *
+ * @return Lcobucci\JWT\Token\Plain $token
+ */
+ private function getToken()
+ {
+ $signer = new Sha256();
+ $token = (new Builder())
+ ->set('client_id', 'https://quill.p3k.io')
+ ->set('me', 'https://jonnybarnes.localhost')
+ ->set('scope', 'post')
+ ->set('issued_at', time())
+ ->sign($signer, env('APP_KEY'))
+ ->getToken();
+
+ return $token;
+ }
+
+ /**
+ * Generate an invalid token to be used in the tests.
+ *
+ * @return Lcobucci\JWT\Token\Plain $token
+ */
+ private function getInvalidToken()
+ {
+ $signer = new Sha256();
+ $token = (new Builder())
+ ->set('client_id', 'https://quill.p3k.io')
+ ->set('me', 'https://jonnybarnes.localhost')
+ ->set('scope', 'view') //error here
+ ->set('issued_at', time())
+ ->sign($signer, env('APP_KEY'))
+ ->getToken();
+
+ return $token;
+ }
+}
diff --git a/tests/Feature/NotesControllerTest.php b/tests/Feature/NotesControllerTest.php
new file mode 100644
index 00000000..df1518bb
--- /dev/null
+++ b/tests/Feature/NotesControllerTest.php
@@ -0,0 +1,57 @@
+get('/notes');
+ $response->assertStatus(200);
+ }
+
+ /**
+ * Test a specific note.
+ *
+ * @return void
+ */
+ public function test_specific_note()
+ {
+ $response = $this->get('/notes/D');
+ $response->assertViewHas('note');
+ }
+
+ /**
+ * Test that `/note/{decID}` redirects to `/notes/{nb60id}`.
+ *
+ * @return void
+ */
+ public function test_dec_id_redirect()
+ {
+ $response = $this->get('/note/11');
+ $response->assertRedirect(config('app.url') . '/notes/B');
+ }
+
+ /**
+ * Visit the tagged page and check the tag view data.
+ *
+ * @return void
+ */
+ public function test_tagged_notes_page()
+ {
+ $response = $this->get('/notes/tagged/beer');
+ $response->assertViewHas('tag', 'beer');
+ }
+}
diff --git a/tests/Feature/PlacesTest.php b/tests/Feature/PlacesTest.php
new file mode 100644
index 00000000..fca57e0f
--- /dev/null
+++ b/tests/Feature/PlacesTest.php
@@ -0,0 +1,34 @@
+get('/places');
+ $response->assertStatus(200);
+ }
+
+ /**
+ * Test a specific place.
+ *
+ * @return void
+ */
+ public function test_single_place()
+ {
+ $place = \App\Place::where('slug', 'the-bridgewater-pub')->first();
+ $response = $this->get('/places/the-bridgewater-pub');
+ $response->assertViewHas('place', $place);
+ }
+}
diff --git a/tests/TokenServiceTest.php b/tests/Feature/TokenServiceTest.php
similarity index 62%
rename from tests/TokenServiceTest.php
rename to tests/Feature/TokenServiceTest.php
index 8ca43fd7..01ac54d0 100644
--- a/tests/TokenServiceTest.php
+++ b/tests/Feature/TokenServiceTest.php
@@ -1,38 +1,30 @@
appurl = config('app.url');
- $this->tokenService = new \App\Services\TokenService();
- }
-
/**
* Given the token is dependent on a random nonce, the time of creation and
* the APP_KEY, to test, we shall create a token, and then verify it.
*
* @return void
*/
- public function testTokenCreationAndValidation()
+ public function test_token_creation_and_validation()
{
+ $tokenService = new \App\Services\TokenService();
$data = [
'me' => 'https://example.org',
'client_id' => 'https://quill.p3k.io',
'scope' => 'post'
];
- $token = $this->tokenService->getNewToken($data);
- $valid = $this->tokenService->validateToken($token);
+ $token = $tokenService->getNewToken($data);
+ $valid = $tokenService->validateToken($token);
$validData = [
'me' => $valid->getClaim('me'),
'client_id' => $valid->getClaim('client_id'),
diff --git a/tests/Feature/WebMentionsControllerTest.php b/tests/Feature/WebMentionsControllerTest.php
new file mode 100644
index 00000000..163eb4ce
--- /dev/null
+++ b/tests/Feature/WebMentionsControllerTest.php
@@ -0,0 +1,84 @@
+call('POST', '/webmention', ['source' => 'https://example.org/post/123']);
+ $response->assertStatus(400);
+ }
+
+ /**
+ * Test invalid target gets a 400 response.
+ *
+ * @return void
+ */
+ public function test_invalid_target_returns_400_response()
+ {
+ $response = $this->call('POST', '/webmention', [
+ 'source' => 'https://example.org/post/123',
+ 'target' => config('app.url') . '/invalid/target'
+ ]);
+ $response->assertStatus(400);
+ }
+
+ /**
+ * Test blog target gets a 501 response due to me not supporting it.
+ *
+ * @return void
+ */
+ public function test_blog_target_returns_501_response()
+ {
+ $response = $this->call('POST', '/webmention', [
+ 'source' => 'https://example.org/post/123',
+ 'target' => config('app.url') . '/blog/target'
+ ]);
+ $response->assertStatus(501);
+ }
+
+ /**
+ * Test that a non-existant note gives a 400 response.
+ *
+ * @return void
+ */
+ public function test_nonexistant_note_returns_400_response()
+ {
+ $response = $this->call('POST', '/webmention', [
+ 'source' => 'https://example.org/post/123',
+ 'target' => config('app.url') . '/notes/ZZZZZ'
+ ]);
+ $response->assertStatus(400);
+ }
+
+ /**
+ * Test a legit webmention triggers the ProcessWebMention job.
+ *
+ * @return void
+ */
+ public function test_legitimate_webmnetion_triggers_processwebmention_job()
+ {
+ Queue::fake();
+
+ $response = $this->call('POST', '/webmention', [
+ 'source' => 'https://example.org/post/123',
+ 'target' => config('app.url') . '/notes/B'
+ ]);
+ $response->assertStatus(202);
+
+ Queue::assertPushed(ProcessWebMention::class);
+ }
+}
diff --git a/tests/IndieAuthTest.php b/tests/IndieAuthTest.php
deleted file mode 100644
index 97fdadf7..00000000
--- a/tests/IndieAuthTest.php
+++ /dev/null
@@ -1,79 +0,0 @@
-appurl = config('app.url');
- }
-
- /**
- * Test the getAuthorizationEndpoint calls the correct service methods,
- * though these methods are actually mocked.
- *
- * @return void
- */
- public function testIndieAuthServiceDiscoversEndpoint()
- {
- $service = new \App\Services\IndieAuthService();
- $client = new \IndieAuth\Client();
- $result = $service->getAuthorizationEndpoint($this->appurl, $client);
- $this->assertSame('https://indieauth.com/auth', $result);
- }
-
- /**
- * Test that the Service build the correct redirect URL.
- *
- * @return void
- */
- public function testIndieAuthServiceBuildRedirectURL()
- {
- $client = new \IndieAuth\Client();
- $service = new \App\Services\IndieAuthService();
- $result = $service->buildAuthorizationURL(
- 'https://indieauth.com/auth',
- $this->appurl,
- $client
- );
- $this->assertSame(
- 'https://indieauth.com/auth?me=',
- substr($result, 0, 30)
- );
- }
-
- /**
- * Test the `start` method redirects to the client on error.
- *
- * @return void
- */
- public function testIndieAuthControllerBeginAuthRedirectsToClientOnFail()
- {
- $response = $this->call('GET', $this->appurl . '/indieauth/start', ['me' => 'http://example.org']);
- $this->assertSame($this->appurl . '/micropub/create', $response->headers->get('Location'));
- }
-
- /**
- * Now we test the `start` method as a whole.
- *
- * @return void
- */
- public function testIndieAuthControllerBeginAuthRedirectsToEndpoint()
- {
- $response = $this->call('GET', $this->appurl . '/indieauth/start', ['me' => $this->appurl]);
- $this->assertSame(
- 'https://indieauth.com/auth?me=',
- substr($response->headers->get('Location'), 0, 30)
- );
- $response = null;
- }
-}
diff --git a/tests/MicropubClientTest.php b/tests/MicropubClientTest.php
deleted file mode 100644
index 67299155..00000000
--- a/tests/MicropubClientTest.php
+++ /dev/null
@@ -1,69 +0,0 @@
-appurl = config('app.url');
- }
-
- /**
- * Test the client gets shown for an unauthorised request.
- *
- * @return void
- */
- public function testClientPageUnauthorised()
- {
- $this->visit($this->appurl . '/micropub/create')
- ->see('IndieAuth');
- }
-
- public function testClientPageRecentAuth()
- {
- $this->visit($this->appurl . '/micropub/create')
- ->see($this->appurl);
- }
-
- public function testClientCreatesNewNoteWithTag()
- {
- //in this test, the syndication targets are blank
- $faker = \Faker\Factory::create();
- $note = 'Fake note from #PHPUnit: ' . $faker->text;
- $this->visit($this->appurl . '/micropub/create')
- ->type($note, 'content')
- ->press('Submit');
- $this->seeInDatabase('notes', ['note' => $note]);
- $this->visit($this->appurl . '/notes/tagged/PHPUnit')
- ->see('PHPUnit');
- //my client has made a request to my endpoint, which then adds
- //to the db, so database transaction don’t work
- //so lets manually delete the new entry
- //first, if we are using algolia, we need to delete it
- if (env('SCOUT_DRIVER') == 'algolia') {
- //we need to allow the index to update in order to query it
- sleep(2);
- $client = new \AlgoliaSearch\Client(env('ALGOLIA_APP_ID'), env('ALGOLIA_SECRET'));
- $index = $client->initIndex('notes');
- //here we query for the new note and tell algolia too delete it
- $res = $index->deleteByQuery('Fake note from');
- if ($res == 0) {
- //somehow the new not didn’t get deleted
- $this->fail('Didn’t delete the note from the index');
- }
- }
- $newNote = \App\Note::where('note', $note)->first();
- $newNote->forceDelete();
- }
-}
diff --git a/tests/MicropubTest.php b/tests/MicropubTest.php
deleted file mode 100644
index de9e3f7f..00000000
--- a/tests/MicropubTest.php
+++ /dev/null
@@ -1,271 +0,0 @@
-appurl = config('app.url');
- }
-
- public function testMicropubRequestWithoutToken()
- {
- $this->call('GET', $this->appurl . '/api/post');
- $this->assertResponseStatus(400);
- $this->seeJson(['error_description' => 'No token provided with request']);
- }
-
- public function testMicropubRequestWithoutValidToken()
- {
- $this->call('GET', $this->appurl . '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer abc123']);
- $this->assertResponseStatus(400);
- $this->seeJson(['error_description' => 'The provided token did not pass validation']);
- }
-
- public function testMicropubRequestWithValidToken()
- {
- $this->call('GET', $this->appurl . '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
- $this->seeJson(['response' => 'token']);
- }
-
- public function testMicropubRequestForSyndication()
- {
- $this->call('GET', $this->appurl . '/api/post', ['q' => 'syndicate-to'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
- $this->seeJson(['uid' => 'https://twitter.com/jonnybarnes']);
- }
-
- public function testMicropubRequestForNearbyPlacesThatExist()
- {
- $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:53.5,-2.38'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
- $this->see('the-bridgewater-pub');
- }
-
- public function testMicropubRequestForNearbyPlacesThatExistWithUncertaintyParameter()
- {
- $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:53.5,-2.38;u=35'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
- $this->see('the-bridgewater-pub');
- }
-
- public function testMicropubRequestForNearbyPlacesThatDoNotExist()
- {
- $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:1.23,4.56'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
- $this->see('[]');
- }
-
- public function testMicropubRequestForConfig()
- {
- $this->call('GET', $this->appurl . '/api/post', ['q' => 'config'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]);
- $this->seeJson(['uid' => 'https://twitter.com/jonnybarnes']);
- }
-
- public function testMicropubRequestCreateNewNote()
- {
- $faker = \Faker\Factory::create();
- $note = $faker->text;
- $this->call(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'h' => 'entry',
- 'content' => $note
- ],
- [],
- [],
- ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
- );
- $this->seeInDatabase('notes', ['note' => $note]);
- }
-
- public function testMicropubRequestCreateNewPlace()
- {
- $this->call(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'h' => 'card',
- 'name' => 'The Barton Arms',
- 'geo' => 'geo:53.4974,-2.3768'
- ],
- [],
- [],
- ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
- );
- $this->seeInDatabase('places', ['slug' => 'the-barton-arms']);
- }
-
- public function testMicropubJSONRequestCreateNewNote()
- {
- $faker = \Faker\Factory::create();
- $note = $faker->text;
- $this->json(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'type' => ['h-entry'],
- 'properties' => [
- 'content' => [$note],
- ],
- ],
- ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
- )->seeJson([
- 'response' => 'created'
- ])->assertResponseStatus(201);
- }
-
- public function testMicropubJSONRequestCreateNewNoteWithoutToken()
- {
- $faker = \Faker\Factory::create();
- $note = $faker->text;
- $this->json(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'type' => ['h-entry'],
- 'properties' => [
- 'content' => [$note],
- ],
- ]
- )->seeJson([
- 'response' => 'error',
- 'error' => 'no_token'
- ])->assertResponseStatus(400);
- }
-
- public function testMicropubJSONRequestCreateNewNoteWithInvalidToken()
- {
- $faker = \Faker\Factory::create();
- $note = $faker->text;
- $this->json(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'type' => ['h-entry'],
- 'properties' => [
- 'content' => [$note],
- ],
- ],
- ['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()]
- )->seeJson([
- 'response' => 'error',
- 'error' => 'invalid_token'
- ]);
- }
-
- public function testMicropubJSONRequestCreateNewPlace()
- {
- $faker = \Faker\Factory::create();
- $this->json(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'type' => ['h-card'],
- 'properties' => [
- 'name' => $faker->name,
- 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude
- ],
- ],
- ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
- )->seeJson([
- 'response' => 'created'
- ])->assertResponseStatus(201);
- }
-
- public function testMicropubJSONRequestCreateNewPlaceWithoutToken()
- {
- $faker = \Faker\Factory::create();
- $this->json(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'type' => ['h-entry'],
- 'properties' => [
- 'name' => $faker->name,
- 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude
- ],
- ]
- )->seeJson([
- 'response' => 'error',
- 'error' => 'no_token'
- ])->assertResponseStatus(400);
- }
-
- public function testMicropubJSONRequestCreateNewPlaceWithInvalidToken()
- {
- $faker = \Faker\Factory::create();
- $this->json(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'type' => ['h-entry'],
- 'properties' => [
- 'name' => $faker->name,
- 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude
- ],
- ],
- ['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()]
- )->seeJson([
- 'response' => 'error',
- 'error' => 'invalid_token'
- ]);
- }
-
- public function testMicropubJSONRequestCreateNewPlaceWithUncertaintyParam()
- {
- $faker = \Faker\Factory::create();
- $this->json(
- 'POST',
- $this->appurl . '/api/post',
- [
- 'type' => ['h-card'],
- 'properties' => [
- 'name' => $faker->name,
- 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude . ';u=35'
- ],
- ],
- ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]
- )->seeJson([
- 'response' => 'created'
- ])->assertResponseStatus(201);
- }
-
- private function getToken()
- {
- $signer = new Sha256();
- $token = (new Builder())
- ->set('client_id', 'https://quill.p3k.io')
- ->set('me', 'https://jonnybarnes.localhost')
- ->set('scope', 'post')
- ->set('issued_at', time())
- ->sign($signer, env('APP_KEY'))
- ->getToken();
-
- return $token;
- }
-
- private function getInvalidToken()
- {
- $signer = new Sha256();
- $token = (new Builder())
- ->set('client_id', 'https://quill.p3k.io')
- ->set('me', 'https://jonnybarnes.localhost')
- ->set('scope', 'view')
- ->set('issued_at', time())
- ->sign($signer, env('APP_KEY'))
- ->getToken();
-
- return $token;
- }
-}
diff --git a/tests/NotesAdminTest.php b/tests/NotesAdminTest.php
deleted file mode 100644
index be476736..00000000
--- a/tests/NotesAdminTest.php
+++ /dev/null
@@ -1,33 +0,0 @@
-appurl = config('app.url');
- $this->notesAdminController = new \App\Http\Controllers\NotesAdminController();
- }
-
- public function testCreatedNoteDispatchesSendWebmentionsJob()
- {
- $this->expectsJobs(\App\Jobs\SendWebMentions::class);
-
- $this->withSession(['loggedin' => true])
- ->visit($this->appurl . '/admin/note/new')
- ->type('Mentioning', 'content')
- ->press('Submit');
- }
-}
diff --git a/tests/NotesTest.php b/tests/NotesTest.php
deleted file mode 100644
index 8112b32f..00000000
--- a/tests/NotesTest.php
+++ /dev/null
@@ -1,182 +0,0 @@
-appurl = config('app.url');
- $this->notesController = new \App\Http\Controllers\NotesController();
- }
-
- /**
- * Test the `/notes` page returns 200, this should
- * mean the database is being hit.
- *
- * @return void
- */
- public function testNotesPage()
- {
- $this->visit($this->appurl . '/notes')
- ->assertResponseOk();
- }
-
- /**
- * Test a specific note so that `singleNote()` get called.
- *
- * @return void
- */
- public function testSpecificNote()
- {
- $this->visit($this->appurl . '/notes/B')
- ->see('#beer');
- }
-
- /**
- * Test that `/note/{decID}` redirects to `/notes/{nb60id}`.
- *
- * @return void
- */
- public function testDecIDRedirect()
- {
- $this->get($this->appurl . '/note/11')
- ->assertRedirectedTo(config('app.url') . '/notes/B');
- }
-
- /**
- * Visit the tagged page and see text from the note.
- *
- * @return void
- */
- public function testTaggedNotesPage()
- {
- $this->visit($this->appurl . '/notes/tagged/beer')
- ->see('at the local.');
- }
-
- /**
- * Look for a default image in the contact’s h-card.
- *
- * @return void
- */
- public function testDefaultImageUsed()
- {
- $this->visit($this->appurl . '/notes/C')
- ->see('

');
- }
-
- /**
- * Look for a specific profile image in the contact’s h-card.
- *
- * @return void
- */
- public function testProfileImageUsed()
- {
- $this->visit($this->appurl . '/notes/D')
- ->see('

');
- }
-
- /**
- * Look for twitter URL when there’s no associated contact.
- *
- * @return void
- */
- public function testTwitterLinkCreatedWhenNoContactFound()
- {
- $this->visit($this->appurl . '/notes/E')
- ->see('
@bob');
- }
-
- /**
- * Test hashtag linking.
- *
- * @return void
- */
- public function testHashtags()
- {
- $this->visit($this->appurl . '/notes/B')
- ->see('
#beer');
- }
-
- /**
- * Look for the client name after the note.
- *
- * @return void
- */
- public function testClientNameDisplayed()
- {
- $this->visit($this->appurl . '/notes/D')
- ->see('JBL5');
- }
-
- /**
- * Look for the client URL after the note.
- *
- * @return void
- */
- public function testClientURLDisplayed()
- {
- $this->visit($this->appurl . '/notes/E')
- ->see('quill.p3k.io');
- }
-
- /**
- * Test a correct profile link is formed from a generic URL.
- *
- * @return void
- */
- public function testCreatePhotoLinkWithNonCachedImage()
- {
- $homepage = 'https://example.org/profile.png';
- $expected = 'https://example.org/profile.png';
- $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage));
- }
-
- /**
- * Test a correct profile link is formed from a generic URL.
- *
- * @return void
- */
- public function testCreatePhotoLinkWithCachedImage()
- {
- $homepage = 'https://aaronparecki.com/profile.png';
- $expected = '/assets/profile-images/aaronparecki.com/image';
- $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage));
- }
-
- /**
- * Test a correct profile link is formed from a twitter URL.
- *
- * @return void
- */
- public function testCreatePhotoLinkWithTwimgProfileImageURL()
- {
- $twitterProfileImage = 'http://pbs.twimg.com/1234';
- $expected = 'https://pbs.twimg.com/1234';
- $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterProfileImage));
- }
-
- /**
- * Test `null` is returned for a twitter profile.
- *
- * @return void
- */
- public function testCreatePhotoLinkWithCachedTwitterURL()
- {
- $twitterURL = 'https://twitter.com/example';
- $expected = 'https://pbs.twimg.com/static_profile_link.jpg';
- Cache::put($twitterURL, $expected, 1);
- $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterURL));
- }
-}
diff --git a/tests/PlacesTest.php b/tests/PlacesTest.php
deleted file mode 100644
index 7af4c6ac..00000000
--- a/tests/PlacesTest.php
+++ /dev/null
@@ -1,52 +0,0 @@
-appurl = config('app.url');
- }
-
- /**
- * Test the `/places` page for OK response.
- *
- * @return void
- */
- public function testPlacesPage()
- {
- $this->visit($this->appurl . '/places')
- ->assertResponseOK();
- }
-
- /**
- * Test a specific place.
- *
- * @return void
- */
- public function testSinglePlace()
- {
- $this->visit($this->appurl . '/places/the-bridgewater-pub')
- ->see('The Bridgewater Pub');
- }
-
- /**
- * Test the nearby method returns a collection.
- *
- * @return void
- */
- public function testNearbyMethod()
- {
- $nearby = \App\Place::near(53.5, -2.38, 1000);
- $this->assertEquals('the-bridgewater-pub', $nearby[0]->slug);
- }
-}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 8208edca..2932d4a6 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -1,25 +1,10 @@
make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
-
- return $app;
- }
+ use CreatesApplication;
}
diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php
new file mode 100644
index 00000000..5663bb49
--- /dev/null
+++ b/tests/Unit/ExampleTest.php
@@ -0,0 +1,20 @@
+assertTrue(true);
+ }
+}
diff --git a/tests/Unit/IndieAuthServiceTest.php b/tests/Unit/IndieAuthServiceTest.php
new file mode 100644
index 00000000..3035d8f5
--- /dev/null
+++ b/tests/Unit/IndieAuthServiceTest.php
@@ -0,0 +1,69 @@
+getAuthorizationEndpoint(config('app.url'), $client);
+ $this->assertEquals('https://indieauth.com/auth', $result);
+ }
+
+ /**
+ * Test that the Service build the correct redirect URL.
+ *
+ * @return void
+ */
+ public function test_indieauthservice_builds_correct_redirect_url()
+ {
+ $service = new \App\Services\IndieAuthService();
+ $client = new \IndieAuth\Client();
+ $result = $service->buildAuthorizationURL(
+ 'https://indieauth.com/auth',
+ config('app.url'),
+ $client
+ );
+ $this->assertEquals(
+ 'https://indieauth.com/auth?me=',
+ substr($result, 0, 30)
+ );
+ }
+
+ /**
+ * Test the getTokenEndpoint method.
+ *
+ * @return void
+ */
+ public function test_indieauthservice_gettokenendpoint_method()
+ {
+ $service = new \App\Services\IndieAuthService();
+ $client = new \IndieAuth\Client();
+ $result = $service->getTokenEndpoint(config('app.url'), $client);
+ $this->assertEquals(config('app.url') . '/api/token', $result);
+ }
+
+ /**
+ * Test the discoverMicropubEndpoint method.
+ *
+ * @return void
+ */
+ public function test_indieauthservice_discovermicropubendpoint_method()
+ {
+ $service = new \App\Services\IndieAuthService();
+ $client = new \IndieAuth\Client();
+ $result = $service->discoverMicropubEndpoint(config('app.url'), $client);
+ $this->assertEquals(config('app.url') . '/api/post', $result);
+ }
+}
diff --git a/tests/Unit/NotesControllerTest.php b/tests/Unit/NotesControllerTest.php
new file mode 100644
index 00000000..2e654f5e
--- /dev/null
+++ b/tests/Unit/NotesControllerTest.php
@@ -0,0 +1,71 @@
+notesController = new NotesController();
+ }
+
+ /**
+ * Test a correct profile link is formed from a generic URL.
+ *
+ * @return void
+ */
+ public function test_create_photo_link_with_non_cached_image()
+ {
+ $notesController = new \App\Http\Controllers\NotesController();
+ $homepage = 'https://example.org/profile.png';
+ $expected = 'https://example.org/profile.png';
+ $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage));
+ }
+
+ /**
+ * Test a correct profile link is formed from a generic URL (cached).
+ *
+ * @return void
+ */
+ public function test_create_photo_link_with_cached_image()
+ {
+ $notesController = new \App\Http\Controllers\NotesController();
+ $homepage = 'https://aaronparecki.com/profile.png';
+ $expected = '/assets/profile-images/aaronparecki.com/image';
+ $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage));
+ }
+
+ /**
+ * Test a correct profile link is formed from a twitter URL.
+ *
+ * @return void
+ */
+ public function test_create_photo_link_with_twimg_profile_image_url()
+ {
+ $notesController = new \App\Http\Controllers\NotesController();
+ $twitterProfileImage = 'http://pbs.twimg.com/1234';
+ $expected = 'https://pbs.twimg.com/1234';
+ $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterProfileImage));
+ }
+
+ /**
+ * Test `null` is returned for a twitter profile.
+ *
+ * @return void
+ */
+ public function test_create_photo_link_with_cached_twitter_url()
+ {
+ $twitterURL = 'https://twitter.com/example';
+ $expected = 'https://pbs.twimg.com/static_profile_link.jpg';
+ Cache::put($twitterURL, $expected, 1);
+ $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterURL));
+ }
+}
diff --git a/tests/Unit/NotesTest.php b/tests/Unit/NotesTest.php
new file mode 100644
index 00000000..eef40e72
--- /dev/null
+++ b/tests/Unit/NotesTest.php
@@ -0,0 +1,70 @@
+Having a
#beer at the local.
🍺' . PHP_EOL;
+ $note = Note::find(11);
+ $this->assertEquals($expected, $note->note);
+ }
+
+ /**
+ * Look for a default image in the contact’s h-card for the makeHCards method.
+ *
+ * @return void
+ */
+ public function test_default_image_used_in_makehcards_method()
+ {
+ $expected = '
Hi
+
+
+ Tantek Çelik
+
+
' . PHP_EOL;
+ $note = Note::find(12);
+ $this->assertEquals($expected, $note->note);
+ }
+
+ /**
+ * Look for a specific profile image in the contact’s h-card.
+ *
+ * @return void
+ */
+ public function test_specific_profile_image_used_in_makehcards_method()
+ {
+ $expected = '
Hi
+
+
+ Aaron Parecki
+
+
' . PHP_EOL;
+ $note = Note::find(13);
+ $this->assertEquals($expected, $note->note);
+ }
+
+ /**
+ * Look for twitter URL when there’s no associated contact.
+ *
+ * @return void
+ */
+ public function test_twitter_link_created_when_no_contact_found()
+ {
+ $expected = '
Hi @bob
' . PHP_EOL;
+ $note = Note::find(14);
+ $this->assertEquals($expected, $note->note);
+ }
+}
diff --git a/tests/Unit/PlacesTest.php b/tests/Unit/PlacesTest.php
new file mode 100644
index 00000000..0a981725
--- /dev/null
+++ b/tests/Unit/PlacesTest.php
@@ -0,0 +1,22 @@
+assertEquals('the-bridgewater-pub', $nearby[0]->slug);
+ }
+}
diff --git a/tests/WebMentionsTest.php b/tests/WebMentionsTest.php
deleted file mode 100644
index e296f96e..00000000
--- a/tests/WebMentionsTest.php
+++ /dev/null
@@ -1,92 +0,0 @@
-appurl = config('app.url');
- }
-
- /**
- * Test webmentions without source and target are rejected.
- *
- * @return void
- */
- public function testWebmentionsWithoutSourceAndTargetAreRejected()
- {
- $this->call('POST', $this->appurl . '/webmention', ['source' => 'https://example.org/post/123']);
- $this->assertResponseStatus(400)
- ->see('You need both the target and source parameters');
- }
-
- /**
- * Test invalid target gets a 400 response.
- *
- * @return void
- */
- public function testInvalidTargetReturns400Response()
- {
- $this->call('POST', $this->appurl . '/webmention', [
- 'source' => 'https://example.org/post/123',
- 'target' => $this->appurl . '/invalid/target'
- ]);
- $this->assertResponseStatus(400)
- ->see('Invalid request');
- }
-
- /**
- * Test blog target gets a 501 response.
- *
- * @return void
- */
- public function testBlogpostTargetReturns501Response()
- {
- $this->call('POST', $this->appurl . '/webmention', [
- 'source' => 'https://example.org/post/123',
- 'target' => $this->appurl . '/blog/target'
- ]);
- $this->assertResponseStatus(501)
- ->see('I don’t accept webmentions for blog posts yet.');
- }
-
- /**
- * Test that a non-existant note gives a 400 response.
- *
- * @return void
- */
- public function testNonexistantNoteReturns400Response()
- {
- $this->call('POST', $this->appurl . '/webmention', [
- 'source' => 'https://example.org/post/123',
- 'target' => $this->appurl . '/notes/ZZZZZ'
- ]);
- $this->assertResponseStatus(400)
- ->see('This note doesn’t exist.');
- }
-
- /**
- * Test a legit webmention triggers the ProcessWebMention job.
- *
- * @return void
- */
- public function testLegitimateWebmnetionTriggersProcessWebMentionJob()
- {
- $this->expectsJobs(\App\Jobs\ProcessWebMention::class);
- $this->call('POST', $this->appurl . '/webmention', [
- 'source' => 'https://example.org/post/123',
- 'target' => $this->appurl . '/notes/B'
- ]);
- $this->assertResponseStatus(202)
- ->see('Webmention received, it will be processed shortly');
- }
-}
diff --git a/travis/default-site.tpl.conf b/travis/default-site.tpl.conf
new file mode 100644
index 00000000..3e41e4e8
--- /dev/null
+++ b/travis/default-site.tpl.conf
@@ -0,0 +1,20 @@
+server {
+ listen 8000 default_server;
+ listen [::]:8000 default_server ipv6only=on;
+
+ root {ROOT}/public;
+ index index.php;
+
+ access_log /tmp/access.log;
+ error_log /tmp/error.log;
+
+ location / {
+ # First attempt to serve request as file, then as directory, then fall back to index.php.
+ try_files $uri $uri/ /index.php$is_args$args;
+ }
+
+ location ~* "\.php(/|$)" {
+ include fastcgi.conf;
+ fastcgi_pass php;
+ }
+}
diff --git a/travis/fastcgi.tpl.conf b/travis/fastcgi.tpl.conf
new file mode 100644
index 00000000..28ccdeb6
--- /dev/null
+++ b/travis/fastcgi.tpl.conf
@@ -0,0 +1,39 @@
+fastcgi_param QUERY_STRING $query_string;
+fastcgi_param REQUEST_METHOD $request_method;
+fastcgi_param CONTENT_TYPE $content_type;
+fastcgi_param CONTENT_LENGTH $content_length;
+
+fastcgi_param SCRIPT_NAME $fastcgi_script_name;
+fastcgi_param REQUEST_URI $request_uri;
+fastcgi_param DOCUMENT_URI $document_uri;
+fastcgi_param DOCUMENT_ROOT $document_root;
+fastcgi_param SERVER_PROTOCOL $server_protocol;
+fastcgi_param HTTPS $https if_not_empty;
+
+fastcgi_param GATEWAY_INTERFACE CGI/1.1;
+fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
+
+fastcgi_param REMOTE_ADDR $remote_addr;
+fastcgi_param REMOTE_PORT $remote_port;
+fastcgi_param SERVER_ADDR $server_addr;
+fastcgi_param SERVER_PORT $server_port;
+fastcgi_param SERVER_NAME $server_name;
+
+# PHP only, required if PHP was built with --enable-force-cgi-redirect
+fastcgi_param REDIRECT_STATUS 200;
+
+fastcgi_split_path_info ^(.+\.php)(.*)$;
+fastcgi_param PATH_INFO $fastcgi_path_info;
+#fastcgi_index index.php;
+fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+
+# fastcgi_intercept_errors on;
+fastcgi_ignore_client_abort off;
+fastcgi_connect_timeout 60;
+fastcgi_send_timeout 1800;
+fastcgi_read_timeout 1800;
+fastcgi_buffer_size 128k;
+fastcgi_buffers 4 256k;
+fastcgi_busy_buffers_size 256k;
+fastcgi_temp_file_write_size 256k;
+fastcgi_keep_conn on;
diff --git a/travis/install-nginx.sh b/travis/install-nginx.sh
new file mode 100755
index 00000000..d74b44a8
--- /dev/null
+++ b/travis/install-nginx.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+set -e
+set -x
+
+DIR=$(realpath $(dirname "$0"))
+USER=$(whoami)
+PHP_VERSION=$(phpenv version-name)
+ROOT=$(realpath "$DIR/..")
+PORT=9000
+SERVER="/tmp/php.sock"
+
+function tpl {
+ sed \
+ -e "s|{DIR}|$DIR|g" \
+ -e "s|{USER}|$USER|g" \
+ -e "s|{PHP_VERSION}|$PHP_VERSION|g" \
+ -e "s|{ROOT}|$ROOT|g" \
+ -e "s|{PORT}|$PORT|g" \
+ -e "s|{SERVER}|$SERVER|g" \
+ < $1 > $2
+}
+
+# Make some working directories.
+mkdir "$DIR/nginx"
+mkdir "$DIR/nginx/sites-enabled"
+mkdir "$DIR/var"
+
+PHP_FPM_BIN="$HOME/.phpenv/versions/$PHP_VERSION/sbin/php-fpm"
+PHP_FPM_CONF="$DIR/nginx/php-fpm.conf"
+
+# Build the php-fpm.conf.
+tpl "$DIR/php-fpm.tpl.conf" "$PHP_FPM_CONF"
+
+# Start php-fpm
+"$PHP_FPM_BIN" --fpm-config "$PHP_FPM_CONF"
+
+# Build the default nginx config files.
+tpl "$DIR/nginx.tpl.conf" "$DIR/nginx/nginx.conf"
+tpl "$DIR/fastcgi.tpl.conf" "$DIR/nginx/fastcgi.conf"
+tpl "$DIR/default-site.tpl.conf" "$DIR/nginx/sites-enabled/default-site.conf"
+
+# Start nginx.
+nginx -c "$DIR/nginx/nginx.conf"
diff --git a/travis/nginx.tpl.conf b/travis/nginx.tpl.conf
new file mode 100644
index 00000000..13a26ad4
--- /dev/null
+++ b/travis/nginx.tpl.conf
@@ -0,0 +1,52 @@
+error_log /tmp/error.log;
+pid /tmp/nginx.pid;
+worker_processes 1;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ # Set an array of temp and cache file options that will otherwise default to restricted locations accessible only to root.
+ client_body_temp_path /tmp/client_body;
+ fastcgi_temp_path /tmp/fastcgi_temp;
+ proxy_temp_path /tmp/proxy_temp;
+ scgi_temp_path /tmp/scgi_temp;
+ uwsgi_temp_path /tmp/uwsgi_temp;
+
+ ##
+ # Basic Settings
+ ##
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+ keepalive_timeout 65;
+ types_hash_max_size 2048;
+ # server_tokens off;
+ # server_names_hash_bucket_size 64;
+ # server_name_in_redirect off;
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ ##
+ # Logging Settings
+ ##
+ access_log /tmp/access.log;
+ error_log /tmp/error.log;
+
+ ##
+ # Gzip Settings
+ ##
+ gzip on;
+ gzip_disable "msie6";
+
+ ##
+ # Virtual Host Configs
+ ##
+ # include {DIR}/nginx/conf.d/*.conf;
+ include {DIR}/nginx/sites-enabled/*;
+
+ upstream php {
+ server 127.0.0.1:{PORT};
+ }
+}
diff --git a/travis/php-fpm.tpl.conf b/travis/php-fpm.tpl.conf
new file mode 100644
index 00000000..fdbf2aa8
--- /dev/null
+++ b/travis/php-fpm.tpl.conf
@@ -0,0 +1,9 @@
+[global]
+
+[travis]
+user = {USER}
+listen = {PORT}
+listen.mode = 0666
+pm = static
+pm.max_children = 5
+php_admin_value[memory_limit] = 32M