From e18033ec1ae181a8977d23d14090d0706f3cc05b Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Mon, 26 Sep 2022 21:25:32 +0100
Subject: [PATCH 1/2] Added initial support for parallel testing

---
 app/Providers/AppServiceProvider.php |  7 +++++++
 composer.json                        |  1 +
 resources/js/wysiwyg/config.js       |  1 +
 tests/TestCase.php                   |  8 ++++++++
 tests/ThemeTest.php                  | 12 ++++++------
 5 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 3c1212e32..02c545db2 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -17,7 +17,9 @@ use GuzzleHttp\Client;
 use Illuminate\Contracts\Cache\Repository;
 use Illuminate\Database\Eloquent\Relations\Relation;
 use Illuminate\Pagination\Paginator;
+use Illuminate\Support\Facades\Artisan;
 use Illuminate\Support\Facades\Blade;
+use Illuminate\Support\Facades\ParallelTesting;
 use Illuminate\Support\Facades\Schema;
 use Illuminate\Support\Facades\URL;
 use Illuminate\Support\Facades\View;
@@ -64,6 +66,11 @@ class AppServiceProvider extends ServiceProvider
 
         // Set paginator to use bootstrap-style pagination
         Paginator::useBootstrap();
+
+        // Setup database upon parallel testing database creation
+        ParallelTesting::setUpTestDatabase(function ($database, $token) {
+            Artisan::call('db:seed --class=DummyContentSeeder');
+        });
     }
 
     /**
diff --git a/composer.json b/composer.json
index 64630833d..44bbf2b99 100644
--- a/composer.json
+++ b/composer.json
@@ -44,6 +44,7 @@
         "ssddanbrown/htmldiff": "^1.0.2"
     },
     "require-dev": {
+        "brianium/paratest": "^6.6",
         "fakerphp/faker": "^1.16",
         "itsgoingd/clockwork": "^5.1",
         "mockery/mockery": "^1.4",
diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js
index 52c52592c..d2f813cfb 100644
--- a/resources/js/wysiwyg/config.js
+++ b/resources/js/wysiwyg/config.js
@@ -252,6 +252,7 @@ export function build(options) {
         document_base_url: window.baseUrl('/'),
         end_container_on_empty_block: true,
         remove_trailing_brs: false,
+        keep_styles: false,
         statusbar: false,
         menubar: false,
         paste_data_images: false,
diff --git a/tests/TestCase.php b/tests/TestCase.php
index f17d27a1a..0926b0dcc 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -26,6 +26,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
 use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Env;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Testing\Assert as PHPUnit;
 use Monolog\Handler\TestHandler;
@@ -299,6 +300,8 @@ abstract class TestCase extends BaseTestCase
     /**
      * Run a set test with the given env variable.
      * Remembers the original and resets the value after test.
+     * Database config is juggled so the value can be restored when
+     * parallel testing are used, where multiple databases exist.
      */
     protected function runWithEnv(string $name, $value, callable $callback)
     {
@@ -311,7 +314,12 @@ abstract class TestCase extends BaseTestCase
             $_SERVER[$name] = $value;
         }
 
+        $database = config('database.connections.mysql_testing.database');
         $this->refreshApplication();
+
+        DB::purge();
+        config()->set('database.connections.mysql_testing.database', $database);
+
         $callback();
 
         if (is_null($originalVal)) {
diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php
index e83758a95..ac4b35de2 100644
--- a/tests/ThemeTest.php
+++ b/tests/ThemeTest.php
@@ -322,8 +322,8 @@ class ThemeTest extends TestCase
 
     public function test_export_body_start_and_end_template_files_can_be_used()
     {
-        $bodyStartStr = 'barry-fought-against-the-panther';
-        $bodyEndStr = 'barry-lost-his-fight-with-grace';
+        $bodyStartStr = 'garry-fought-against-the-panther';
+        $bodyEndStr = 'garry-lost-his-fight-with-grace';
         /** @var Page $page */
         $page = Page::query()->first();
 
@@ -342,18 +342,18 @@ class ThemeTest extends TestCase
     protected function usingThemeFolder(callable $callback)
     {
         // Create a folder and configure a theme
-        $themeFolderName = 'testing_theme_' . rtrim(base64_encode(time()), '=');
+        $themeFolderName = 'testing_theme_' . str_shuffle(rtrim(base64_encode(time()), '='));
         config()->set('view.theme', $themeFolderName);
         $themeFolderPath = theme_path('');
+
+        // Create theme folder and clean it up on application tear-down
         File::makeDirectory($themeFolderPath);
+        $this->beforeApplicationDestroyed(fn() => File::deleteDirectory($themeFolderPath));
 
         // Run provided callback with theme env option set
         $this->runWithEnv('APP_THEME', $themeFolderName, function () use ($callback, $themeFolderName) {
             call_user_func($callback, $themeFolderName);
         });
-
-        // Cleanup the custom theme folder we created
-        File::deleteDirectory($themeFolderPath);
     }
 }
 

From f21669c0c966f3dadeac2024a382b8a7cd831a8a Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Tue, 27 Sep 2022 01:27:51 +0100
Subject: [PATCH 2/2] Cleaned testing service provider usage

Moved testing content out of AppServiceProvider, to a testing-specific
service provider. Updated docs and added composer commands to support
parallel testing.
Also reverted unintentional change to wysiwyg/config.js.
---
 app/Providers/AppServiceProvider.php |  7 -------
 composer.json                        |  2 ++
 readme.md                            |  9 ++-------
 resources/js/wysiwyg/config.js       |  1 -
 tests/TestCase.php                   | 16 ++++++++++++++++
 tests/TestServiceProvider.php        | 26 ++++++++++++++++++++++++++
 6 files changed, 46 insertions(+), 15 deletions(-)
 create mode 100644 tests/TestServiceProvider.php

diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 02c545db2..3c1212e32 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -17,9 +17,7 @@ use GuzzleHttp\Client;
 use Illuminate\Contracts\Cache\Repository;
 use Illuminate\Database\Eloquent\Relations\Relation;
 use Illuminate\Pagination\Paginator;
-use Illuminate\Support\Facades\Artisan;
 use Illuminate\Support\Facades\Blade;
-use Illuminate\Support\Facades\ParallelTesting;
 use Illuminate\Support\Facades\Schema;
 use Illuminate\Support\Facades\URL;
 use Illuminate\Support\Facades\View;
@@ -66,11 +64,6 @@ class AppServiceProvider extends ServiceProvider
 
         // Set paginator to use bootstrap-style pagination
         Paginator::useBootstrap();
-
-        // Setup database upon parallel testing database creation
-        ParallelTesting::setUpTestDatabase(function ($database, $token) {
-            Artisan::call('db:seed --class=DummyContentSeeder');
-        });
     }
 
     /**
diff --git a/composer.json b/composer.json
index 44bbf2b99..81896f8f8 100644
--- a/composer.json
+++ b/composer.json
@@ -74,6 +74,8 @@
         "format": "phpcbf",
         "lint": "phpcs",
         "test": "phpunit",
+        "t": "@php artisan test --parallel",
+        "t-reset": "@php artisan test --recreate-databases",
         "post-autoload-dump": [
             "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
             "@php artisan package:discover --ansi"
diff --git a/readme.md b/readme.md
index d0ae1b4f6..16992341d 100644
--- a/readme.md
+++ b/readme.md
@@ -108,14 +108,9 @@ npm run dev
 
 BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the database name, user name and password all defined as `bookstack-test`. You will have to create that database and that set of credentials before testing.
 
-The testing database will also need migrating and seeding beforehand. This can be done with the following commands:
+The testing database will also need migrating and seeding beforehand. This can be done by running `composer refresh-test-database`.
 
-``` bash
-php artisan migrate --database=mysql_testing
-php artisan db:seed --class=DummyContentSeeder --database=mysql_testing
-```
-
-Once done you can run `composer test` in the application root directory to run all tests.
+Once done you can run `composer test` in the application root directory to run all tests. Tests can be ran in parallel by running them via `composer t`. This will use Laravel's built-in parallel testing functionality, and attempt to create and seed a database instance for each testing thread. If required these parallel testing instances can be reset, before testing again, by running `composer t-reset`.
 
 ### 📜 Code Standards
 
diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js
index d2f813cfb..52c52592c 100644
--- a/resources/js/wysiwyg/config.js
+++ b/resources/js/wysiwyg/config.js
@@ -252,7 +252,6 @@ export function build(options) {
         document_base_url: window.baseUrl('/'),
         end_container_on_empty_block: true,
         remove_trailing_brs: false,
-        keep_styles: false,
         statusbar: false,
         menubar: false,
         paste_data_images: false,
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 0926b0dcc..594194168 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -22,6 +22,7 @@ use GuzzleHttp\Client;
 use GuzzleHttp\Handler\MockHandler;
 use GuzzleHttp\HandlerStack;
 use GuzzleHttp\Middleware;
+use Illuminate\Contracts\Console\Kernel;
 use Illuminate\Foundation\Testing\DatabaseTransactions;
 use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
 use Illuminate\Http\JsonResponse;
@@ -48,6 +49,21 @@ abstract class TestCase extends BaseTestCase
      */
     protected string $baseUrl = 'http://localhost';
 
+    /**
+     * Creates the application.
+     *
+     * @return \Illuminate\Foundation\Application
+     */
+    public function createApplication()
+    {
+        /** @var \Illuminate\Foundation\Application  $app */
+        $app = require __DIR__ . '/../bootstrap/app.php';
+        $app->register(TestServiceProvider::class);
+        $app->make(Kernel::class)->bootstrap();
+
+        return $app;
+    }
+
     /**
      * Set the current user context to be an admin.
      */
diff --git a/tests/TestServiceProvider.php b/tests/TestServiceProvider.php
new file mode 100644
index 000000000..9ad48c442
--- /dev/null
+++ b/tests/TestServiceProvider.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Tests;
+
+use Illuminate\Support\Facades\Artisan;
+use Illuminate\Support\Facades\ParallelTesting;
+use Illuminate\Support\ServiceProvider;
+
+class TestServiceProvider extends ServiceProvider
+{
+    /**
+     * Bootstrap services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        // Tell Laravel's parallel testing functionality to seed the test
+        // databases with the DummyContentSeeder upon creation.
+        // This is only done for initial database creation. Seeding
+        // won't occur on every run.
+        ParallelTesting::setUpTestDatabase(function ($database, $token) {
+            Artisan::call('db:seed --class=DummyContentSeeder');
+        });
+    }
+}