envPath = sys_get_temp_dir().'/clupilot-env-'.bin2hex(random_bytes(6)); $this->app->instance(EnvFileEditor::class, new EnvFileEditor($this->envPath)); }); afterEach(function () { foreach (glob($this->envPath.'*') ?: [] as $file) { @unlink($file); } }); it('trägt die fehlenden Hostnamen nach', function () { file_put_contents($this->envPath, "APP_URL=https://app.example.test\nADMIN_HOSTS=admin.example.test\n"); $this->artisan('clupilot:bind-hosts', [ '--site' => 'www.example.test,example.test', '--status' => 'status.example.test', '--files' => 'files.example.test', '--force' => true, ])->assertSuccessful(); $written = file_get_contents($this->envPath); // APP_HOST kommt aus dem Host von APP_URL — danach muss niemand gefragt // werden, die Antwort steht schon in der Datei. expect($written)->toContain('APP_HOST=app.example.test') ->and($written)->toContain('SITE_HOST=www.example.test,example.test') ->and($written)->toContain('STATUS_HOST=status.example.test') ->and($written)->toContain('FILES_HOST=files.example.test') // Und nichts Vorhandenes geht verloren. ->and($written)->toContain('APP_URL=https://app.example.test') ->and($written)->toContain('ADMIN_HOSTS=admin.example.test'); }); it('überschreibt niemals einen Wert, den jemand von Hand gesetzt hat', function () { file_put_contents( $this->envPath, "APP_URL=https://app.example.test\nAPP_HOST=eigener.example.test\nSITE_HOST=\n", ); $this->artisan('clupilot:bind-hosts', [ '--site' => 'www.example.test', '--force' => true, ])->assertSuccessful(); $written = file_get_contents($this->envPath); // Gesetzt bleibt gesetzt … expect($written)->toContain('APP_HOST=eigener.example.test') ->and($written)->not->toContain('APP_HOST=app.example.test') // … und leer gilt als fehlend, denn genau so liefert .env.example aus. ->and($written)->toContain('SITE_HOST=www.example.test'); }); it('sichert die alte Datei, bevor es schreibt', function () { file_put_contents($this->envPath, "APP_URL=https://app.example.test\n"); $this->artisan('clupilot:bind-hosts', ['--force' => true])->assertSuccessful(); // EnvFileEditor legt eine Kopie mit Zeitstempel daneben. Diese Datei hält // jedes Geheimnis der Installation; ein Schreibfehler darf sie nicht // ersatzlos ersetzen. expect(glob($this->envPath.'.bak-*'))->not->toBeEmpty(); }); it('tut ohne Bestätigung nichts', function () { file_put_contents($this->envPath, "APP_URL=https://app.example.test\n"); $this->artisan('clupilot:bind-hosts', ['--site' => 'www.example.test']) ->expectsConfirmation( 'Diese Namen jetzt binden?', 'no', ) ->assertFailed(); expect(file_get_contents($this->envPath))->not->toContain('SITE_HOST'); }); it('schreibt bei --dry-run nichts, sagt aber was geschähe', function () { file_put_contents($this->envPath, "APP_URL=https://app.example.test\n"); $this->artisan('clupilot:bind-hosts', ['--site' => 'www.example.test', '--dry-run' => true]) ->expectsOutputToContain('SITE_HOST=www.example.test') ->assertSuccessful(); expect(file_get_contents($this->envPath))->not->toContain('SITE_HOST') ->and(glob($this->envPath.'.bak-*'))->toBeEmpty(); }); it('sagt es, wenn schon alles steht, statt eine Sicherung anzulegen', function () { file_put_contents($this->envPath, implode("\n", [ 'APP_URL=https://app.example.test', 'APP_HOST=app.example.test', 'SITE_HOST=www.example.test', 'STATUS_HOST=status.example.test', 'FILES_HOST=files.example.test', '', ])); $this->artisan('clupilot:bind-hosts', ['--force' => true]) ->expectsOutputToContain('Nichts nachzutragen') ->assertSuccessful(); expect(glob($this->envPath.'.bak-*'))->toBeEmpty(); }); it('schreibt einen Wert wörtlich, auch wenn er wie ein Rückverweis aussieht', function () { // SITE_HOST steht schon leer da — genau das, was .env.example wirklich // ausliefert, und der Pfad, auf dem apply() eine vorhandene Zeile per // preg_replace ersetzt statt sie nur anzuhängen. file_put_contents($this->envPath, "APP_URL=https://app.example.test\nSITE_HOST=\n"); // `$1` und `\1` sind das, was preg_replace im ERSATZ als Rückverweis // deutet und still verschluckt. Diese Datei hält jedes Geheimnis der // Installation — was hier ankommt, muss ankommen wie eingegeben. $this->artisan('clupilot:bind-hosts', [ '--site' => 'www.example.test$1,foo\1bar', '--force' => true, ])->assertSuccessful(); expect(file_get_contents($this->envPath)) ->toContain('SITE_HOST=www.example.test$1,foo\1bar'); });