CluPilotCloud/tests/Feature/ReleaseCeilingAgentTest.php

124 lines
4.6 KiB
PHP

<?php
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;
/**
* Die Decke, gefahren vom echten Agenten.
*
* Nicht nachgerechnet: die Klemmung sitzt zwischen `git fetch` und dem
* Schreiben der Statusdatei, und genau dieses Zusammenspiel ist die Frage.
* `git` liegt als Attrappe im PATH, deren `fetch` sofort gelingt, ohne eine
* Gegenstelle zu brauchen — alle uebrigen git-Aufrufe gehen ans echte git im
* Wegwerf-Repo.
*/
function runAgentWithCeiling(?string $ceiling, array $tags, string $deployedVersion = '1.7.3'): array
{
// Ein WEGWERF-Checkout mit EIGENEM .git — niemals das Repository dieses
// Arbeitsbaums.
//
// Tags liegen im gemeinsamen .git und sind damit auch fuer den Hauptbaum
// und jede Parallelsitzung sichtbar. Ein hier angelegtes v9.9.9 wuerde
// `git tag -l 'v*' --sort=-v:refname | head -1` falsch beantworten — und
// genau diese Frage entscheidet, wohin ein Server aktualisiert. Stirbt der
// Test vor seinem Aufraeumen, bliebe es liegen.
//
// Der Agent bestimmt seine Wurzel aus dem eigenen Pfad
// (`ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"`). Es genuegt
// also, deploy/ zu kopieren und ihn dort zu starten.
$root = sys_get_temp_dir().'/clupilot-ceiling-'.bin2hex(random_bytes(6));
File::ensureDirectoryExists($root.'/deploy/lib');
File::ensureDirectoryExists($root.'/storage/app/deploy');
File::copy(base_path('deploy/update-agent.sh'), $root.'/deploy/update-agent.sh');
File::copy(base_path('deploy/lib/release.sh'), $root.'/deploy/lib/release.sh');
// MANIFEST_FILE ist `storage/app/deployment.json` — eine Ebene UEBER
// storage/app/deploy/. Siehe deploy/lib/release.sh:19.
File::put($root.'/storage/app/deployment.json', json_encode([
'version' => $deployedVersion,
'commit' => 'deadbeef',
'source' => 'refs/tags/v'.$deployedVersion,
]));
if ($ceiling !== null) {
File::put($root.'/storage/app/deploy/release-ceiling', $ceiling);
}
$tagCommands = '';
foreach ($tags as $tag) {
$tagCommands .= "git tag {$tag}\n";
}
$result = Process::path($root)->timeout(90)->run(<<<BASH
set -e
git init -q .
git -c user.email=t@example.com -c user.name=t commit -q --allow-empty -m init
{$tagCommands}
stub="\$(mktemp -d)"
# Nur `fetch` wird abgefangen — es gibt keine Gegenstelle. Alles andere
# geht ans echte git und wirkt im Wegwerf-Checkout.
cat > "\$stub/git" <<'GIT'
#!/bin/sh
if [ "\$1" = "fetch" ]; then exit 0; fi
exec /usr/bin/git "\$@"
GIT
chmod +x "\$stub/git"
printf '#!/bin/sh\nexit 1\n' > "\$stub/docker"
chmod +x "\$stub/docker"
PATH="\$stub:\$PATH" bash deploy/update-agent.sh >/dev/null 2>&1 || true
rm -rf "\$stub"
BASH);
expect($result->successful())->toBeTrue($result->errorOutput());
$status = json_decode(File::get($root.'/storage/app/deploy/update-status.json'), true);
File::deleteDirectory($root);
return $status;
}
it('offers only up to the ceiling', function () {
// Ausgeliefert 1.7.3, vorhanden bis v9.9.9, Decke auf v9.9.8: genau eine
// Aktualisierung, und das Ziel ist die Decke.
$status = runAgentWithCeiling('v9.9.8', ['v9.9.8', 'v9.9.9']);
expect($status['target_release'])->toBe('v9.9.8')
->and($status['behind'])->toBe(1)
->and($status['ceiling'])->toBe('v9.9.8')
->and($status['ceiling_error'])->toBe('');
});
it('offers nothing when the ceiling points at a tag that does not exist', function () {
// Die Decke faellt ZU. Ein Rueckfall auf "neueste" installierte genau das,
// wovon der Besitzer weggenagelt hat.
$status = runAgentWithCeiling('v9.9.7', ['v9.9.8', 'v9.9.9']);
expect($status['behind'])->toBe(0)
->and($status['ceiling_error'])->toBe('ceiling_missing')
->and($status['target_release'])->toBe('');
});
it('offers nothing when the ceiling is malformed', function () {
$status = runAgentWithCeiling('neueste bitte', ['v9.9.8', 'v9.9.9']);
expect($status['behind'])->toBe(0)
->and($status['ceiling_error'])->toBe('ceiling_invalid');
});
it('behaves exactly as before without a ceiling', function () {
$status = runAgentWithCeiling(null, ['v9.9.8', 'v9.9.9']);
expect($status['target_release'])->toBe('v9.9.9')
->and($status['ceiling'])->toBe('')
->and($status['ceiling_error'])->toBe('');
});
it('reports the versions that may be pinned to', function () {
$status = runAgentWithCeiling(null, ['v9.9.8', 'v9.9.9']);
expect($status['releases'])->toContain('v9.9.9')
->and($status['releases'])->toContain('v9.9.8');
});