File: /home/xedaptot/be.naniguide.com/tests/Unit/UpgradeManagerPatchTest.php
<?php
/**
* Unit tests for UpgradeManager helpers added for POST /api/v1/upgrade/run.
*
* Covers:
* 1. downloadPatchTo — throws on bad URL, cleans up partial file
* 2. applyPatchFromUrl — cleans up tmp zip even on download failure
* 3. applyLoadedPatch — dry_run path with hand-crafted no-op patch fixture
*
* applyLoadedPatch replaces files only — migrate + upgrade:finalize are a separate
* HTTP request (POST /api/v1/upgrade/finalize) so a fresh PHP worker can load the
* newly-written code.
*
* NOTE: Real patch apply (run) is NOT tested here — too risky even with rollback.
* That path is verified via the smoke recipe in .support/HANDBOOK_DETAILS.md
* "Method API" section against a dev instance.
*/
use App\Library\UpgradeManager;
uses(\Tests\TestCase::class);
beforeEach(function () {
$this->tmpDir = storage_path('tmp');
if (!is_dir($this->tmpDir)) {
@mkdir($this->tmpDir, 0755, true);
}
});
afterEach(function () {
// Wipe patch source dir so other tests don't see stale state
$patchDir = storage_path('tmp/patch');
if (is_dir($patchDir)) {
(new \Illuminate\Filesystem\Filesystem())->deleteDirectory($patchDir);
}
// Wipe leftover api-upgrade-* zips
foreach (glob(storage_path('tmp/api-upgrade-*.zip')) ?: [] as $f) {
@unlink($f);
}
});
// -----------------------------------------------------------------------------
// downloadPatchTo
// -----------------------------------------------------------------------------
it('downloadPatchTo throws and cleans up partial file when URL returns non-200', function () {
$mgr = new UpgradeManager();
$dest = storage_path('tmp/test-download-' . uniqid() . '.zip');
// Use a localhost port nothing is listening on — connect refused or timeout
expect(fn () => $mgr->downloadPatchTo('http://127.0.0.1:1/nonexistent.zip', $dest, [
'timeout' => 2,
]))->toThrow(\Exception::class);
expect(is_file($dest))->toBeFalse();
});
it('downloadPatchTo throws on connection error (unreachable host)', function () {
$mgr = new UpgradeManager();
$dest = storage_path('tmp/test-download-' . uniqid() . '.zip');
expect(fn () => $mgr->downloadPatchTo('http://0.0.0.0:1/x.zip', $dest, [
'timeout' => 2,
]))->toThrow(\Exception::class);
});
// -----------------------------------------------------------------------------
// applyPatchFromUrl
// -----------------------------------------------------------------------------
it('applyPatchFromUrl cleans up tmp zip when download fails', function () {
$mgr = new UpgradeManager();
$beforeCount = count(glob(storage_path('tmp/api-upgrade-*.zip')) ?: []);
expect(fn () => $mgr->applyPatchFromUrl('http://127.0.0.1:1/nope.zip', [
'timeout' => 2,
]))->toThrow(\Exception::class);
$afterCount = count(glob(storage_path('tmp/api-upgrade-*.zip')) ?: []);
expect($afterCount)->toBe($beforeCount);
});
// -----------------------------------------------------------------------------
// applyLoadedPatch — dry_run with hand-crafted no-op patch (no files to mutate)
// -----------------------------------------------------------------------------
it('applyLoadedPatch returns dry_run summary without mutating VERSION', function () {
$mgr = new UpgradeManager();
$patchDir = storage_path('tmp/patch');
if (!is_dir($patchDir)) {
@mkdir($patchDir, 0755, true);
}
$currentVersion = trim(file_get_contents(base_path('VERSION')));
// Bump to a higher version so validate() won't reject as same/older
$newVersion = $currentVersion . '+test';
file_put_contents($patchDir . '/' . UpgradeManager::META_FILE, json_encode([
'version' => $newVersion,
'last_supported' => $currentVersion,
'updated' => [],
'deleted' => [],
'packages' => [],
'dirs' => [],
]));
$summary = $mgr->applyLoadedPatch(['dry_run' => true]);
expect($summary['dry_run'])->toBeTrue();
expect($summary['old_version'])->toBe($currentVersion);
expect($summary['new_version'])->toBe($currentVersion); // unchanged in dry_run
expect($summary['patch_target_version'])->toBe($newVersion);
expect($summary['duration_ms'])->toBeInt();
// applyLoadedPatch no longer runs migrate — that's the /upgrade/finalize endpoint's job
expect($summary)->not->toHaveKey('migrations_ran');
expect($summary)->not->toHaveKey('migrate_output_tail');
expect($summary)->not->toHaveKey('finalize_output_tail');
// VERSION file unchanged on disk
expect(trim(file_get_contents(base_path('VERSION'))))->toBe($currentVersion);
});
it('applyLoadedPatch dry_run reports patch_last_supported correctly', function () {
$mgr = new UpgradeManager();
$patchDir = storage_path('tmp/patch');
if (!is_dir($patchDir)) {
@mkdir($patchDir, 0755, true);
}
$currentVersion = trim(file_get_contents(base_path('VERSION')));
file_put_contents($patchDir . '/' . UpgradeManager::META_FILE, json_encode([
'version' => $currentVersion . '+test2',
'last_supported' => '0.0.1',
'updated' => [],
'deleted' => [],
'packages' => [],
'dirs' => [],
]));
$summary = $mgr->applyLoadedPatch(['dry_run' => true]);
expect($summary['patch_last_supported'])->toBe('0.0.1');
});