#8: Implemented several tests for the API.

This commit is contained in:
Peter Deltchev 2015-12-24 18:08:49 -08:00
parent 4d119ff758
commit 7bd336ef55
22 changed files with 845 additions and 366 deletions

View file

@ -88,11 +88,12 @@ class UploadTrackCommand extends CommandBase
$track->save(); $track->save();
$track->ensureDirectoryExists(); $track->ensureDirectoryExists();
Storage::makeDirectory(Config::get('ponyfm.files_directory') . '/queued-tracks', 0755, false, true); if (!is_dir(Config::get('ponyfm.files_directory') . '/queued-tracks')) {
mkdir(Config::get('ponyfm.files_directory') . '/queued-tracks', 0755, true);
}
$trackFile = $trackFile->move(Config::get('ponyfm.files_directory').'/queued-tracks', $track->id); $trackFile = $trackFile->move(Config::get('ponyfm.files_directory').'/queued-tracks', $track->id);
$validator = \Validator::make(['track' => $trackFile], [ $validator = \Validator::make(['track' => $trackFile], [
'track' => 'track' =>
'required|' 'required|'

View file

@ -20,6 +20,8 @@
namespace Poniverse\Ponyfm\Providers; namespace Poniverse\Ponyfm\Providers;
use DB;
use Illuminate\Database\SQLiteConnection;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use PfmValidator; use PfmValidator;

View file

@ -20,7 +20,6 @@
namespace Poniverse\Ponyfm; namespace Poniverse\Ponyfm;
use Exception;
use Gravatar; use Gravatar;
use Illuminate\Auth\Authenticatable; use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Auth\Passwords\CanResetPassword;
@ -28,8 +27,7 @@ use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Support\Facades\Auth; use Auth;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Venturecraft\Revisionable\RevisionableTrait; use Venturecraft\Revisionable\RevisionableTrait;

View file

@ -19,7 +19,7 @@
"require-dev": { "require-dev": {
"fzaninotto/faker": "~1.4", "fzaninotto/faker": "~1.4",
"mockery/mockery": "0.9.*", "mockery/mockery": "0.9.*",
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "~4.1",
"phpspec/phpspec": "~2.1" "phpspec/phpspec": "~2.1"
}, },
"autoload": { "autoload": {

786
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -52,6 +52,12 @@ return [
'prefix' => '', 'prefix' => '',
], ],
'memory' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
'mysql' => [ 'mysql' => [
'driver' => 'mysql', 'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'), 'host' => env('DB_HOST', 'localhost'),

View file

@ -48,6 +48,11 @@ return [
'root' => storage_path('app'), 'root' => storage_path('app'),
], ],
'testing' => [
'driver' => 'local',
'root' => storage_path('app').'/test-files',
],
'ftp' => [ 'ftp' => [
'driver' => 'ftp', 'driver' => 'ftp',
'host' => 'ftp.example.com', 'host' => 'ftp.example.com',

View file

@ -31,9 +31,13 @@
$factory->define(Poniverse\Ponyfm\User::class, function ($faker) { $factory->define(Poniverse\Ponyfm\User::class, function ($faker) {
return [ return [
'name' => $faker->name, 'username' => $faker->userName,
'display_name' => $faker->userName,
'slug' => $faker->slug,
'email' => $faker->email, 'email' => $faker->email,
'password' => str_random(10), 'can_see_explicit_content' => true,
'remember_token' => str_random(10), 'bio' => $faker->paragraph,
'track_count' => 0,
'comment_count' => 0,
]; ];
}); });

View file

@ -19,12 +19,13 @@
*/ */
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateAlbums extends Migration class CreateAlbums extends Migration
{ {
public function up() public function up()
{ {
Schema::create('albums', function ($table) { Schema::create('albums', function (Blueprint $table) {
$table->increments('id'); $table->increments('id');
$table->integer('user_id')->unsigned(); $table->integer('user_id')->unsigned();
$table->string('title')->index(); $table->string('title')->index();
@ -55,9 +56,16 @@ class CreateAlbums extends Migration
public function down() public function down()
{ {
Schema::table('tracks', function ($table) { // These are separated to prevent weirdness with SQLite.
Schema::table('tracks', function (Blueprint $table) {
$table->dropForeign('tracks_album_id_foreign'); $table->dropForeign('tracks_album_id_foreign');
});
Schema::table('tracks', function (Blueprint $table) {
$table->dropColumn('album_id'); $table->dropColumn('album_id');
});
Schema::table('tracks', function (Blueprint $table) {
$table->dropColumn('track_number'); $table->dropColumn('track_number');
}); });

View file

@ -25,9 +25,10 @@ class CreateLatestColumn extends Migration
public function up() public function up()
{ {
Schema::table('tracks', function ($table) { Schema::table('tracks', function ($table) {
$table->boolean('is_latest')->notNullable()->indexed(); $table->boolean('is_latest')->notNullable()->default(false)->indexed();
}); });
if ('sqlite' !== DB::getDriverName()) {
DB::update(' DB::update('
UPDATE tracks t1 UPDATE tracks t1
INNER JOIN ( INNER JOIN (
@ -44,6 +45,7 @@ class CreateLatestColumn extends Migration
AND published_at IS NOT NULL AND published_at IS NOT NULL
'); ');
} }
}
public function down() public function down()
{ {

View file

@ -26,13 +26,18 @@ class CreateTrackHashes extends Migration
public function up() public function up()
{ {
Schema::table('tracks', function ($table) { Schema::table('tracks', function ($table) {
$table->string('hash', 32)->notNullable()->indexed(); $table->string('hash', 32)->nullable()->indexed();
}); });
foreach (Track::with('user')->get() as $track) { foreach (Track::with('user')->get() as $track) {
$track->updateHash(); $track->updateHash();
$track->save(); $track->save();
} }
Schema::table('tracks', function ($table) {
$table->string('hash', 32)->notNullable()->change();
});
} }
public function down() public function down()

View file

@ -25,14 +25,8 @@ class TrackIsListed extends Migration
public function up() public function up()
{ {
Schema::table('tracks', function ($table) { Schema::table('tracks', function ($table) {
$table->boolean('is_listed')->notNullable()->indexed(); $table->boolean('is_listed')->notNullable()->default(true)->indexed();
}); });
DB::update('
UPDATE
tracks
SET
is_listed = true');
} }
public function down() public function down()

View file

@ -30,7 +30,9 @@ class MakeEmailNullable extends Migration
*/ */
public function up() public function up()
{ {
DB::statement('ALTER TABLE `users` MODIFY `email` VARCHAR(150) NULL;'); Schema::table('users', function(Blueprint $table){
$table->string('email', 150)->nullable()->change();
});
} }
/** /**
@ -40,6 +42,8 @@ class MakeEmailNullable extends Migration
*/ */
public function down() public function down()
{ {
DB::statement('ALTER TABLE `users` MODIFY `email` VARCHAR(150) NOT NULL DEFAULT "";'); Schema::table('users', function (Blueprint $table) {
$table->string('email', 150)->notNullable()->default('')->change();
});
} }
} }

View file

@ -30,7 +30,9 @@ class AddNewIndices extends Migration
*/ */
public function up() public function up()
{ {
if ('sqlite' !== DB::getDriverName()) {
DB::statement('ALTER TABLE `show_songs` ADD FULLTEXT show_songs_title_fulltext (title)'); DB::statement('ALTER TABLE `show_songs` ADD FULLTEXT show_songs_title_fulltext (title)');
}
Schema::table('images', function ($table) { Schema::table('images', function ($table) {
$table->index('hash'); $table->index('hash');
@ -49,7 +51,9 @@ class AddNewIndices extends Migration
*/ */
public function down() public function down()
{ {
if ('sqlite' !== DB::getDriverName()) {
DB::statement('ALTER TABLE `show_songs` DROP INDEX show_songs_title_fulltext'); DB::statement('ALTER TABLE `show_songs` DROP INDEX show_songs_title_fulltext');
}
Schema::table('images', function ($table) { Schema::table('images', function ($table) {
$table->dropIndex('images_hash_index'); $table->dropIndex('images_hash_index');

View file

@ -45,9 +45,16 @@ class UpdateTrackFilesWithCache extends Migration
*/ */
public function down() public function down()
{ {
// These are separated to avoid a weird "no such index" error with SQLite.
Schema::table('track_files', function (Blueprint $table) { Schema::table('track_files', function (Blueprint $table) {
$table->dropColumn('is_cacheable'); $table->dropColumn('is_cacheable');
});
Schema::table('track_files', function (Blueprint $table) {
$table->dropColumn('expires_at'); $table->dropColumn('expires_at');
});
Schema::table('track_files', function (Blueprint $table) {
$table->dropColumn('is_in_progress'); $table->dropColumn('is_in_progress');
}); });
} }

View file

@ -37,10 +37,9 @@ class AddTrackSourceColumn extends Migration
// Mark MLPMA tracks retroactively // Mark MLPMA tracks retroactively
// --> The default value in the database, set above, will // --> The default value in the database, set above, will
// be used automatically for all non-MLPMA tracks. // be used automatically for all non-MLPMA tracks.
$tracks = DB::table('tracks') DB::table('tracks')
->join('mlpma_tracks', 'mlpma_tracks.track_id', '=', 'tracks.id'); ->where(DB::raw('id IN (SELECT `id` FROM `mlpma_tracks`)'))
->update(['source' => 'mlpma']);
$tracks->whereNotNull('mlpma_tracks.id')->update(['source' => 'mlpma']);
} }
/** /**

View file

@ -0,0 +1,63 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2015 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddSensibleDefaults extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('tracks', function(Blueprint $table){
$table->boolean('is_listed')->default(true)->change();
$table->boolean('is_explicit')->default(false)->change();
$table->boolean('is_vocal')->default(false)->change();
$table->boolean('is_downloadable')->default(false)->change();
$table->unsignedInteger('play_count')->default(0)->change();
$table->unsignedInteger('view_count')->default(0)->change();
$table->unsignedInteger('download_count')->default(0)->change();
$table->unsignedInteger('favourite_count')->default(0)->change();
$table->unsignedInteger('comment_count')->default(0)->change();
});
Schema::table('users', function(Blueprint $table){
$table->boolean('can_see_explicit_content')->default(false)->change();
$table->text('bio')->default('')->change();
$table->unsignedInteger('track_count')->default(0)->change();
$table->unsignedInteger('comment_count')->default(0)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// This migration is not reversible.
}
}

View file

@ -24,5 +24,8 @@
<env name="CACHE_DRIVER" value="array"/> <env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/> <env name="QUEUE_DRIVER" value="sync"/>
<env name="APP_URL" value="http://ponyfm-dev.poni"/>
<env name="DB_CONNECTION" value="memory"/>
<env name="PONYFM_DATASTORE" value="/vagrant/storage/app/testing-datastore"/>
</php> </php>
</phpunit> </phpunit>

59
tests/ApiAuthTest.php Normal file
View file

@ -0,0 +1,59 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2015 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Poniverse\Ponyfm\User;
class ApiAuthTest extends TestCase {
use DatabaseMigrations;
/**
* Ensures that when we call the Pony.fm API with a user who has never
* logged into Pony.fm before, a Pony.fm account is created for them using
* their Poniverse details.
*/
public function testApiCreatesNewUser() {
$user = factory(User::class)->make();
$accessTokenInfo = new \Poniverse\AccessTokenInfo('nonsense-token');
$accessTokenInfo->setIsActive(true);
$accessTokenInfo->setScopes(['basic', 'ponyfm-upload-track']);
$poniverse = Mockery::mock('overload:Poniverse');
$poniverse->shouldReceive('getUser')
->andReturn([
'username' => $user->username,
'display_name' => $user->display_name,
'email' => $user->email,
]);
$poniverse->shouldReceive('setAccessToken');
$poniverse
->shouldReceive('getAccessTokenInfo')
->andReturn($accessTokenInfo);
$this->dontSeeInDatabase('users', ['username' => $user->username]);
$this->post('/api/v1/tracks', ['access_token' => 'nonsense-token']);
$this->seeInDatabase('users', ['username' => $user->username]);
}
}

59
tests/ApiTest.php Normal file
View file

@ -0,0 +1,59 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2015 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Poniverse\Ponyfm\User;
class ApiTest extends TestCase {
use DatabaseMigrations;
use WithoutMiddleware;
public function testUploadWithoutFile() {
$user = factory(User::class)->create();
$this->actingAs($user)
->post('/api/v1/tracks', [])
->seeJsonEquals([
'errors' => [
'track' => ['You must upload an audio file!']
],
'message' => 'Validation failed'
]);
$this->assertResponseStatus(400);
}
public function testUploadWithFile() {
$this->expectsJobs(Poniverse\Ponyfm\Jobs\EncodeTrackFile::class);
$user = factory(User::class)->create();
$file = $this->getTestFileForUpload('ponyfm-test.flac');
$this->actingAs($user)
->call('POST', '/api/v1/tracks', [], [], ['track' => $file]);
$this->assertResponseStatus(202);
$this->seeJsonEquals([
'message' => "This track has been accepted for processing! Poll the status_url to know when it's ready to publish.",
'id' => 1,
'status_url' => "http://ponyfm-testing.poni/api/v1/tracks/1/upload-status"
]);
}
}

View file

@ -1,19 +0,0 @@
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}

View file

@ -1,5 +1,23 @@
<?php <?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2015 Peter Deltchev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class TestCase extends Illuminate\Foundation\Testing\TestCase class TestCase extends Illuminate\Foundation\Testing\TestCase
{ {
/** /**
@ -7,7 +25,9 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
* *
* @var string * @var string
*/ */
protected $baseUrl = 'http://localhost'; protected $baseUrl = 'http://ponyfm-testing.poni';
protected static $initializedFiles = false;
/** /**
* Creates the application. * Creates the application.
@ -22,4 +42,77 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
return $app; return $app;
} }
/**
* @before
*/
public function initializeTestFiles() {
// Ensure we have the Pony.fm test files
if (!static::$initializedFiles) {
Storage::disk('local')->makeDirectory('test-files');
$storage = Storage::disk('testing');
// To add new test files, upload them to poniverse.net/files
// and add them here with their last-modified date as a Unix
// timestamp.
$files = [
'ponyfm-test.flac' => 1450965707
];
foreach ($files as $filename => $lastModifiedTimestamp) {
if (
!$storage->has($filename) ||
$storage->lastModified($filename) < $lastModifiedTimestamp
) {
echo "Downloading test file: ${filename}...".PHP_EOL;
$testFileUrl = "https://poniverse.net/files/ponyfm-test-files/${filename}";
$data = \Httpful\Request::getQuick($testFileUrl);
if ($data->code === 200) {
$storage->put(
$filename,
$data->body
);
} else {
$this->fail("A necessary test file was unavailable: ${testFileUrl}");
}
}
}
// Delete any unnecessary test files
foreach ($storage->allFiles() as $filename) {
if (!isset($files[$filename])) {
$storage->delete($filename);
}
}
static::$initializedFiles = true;
}
}
public function tearDown() {
Storage::disk('local')->deleteDirectory('testing-datastore');
parent::tearDown();
}
/**
* Returns an object for testing file uploads using the given test file.
* In a test, to "attach" a file to the `track` field, call the following:
*
* $this->call('POST', '/api/v1/tracks', [], [], ['track' => $file]);
* // then, deal with the response
*
* Adapted from: http://laravel.io/forum/03-09-2014-unit-test-progressive-unit-test-for-uploaded-files-with-validation?page=1#reply-27008
*
* @param $filename
* @return \Symfony\Component\HttpFoundation\File\UploadedFile
*/
public function getTestFileForUpload($filename) {
Storage::disk('local')->makeDirectory('testing-datastore/tmp');
Storage::disk('local')->copy("test-files/${filename}", "testing-datastore/tmp/${filename}");
return new \Symfony\Component\HttpFoundation\File\UploadedFile(storage_path("app/testing-datastore/tmp/${filename}"), $filename, null, null, null, true);
}
} }