119 lines
4.7 KiB
PHP
119 lines
4.7 KiB
PHP
<?php // tests/Feature/Provisioning/ConfigureInstanceMailTest.php
|
|
|
|
use App\Models\Host;
|
|
use App\Models\Instance;
|
|
use App\Models\Mailbox;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\Steps\Customer\ConfigureInstanceMail;
|
|
use App\Services\Proxmox\FakeProxmoxClient;
|
|
use App\Services\Proxmox\ProxmoxClient;
|
|
use App\Support\Settings;
|
|
|
|
function laufMitInstanz(Instance $instance): ProvisioningRun
|
|
{
|
|
return ProvisioningRun::factory()->create([
|
|
'pipeline' => 'instance-mail',
|
|
'context' => ['instance_id' => $instance->id, 'node' => 'pve', 'vmid' => 201],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Eine Instanz, die tatsaechlich auf einem Host steht.
|
|
*
|
|
* Der Schritt ruft ProxmoxClient::forHost($instance->host) auf — genau wie
|
|
* ApplyStorageQuota, dessen Testaufbau (ApplyStorageQuotasTest::unquotedInstance())
|
|
* aus demselben Grund einen Host anlegt. Eine Instanz ohne host_id gibt es in
|
|
* diesem Bestand fuer eine Maschine, die tatsaechlich Gastbefehle bekommt,
|
|
* nicht — FakeProxmoxClient::forHost() verlangt ein echtes Host-Objekt, weil
|
|
* der reale Client genauso wenig ohne einen Host wuesste, wohin er soll.
|
|
*/
|
|
function instanzAufHost(): Instance
|
|
{
|
|
return Instance::factory()->create(['host_id' => Host::factory()]);
|
|
}
|
|
|
|
function versandbereitFuerSchritt(): void
|
|
{
|
|
Settings::set('mail.host', 'mail.clupilot.cloud');
|
|
Settings::set('mail.port', 587);
|
|
Settings::set('mail.encryption', 'tls');
|
|
Mailbox::factory()->create([
|
|
'key' => 'instance-relay', 'address' => 'noreply@clupilot.cloud',
|
|
'username' => 'noreply@clupilot.cloud', 'password' => 'geheim',
|
|
'active' => true, 'authenticates' => true,
|
|
]);
|
|
}
|
|
|
|
it('traegt jeden Wert einzeln in den Gast', function () {
|
|
versandbereitFuerSchritt();
|
|
$pve = new FakeProxmoxClient;
|
|
app()->instance(ProxmoxClient::class, $pve);
|
|
$instance = instanzAufHost();
|
|
|
|
app(ConfigureInstanceMail::class)->execute(laufMitInstanz($instance));
|
|
|
|
$befehle = implode("\n", $pve->guestCommands);
|
|
|
|
expect($befehle)->toContain('config:system:set mail_smtphost --value=')
|
|
->and($befehle)->toContain('mail.clupilot.cloud')
|
|
->and($befehle)->toContain('config:system:set mail_domain --value=')
|
|
->and($befehle)->toContain('clupilot.cloud');
|
|
});
|
|
|
|
it('maskiert das Passwort, sodass es keinen zweiten Befehl starten kann', function () {
|
|
// Verstecken laesst sich der Wert auf dieser Maschine nicht (siehe
|
|
// Kopfkommentar des Schrittes). Was sehr wohl gilt und geprueft gehoert:
|
|
// er darf aus seiner Klammerung nicht ausbrechen. Der Befehl laeuft als
|
|
// root auf einer Kundenmaschine — ein Semikolon im Passwort waere dort
|
|
// ein zweiter Befehl.
|
|
Settings::set('mail.host', 'mail.clupilot.cloud');
|
|
Settings::set('mail.port', 587);
|
|
Mailbox::factory()->create([
|
|
'key' => 'instance-relay', 'address' => 'noreply@clupilot.cloud',
|
|
'username' => 'noreply@clupilot.cloud',
|
|
'password' => "boes'; touch /tmp/PWNED; echo '",
|
|
'active' => true, 'authenticates' => true,
|
|
]);
|
|
|
|
$pve = new FakeProxmoxClient;
|
|
app()->instance(ProxmoxClient::class, $pve);
|
|
|
|
app(ConfigureInstanceMail::class)->execute(laufMitInstanz(instanzAufHost()));
|
|
|
|
$passwortbefehl = collect($pve->guestCommands)
|
|
->first(fn ($b) => str_contains($b, 'mail_smtppassword'));
|
|
|
|
// Der ganze Wert steht in EINEM maskierten Argument: das Semikolon darf
|
|
// nicht ausserhalb der Anfuehrungszeichen stehen.
|
|
expect($passwortbefehl)->toContain(escapeshellarg("boes'; touch /tmp/PWNED; echo '"));
|
|
});
|
|
|
|
it('schreibt GAR NICHTS, wenn der Mailserver fehlt', function () {
|
|
Settings::set('mail.host', '');
|
|
$pve = new FakeProxmoxClient;
|
|
app()->instance(ProxmoxClient::class, $pve);
|
|
|
|
$ergebnis = app(ConfigureInstanceMail::class)->execute(laufMitInstanz(Instance::factory()->create()));
|
|
|
|
expect($pve->guestCommands)->toBe([])
|
|
->and($ergebnis->type)->toBe(\App\Provisioning\StepResult::FAIL)
|
|
->and($ergebnis->reason)->toBe('no_server');
|
|
});
|
|
|
|
it('schreibt beim zweiten Lauf erneut, statt sich mit einem Merker zu sperren', function () {
|
|
versandbereitFuerSchritt();
|
|
$pve = new FakeProxmoxClient;
|
|
app()->instance(ProxmoxClient::class, $pve);
|
|
$instance = instanzAufHost();
|
|
|
|
app(ConfigureInstanceMail::class)->execute(laufMitInstanz($instance));
|
|
$ersteRunde = count($pve->guestCommands);
|
|
app(ConfigureInstanceMail::class)->execute(laufMitInstanz($instance));
|
|
|
|
// config:system:set ist von sich aus wiederholbar — derselbe Wert zweimal
|
|
// geschrieben ist derselbe Wert. Der Schritt darf deshalb ohne Merker
|
|
// erneut laufen; das ist bei einer Nachruestung ueber den Bestand der
|
|
// Normalfall, nicht die Ausnahme.
|
|
expect(count($pve->guestCommands))->toBe($ersteRunde * 2);
|
|
});
|