diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 51a707915..186f85170 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -150,9 +150,10 @@ class AppServiceProvider extends ServiceProvider $db_type = config()->get('database.default'); if ($db_type == 'sqlite') { - $db_file = database_path(env('DB_DATABASE', 'app.sqlite')); + $db_file = config()->get('database.connections.sqlite.database'); Log::debug('SQLite Database Path: ' . $db_file); - if (! is_file($db_file)) { + // Do not create a file for the in-memory database identifier. + if ($db_file !== ':memory:' && ! is_file($db_file)) { touch($db_file); } } diff --git a/config/database.php b/config/database.php index 028c6bc86..5a21e148a 100644 --- a/config/database.php +++ b/config/database.php @@ -7,7 +7,11 @@ return [ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', - 'database' => database_path(env('DB_DATABASE', 'app.sqlite')), // Make sure to use the correct path + // Use the correct path, but let the special in-memory identifier + // pass through untouched so tests can run against ':memory:'. + 'database' => env('DB_DATABASE', 'app.sqlite') === ':memory:' + ? ':memory:' + : database_path(env('DB_DATABASE', 'app.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), // Enable foreign key constraints ], diff --git a/phpunit.xml b/phpunit.xml index 909feb8dc..348ed5931 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,8 +12,8 @@ - - + + diff --git a/tests/Feature/ItemExportTest.php b/tests/Feature/ItemExportTest.php index 51b9d0c33..58c251793 100644 --- a/tests/Feature/ItemExportTest.php +++ b/tests/Feature/ItemExportTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature; use App\Item; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\DB; use Tests\TestCase; class ItemExportTest extends TestCase @@ -39,6 +40,18 @@ class ItemExportTest extends TestCase public function test_exports_assigned_tag_titles_excluding_the_root_tag(): void { + // Mirror the root/default dashboard row that production seeds (id 0), + // so the item_tag pivot's foreign key to items.id is satisfied on a + // fresh in-memory database. + DB::table('items')->insert([ + 'id' => 0, + 'title' => 'app.dashboard', + 'url' => '', + 'type' => 1, + 'user_id' => 0, + 'pinned' => 0, + ]); + $item = Item::factory() ->create([ 'title' => 'Tagged Item', diff --git a/tests/TestCase.php b/tests/TestCase.php index fe1ffc2ff..98e540cd8 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,5 +6,42 @@ use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { - // + protected function setUp(): void + { + parent::setUp(); + + $this->guardAgainstRealDatabase(); + } + + /** + * Refuse to run the test suite against anything other than an + * in-memory SQLite database. + * + * The suite uses RefreshDatabase (migrate:fresh), which would wipe + * whatever database it is pointed at. The only supported test + * configuration for this repo is sqlite + ':memory:'. Any other + * connection (a real sqlite file, mysql, pgsql, ...) is rejected so + * we can never destroy real data. + */ + private function guardAgainstRealDatabase(): void + { + $default = config('database.default'); + $driver = config("database.connections.{$default}.driver"); + $database = config("database.connections.{$default}.database"); + + if ($driver === 'sqlite' && $database === ':memory:') { + return; + } + + throw new \RuntimeException(sprintf( + 'Refusing to run tests: the default database connection (%s) is ' + . 'driver "%s" pointing at "%s". Tests only run against an ' + . 'in-memory SQLite database (driver "sqlite", database ":memory:") ' + . 'to avoid wiping real data via RefreshDatabase. Check phpunit.xml ' + . 'DB_CONNECTION/DB_DATABASE overrides.', + $default, + $driver, + is_scalar($database) ? (string) $database : gettype($database) + )); + } }