chore(tests): hard-isolate the test DB so it can never hit the live file
Context: on 2026-10-21 the dev SQLite DB was wiped because an early revision of Phase 1 tests called RefreshDatabase before the per-test config() swap had been applied, so migrate:fresh ran against the real database file. Restored from data/backups/db-20261020-033001.sqlite (24 users, 72 videos, 32 comments recovered); anything created between Oct 20 03:30 and Oct 21 08:25 needs manual rebuild from NAS. Two layers of defense added so this cannot recur: 1. .env.testing — forces DB_CONNECTION=sqlite and DB_DATABASE=:memory: at the earliest bootstrap stage, before any trait setUp hook runs. Also carries the APP_KEY and testing-safe defaults for mail/queue /session. 2. TestCase::assertSafeTestDatabase() — a belt-and-braces guard that aborts the whole phpunit run (exit code 2) if the resolved default DB target is anything other than :memory: or a /testing/ or /tmp/ path. The abort happens BEFORE any query can execute. Running the suite with a bad DB target now prints a stderr banner and stops instantly; the dev file is safe even if someone writes a new test that forgets to override the connection. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
fa4e09ad62
commit
d9f97a672e
2 files changed
+57
No files matched your search
@@ -0,0 +1,22 @@
|
|||||||
|
APP_ENV=testing
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost
|
||||||
|
APP_KEY=base64:Zm7kR6vDfsQQmCeWTR2Mk2WQGu6fGP1UHEcZVk/hIt0=
|
||||||
|
|
||||||
|
# ── Hard rule — tests must never touch the shared SQLite file. ───────────
|
||||||
|
# phpunit.xml also sets these via <env>, but having them here means Laravel
|
||||||
|
# picks them up during the earliest bootstrap step (before any trait or
|
||||||
|
# setUp hook can run), closing the window that let migrate:fresh wipe the
|
||||||
|
# dev DB on 2026-10-21. If you change these values, read tests/TestCase.php's
|
||||||
|
# assertSafeTestDatabase() guard first.
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
DB_DATABASE=:memory:
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=4
|
||||||
|
CACHE_DRIVER=array
|
||||||
|
MAIL_MAILER=array
|
||||||
|
QUEUE_CONNECTION=sync
|
||||||
|
SESSION_DRIVER=array
|
||||||
|
|
||||||
|
TELESCOPE_ENABLED=false
|
||||||
|
PULSE_ENABLED=false
|
||||||
@@ -7,4 +7,39 @@ use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
|||||||
abstract class TestCase extends BaseTestCase
|
abstract class TestCase extends BaseTestCase
|
||||||
{
|
{
|
||||||
use CreatesApplication;
|
use CreatesApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Belt-and-braces guard against a repeat of the 2026-10-21 incident, where
|
||||||
|
* a test with the RefreshDatabase trait ran migrate:fresh against the
|
||||||
|
* shared dev SQLite file and wiped every row.
|
||||||
|
*
|
||||||
|
* By the time setUp() runs we know the final DB config. If it points at
|
||||||
|
* anything other than an in-memory SQLite (or a file inside the test
|
||||||
|
* directory), we abort the whole run before any migration can execute.
|
||||||
|
*/
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->assertSafeTestDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertSafeTestDatabase(): void
|
||||||
|
{
|
||||||
|
$driver = config('database.default');
|
||||||
|
$database = config("database.connections.{$driver}.database");
|
||||||
|
|
||||||
|
$allowed = $database === ':memory:'
|
||||||
|
|| $database === null
|
||||||
|
|| str_contains((string) $database, '/testing')
|
||||||
|
|| str_contains((string) $database, '/tmp/')
|
||||||
|
|| str_ends_with((string) $database, '.testing.sqlite');
|
||||||
|
|
||||||
|
if (! $allowed) {
|
||||||
|
// Fatal, not an assertion failure — a wrong DB target has to stop
|
||||||
|
// execution before Laravel can run even one query against it.
|
||||||
|
fwrite(STDERR, "\n\n*** ABORTING TESTS: unsafe DB target '{$database}' on connection '{$driver}'. ***\n");
|
||||||
|
fwrite(STDERR, "Set DB_CONNECTION=sqlite and DB_DATABASE=:memory: in .env.testing (or in your test's setUp before parent::setUp).\n\n");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user