CluPilotCloud/tests/Feature/HostStepContractTest.php

86 lines
3.3 KiB
PHP

<?php
use App\Services\Deployment\UpdateChannel;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;
/**
* Der Wirt-Helfer ist root-eigen und wird von `install-agent.sh` gesetzt.
* Hebt ein Release seine Vertragsversion an, braucht JEDER Wirt einmal
* Handarbeit als root — bei zehn Servern zehnmal.
*
* Automatisieren lässt sich das nicht: `sudoers` gewährt dem Dienstbenutzer
* genau drei benannte Befehle, und etwas Root-Eigenes, das ungeprüft aus dem
* (vom Dienstbenutzer beschreibbaren) Checkout ausführt, gäbe jedem, der je
* an diesen Benutzer kommt, Root auf dem Wirt.
*
* Also wird gemeldet statt automatisiert — aber VOLLSTÄNDIG und BEVOR jemand
* einen Knopf drückt, der daran scheitert.
*/
afterEach(function () {
File::deleteDirectory(storage_path('app/deploy'));
});
it('keeps the needed contract version in exactly one place', function () {
// Sie stand zweimal im Repo: `HOST_STEP_NEEDS=3` in update.sh und eine
// hartkodierte 3 im Agenten. Zwei Zahlen, die zusammenpassen müssen,
// laufen irgendwann auseinander.
$lib = File::get(base_path('deploy/lib/release.sh'));
$update = File::get(base_path('deploy/update.sh'));
$agent = File::get(base_path('deploy/update-agent.sh'));
expect($lib)->toContain('release_host_step_needs')
->and($update)->toContain('release_host_step_needs')
->and($agent)->toContain('release_host_step_needs');
// Und keine nackte Zahl mehr an den beiden alten Stellen.
expect($update)->not->toContain('HOST_STEP_NEEDS=3')
->and($agent)->not->toContain('(( have < 3 ))');
});
it('answers the needed contract version from the shell', function () {
$result = Process::path(base_path())->timeout(30)->run(
'bash -c '.escapeshellarg('set -Eeuo pipefail; . deploy/lib/release.sh; release_host_step_needs')
);
expect($result->exitCode())->toBe(0)
->and(trim($result->output()))->toMatch('/^[0-9]+$/');
});
it('reports the helper as not ok when the host has an older one', function () {
File::ensureDirectoryExists(storage_path('app/deploy'));
File::put(storage_path('app/deploy/update-status.json'), json_encode([
'state' => 'idle',
'host_step_contract' => 2,
'host_step_needs' => 3,
]));
$state = app(UpdateChannel::class)->state();
expect($state['host_step_ok'])->toBeFalse()
->and($state['host_step_have'])->toBe(2)
->and($state['host_step_needs'])->toBe(3);
});
it('reports the helper as ok when it is current', function () {
File::ensureDirectoryExists(storage_path('app/deploy'));
File::put(storage_path('app/deploy/update-status.json'), json_encode([
'state' => 'idle',
'host_step_contract' => 3,
'host_step_needs' => 3,
]));
expect(app(UpdateChannel::class)->state()['host_step_ok'])->toBeTrue();
});
it('does not cry wolf when the agent has not reported yet', function () {
// Ein Wirt, dessen Agent die Zahlen noch nie gemeldet hat (alte Fassung,
// erster Lauf), darf nicht als kaputt dastehen. „Ich weiß es nicht" ist
// nicht dasselbe wie „zu alt".
File::ensureDirectoryExists(storage_path('app/deploy'));
File::put(storage_path('app/deploy/update-status.json'), json_encode(['state' => 'idle']));
expect(app(UpdateChannel::class)->state()['host_step_ok'])->toBeTrue();
});