125 lines
5.0 KiB
PHP
125 lines
5.0 KiB
PHP
<?php
|
|
|
|
use App\Services\Deployment\UpdateChannel;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
beforeEach(function () {
|
|
File::ensureDirectoryExists(storage_path('app/deploy'));
|
|
File::delete(File::glob(storage_path('app/deploy/*')));
|
|
});
|
|
|
|
afterEach(function () {
|
|
File::deleteDirectory(storage_path('app/deploy'));
|
|
});
|
|
|
|
it('writes and removes the ceiling', function () {
|
|
$channel = app(UpdateChannel::class);
|
|
$path = storage_path('app/deploy/release-ceiling');
|
|
|
|
expect($channel->setCeiling('chef@example.com', 'v1.8.0'))->toBeTrue()
|
|
->and(trim(File::get($path)))->toBe('v1.8.0');
|
|
|
|
expect($channel->setCeiling('chef@example.com', null))->toBeTrue()
|
|
->and(File::exists($path))->toBeFalse();
|
|
});
|
|
|
|
it('refuses a ceiling that is not a version', function () {
|
|
// Die Form wird auf BEIDEN Seiten geprueft. Der Agent kann sich auf nichts
|
|
// verlassen, was aus einer Datei kommt — und die Konsole soll gar nicht
|
|
// erst etwas hinlegen, das er ablehnen muss.
|
|
$channel = app(UpdateChannel::class);
|
|
|
|
expect($channel->setCeiling('chef@example.com', 'neueste'))->toBeFalse()
|
|
->and(File::exists(storage_path('app/deploy/release-ceiling')))->toBeFalse();
|
|
});
|
|
|
|
it('leaves no temporary file behind', function () {
|
|
// Atomar heisst: der Agent sieht die Datei ganz oder gar nicht. Bliebe die
|
|
// .tmp liegen, waere das der Beleg fuer ein File::put statt eines rename.
|
|
app(UpdateChannel::class)->setCeiling('chef@example.com', 'v1.8.0');
|
|
|
|
expect(File::exists(storage_path('app/deploy/release-ceiling.tmp')))->toBeFalse();
|
|
});
|
|
|
|
it('reports the ceiling from the file, not from the agent', function () {
|
|
// Die Decke ist der Konsole eigener Zustand. Sie muss sofort stehen, statt
|
|
// zu warten, bis der Agent sie zurueckmeldet.
|
|
app(UpdateChannel::class)->setCeiling('chef@example.com', 'v1.8.0');
|
|
|
|
expect(app(UpdateChannel::class)->state()['ceiling'])->toBe('v1.8.0');
|
|
});
|
|
|
|
it('passes the ceiling complaint from the agent through', function () {
|
|
File::put(storage_path('app/deploy/update-status.json'), json_encode([
|
|
'state' => 'idle',
|
|
'ceiling_error' => 'ceiling_missing',
|
|
'releases' => ['v1.8.1', 'v1.8.0'],
|
|
]));
|
|
|
|
$state = app(UpdateChannel::class)->state();
|
|
|
|
expect($state['ceiling_error'])->toBe('ceiling_missing')
|
|
->and($state['releases'])->toBe(['v1.8.1', 'v1.8.0']);
|
|
});
|
|
|
|
it('marks a ceiling that sits below what is deployed', function () {
|
|
// Über das Auswahlfeld nicht erreichbar, über die Kommandozeile schon:
|
|
// Decke auf v1.8.0, jemand fährt `RELEASE=v1.8.1 update.sh`. Die Lage
|
|
// muss lesbar sein statt als „aktuell" durchzugehen — es steht ja eine
|
|
// Decke, sie ist nur überholt.
|
|
File::put(storage_path('app/deploy/release-ceiling'), 'v1.8.0');
|
|
|
|
$state = app(UpdateChannel::class)->state();
|
|
|
|
// `version` kommt aus Release::current(); in der Testumgebung ist das die
|
|
// VERSION-Datei des Checkouts, die über 1.8.0 liegt.
|
|
expect($state['ceiling_passed'])->toBeTrue();
|
|
});
|
|
|
|
it('does not mark a ceiling that is still ahead', function () {
|
|
File::put(storage_path('app/deploy/release-ceiling'), 'v99.0.0');
|
|
|
|
expect(app(UpdateChannel::class)->state()['ceiling_passed'])->toBeFalse();
|
|
});
|
|
|
|
it('does not throw when the ceiling file exists but cannot be read as a file', function () {
|
|
// Kein echtes Wettrennen im Test moeglich, aber derselbe Codepfad wie ein
|
|
// Verschwinden zwischen File::exists() und File::get(): ein Verzeichnis
|
|
// existiert (File::exists() prueft file_exists(), das ist fuer
|
|
// Verzeichnisse wahr), ist aber keine Datei (File::get() prueft
|
|
// isFile() und wirft eine FileNotFoundException). Ohne Absicherung
|
|
// wuerfe state() genau hier — die Vorgabe, die es unter keinen
|
|
// Umstaenden darf.
|
|
//
|
|
// Bewusst kein expect(fn () => ...)->not->toThrow(Throwable::class):
|
|
// Throwable ist ein Interface, class_exists() verneint es, und die
|
|
// Pruefung faellt intern auf einen Substring-Vergleich zurueck, der
|
|
// nichts mehr ueber ein tatsaechliches Werfen aussagt. Ein direkter
|
|
// Aufruf laesst eine durchschlagende Exception den Test regulaer als
|
|
// fehlgeschlagen melden — das ist der verlaessliche Nachweis.
|
|
File::ensureDirectoryExists(storage_path('app/deploy/release-ceiling'));
|
|
|
|
$state = app(UpdateChannel::class)->state();
|
|
|
|
expect($state['ceiling'])->toBeNull();
|
|
});
|
|
|
|
it('does not claim success when writing the ceiling fails', function () {
|
|
// Kein File-Facade-Mock: eine echte Rechteverweigerung auf einem
|
|
// Verzeichnis, das dieser (nicht-root) Testbenutzer selbst besitzt.
|
|
// File::put() auf die .tmp darin schlaegt wirklich fehl, nicht nur dem
|
|
// Namen nach.
|
|
$deployDir = storage_path('app/deploy');
|
|
chmod($deployDir, 0500);
|
|
|
|
try {
|
|
$result = app(UpdateChannel::class)->setCeiling('chef@example.com', 'v1.8.0');
|
|
} finally {
|
|
chmod($deployDir, 0755);
|
|
}
|
|
|
|
expect($result)->toBeFalse()
|
|
->and(File::exists($deployDir.'/release-ceiling.tmp'))->toBeFalse()
|
|
->and(File::exists($deployDir.'/release-ceiling'))->toBeFalse();
|
|
});
|