'mysql', 'host' => 'db.example.test', 'port' => 3306, 'database' => 'pressekonto', 'username' => 'app', 'password' => 'geheim', ], $overrides); $command = new OnlineSchemaChange; $command->setLaravel(app()); // Ohne --execute gilt der Trockenlauf; die Option wird hier gesetzt, // weil buildArguments() sie liest. (fn () => $this->input = new ArrayInput( ['--execute' => true], new InputDefinition([ new InputOption('execute', null, InputOption::VALUE_NONE), new InputOption('drop-old-table', null, InputOption::VALUE_NONE), ]), ))->call($command); return $command->buildArguments($config, 'press_releases', 'ADD COLUMN gone_at TIMESTAMP NULL', '/tmp/creds.cnf'); } test('the generated call carries the flags that keep the table writable', function () { $arguments = ptOscArguments(); expect($arguments) ->toContain('--alter=ADD COLUMN gone_at TIMESTAMP NULL') // 17 Fremdschlüssel zeigen auf die beiden großen Tabellen. Das // schnellere `drop_swap` wäre kurzzeitig inkonsistent. ->toContain('--alter-foreign-keys-method=rebuild_constraints') // pt-osc darf den laufenden Betrieb nicht ausbremsen. ->toContain('--set-vars=lock_wait_timeout=60,innodb_lock_wait_timeout=1') ->toContain('--max-load=Threads_running=40') ->toContain('--critical-load=Threads_running=100') ->toContain('--execute') ->not->toContain('--dry-run'); }); test('the credentials never appear on the command line', function () { $arguments = ptOscArguments(); $line = implode(' ', $arguments); // Über `ps` ist die Kommandozeile für jeden Benutzer des Servers lesbar. expect($line) ->not->toContain('geheim') ->not->toContain('app') ->toContain('F=/tmp/creds.cnf'); }); test('the connection details land in the dsn', function () { $arguments = ptOscArguments(); expect(end($arguments)) ->toContain('h=db.example.test') ->toContain('P=3306') ->toContain('D=pressekonto') ->toContain('t=press_releases'); }); test('the old table is kept unless dropping it is requested', function () { expect(ptOscArguments())->toContain('--no-drop-old-table'); }); test('a non mysql connection is refused instead of pretending to work', function () { /** @var TestCase $this */ Process::fake(); // Die Testsuite läuft auf SQLite — dort richtet pt-osc nichts aus. $this->artisan('db:online-alter', ['table' => 'users', 'alter' => 'ADD COLUMN foo INT NULL']) ->expectsOutputToContain('kein MySQL') ->assertFailed(); Process::assertNothingRan(); }); test('an unreachable database reports plainly instead of throwing', function () { /** @var TestCase $this */ Process::fake(); config()->set('database.connections.fake_mysql', [ 'driver' => 'mysql', 'host' => 'db.example.invalid', 'database' => 'pressekonto', 'username' => 'app', 'password' => 'geheim', ]); $this->artisan('db:online-alter', [ 'table' => 'press_releases', 'alter' => 'ADD COLUMN foo INT NULL', '--connection' => 'fake_mysql', ]) ->expectsOutputToContain('nicht erreichbar') ->assertFailed(); Process::assertNothingRan(); });