diff --git a/.github/workflows/unit-backend-mysql.yml b/.github/workflows/unit-backend-mysql.yml new file mode 100644 index 00000000..2809b25e --- /dev/null +++ b/.github/workflows/unit-backend-mysql.yml @@ -0,0 +1,81 @@ +name: Backend Unit Tests - MySQL +on: + pull_request: + branches: + - master + paths-ignore: + - resources/assets/** + push: + branches: + - master + paths-ignore: + - resources/assets/** + workflow_dispatch: + branches: + - master + paths-ignore: + - resources/assets/** +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: [ 8.0 ] + mysql-version: [ 5.7, 8.0 ] + fail-fast: false + + services: + mysql: + image: mysql:${{ matrix.mysql-version }} + env: + MYSQL_DATABASE: koel + MYSQL_USER: mysql + MYSQL_PASSWORD: mysql + MYSQL_ROOT_PASSWORD: mysql + ports: + - 3306:3306 + options: >- + --health-cmd "mysqladmin ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + DB_CONNECTION: mysql-ci + DB_HOST: 127.0.1.1 + DB_PORT: 3306 + DB_DATABASE: koel + DB_USERNAME: mysql + DB_PASSWORD: mysql + + steps: + - uses: actions/checkout@v1 + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer:v2 + coverage: xdebug + extensions: pdo_sqlite, zip, gd + - name: Install PHP dependencies + uses: ramsey/composer-install@v2 + with: + composer-options: --prefer-dist + - name: Generate app key + run: php artisan key:generate --quiet + - name: Run code style checker + run: composer cs + - name: Run static analysis + run: composer analyze -- --no-progress + - name: Run tests + run: composer coverage + - name: Upload logs if broken + uses: actions/upload-artifact@v1 + if: failure() + with: + name: logs + path: storage/logs + - name: Upload coverage + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/config/database.php b/config/database.php index 365f4bc8..f3299869 100644 --- a/config/database.php +++ b/config/database.php @@ -86,6 +86,21 @@ return [ 'sslmode' => 'prefer', ], + 'mysql-ci' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', 'koel'), + 'username' => env('DB_USERNAME', 'mysql'), + 'password' => env('DB_PASSWORD', 'mysql'), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => false, + 'engine' => null, + ], + 'pgsql-ci' => [ 'driver' => 'pgsql', 'host' => env('DB_HOST', 'localhost'), diff --git a/database/migrations/2016_07_09_054503_fix_artist_autoindex_value.php b/database/migrations/2016_07_09_054503_fix_artist_autoindex_value.php index 80212fc0..2f1e90da 100644 --- a/database/migrations/2016_07_09_054503_fix_artist_autoindex_value.php +++ b/database/migrations/2016_07_09_054503_fix_artist_autoindex_value.php @@ -1,23 +1,11 @@ orderByDesc('id')->first(); - DB::statement('ALTER TABLE artists AUTO_INCREMENT=' . ($latestArtist->id + 1)); } public function down(): void diff --git a/database/migrations/2022_11_08_144634_add_last_played_at_into_interactions_table.php b/database/migrations/2022_11_08_144634_add_last_played_at_into_interactions_table.php index 54a6032c..ad5680d3 100644 --- a/database/migrations/2022_11_08_144634_add_last_played_at_into_interactions_table.php +++ b/database/migrations/2022_11_08_144634_add_last_played_at_into_interactions_table.php @@ -13,9 +13,9 @@ return new class extends Migration $table->timestamp('last_played_at')->nullable(); }); - DB::statement('UPDATE interactions SET last_played_at = updated_at'); + DB::unprepared('UPDATE interactions SET last_played_at = updated_at'); - DB::statement( + DB::unprepared( "UPDATE playlists SET rules = REPLACE(rules, 'interactions.updated_at', 'interactions.last_played_at')" ); } diff --git a/tests/Feature/AlbumCoverTest.php b/tests/Feature/AlbumCoverTest.php index cf8ddb7b..66a86c41 100644 --- a/tests/Feature/AlbumCoverTest.php +++ b/tests/Feature/AlbumCoverTest.php @@ -28,12 +28,12 @@ class AlbumCoverTest extends TestCase $user = User::factory()->admin()->create(); /** @var Album $album */ - $album = Album::factory()->create(['id' => 9999]); + $album = Album::factory()->create(); $this->mediaMetadataService ->shouldReceive('writeAlbumCover') ->once() - ->with(Mockery::on(static fn (Album $album) => $album->id === 9999), 'Foo', 'jpeg'); + ->with(Mockery::on(static fn (Album $target) => $target->is($album)), 'Foo', 'jpeg'); $this->putAs('api/album/' . $album->id . '/cover', ['cover' => 'data:image/jpeg;base64,Rm9v'], $user) ->assertOk(); diff --git a/tests/Feature/PlaylistSongTest.php b/tests/Feature/PlaylistSongTest.php index 4380b09b..3be9536e 100644 --- a/tests/Feature/PlaylistSongTest.php +++ b/tests/Feature/PlaylistSongTest.php @@ -49,7 +49,9 @@ class PlaylistSongTest extends TestCase $songs = Song::factory(2)->create(); $playlist->songs()->saveMany($songs); - $this->getAs("api/playlist/$playlist->id/songs", $playlist->user) - ->assertJson($songs->pluck('id')->all()); + $responseIds = $this->getAs("api/playlist/$playlist->id/songs", $playlist->user) + ->json(); + + self::assertEqualsCanonicalizing($responseIds, $songs->pluck('id')->all()); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 66dbb117..4b0fb9d3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,7 +23,6 @@ abstract class TestCase extends BaseTestCase { parent::setUp(); - $this->prepareForTests(); self::createSandbox(); } diff --git a/tests/Traits/CreatesApplication.php b/tests/Traits/CreatesApplication.php index a2b7a4e5..f1a0de7d 100644 --- a/tests/Traits/CreatesApplication.php +++ b/tests/Traits/CreatesApplication.php @@ -6,12 +6,14 @@ use App\Console\Kernel; use App\Models\User; use Illuminate\Contracts\Console\Kernel as Artisan; use Illuminate\Foundation\Application; +use Illuminate\Support\Facades\DB; trait CreatesApplication { protected string $mediaPath = __DIR__ . '/../songs'; private Kernel $artisan; protected string $baseUrl = 'http://localhost'; + public static bool $migrated = false; public function createApplication(): Application { @@ -23,15 +25,17 @@ trait CreatesApplication $this->artisan = $app->make(Artisan::class); $this->artisan->bootstrap(); + // Unless the DB is stored in memory, we need to migrate the DB only once for the whole test suite. + if (!CreatesApplication::$migrated || DB::connection()->getDatabaseName() === ':memory:') { + $this->artisan->call('migrate'); + + if (!User::query()->count()) { + $this->artisan->call('db:seed'); + } + + CreatesApplication::$migrated = true; + } + return $app; } - - private function prepareForTests(): void - { - $this->artisan->call('migrate'); - - if (!User::query()->count()) { - $this->artisan->call('db:seed'); - } - } }