Files
XC_VM/tests/Unit/UpdateCommandPinnedTest.php
T
obscuremindandClaude Opus 5.5 e9b5299016 feat(update): patch builds, to ship fixes without a new release
A fix reached panels only with the next release, and a release is a
ceremony: a version bump, a changelog, a translation pass, a release PR.

A patch build is the release with the fixes made since, published as
X.Y.Z-pN by one run of the new Build Patch workflow from any ref: it picks
the release the ref contains, numbers the patch, runs the unit tests, stamps
the version into the build (as a nightly's is), builds the files a release
has and publishes them on the channel of the release it patches. Nothing is
prepared by hand.

The panel takes it as it takes a release, on MAIN and on load balancers:
- version_compare() already orders 2.6.5 < 2.6.5-p1 < 2.6.6. The release
  list is now ordered by version on every channel, not by publication: a
  patch of an older release is published after the release that follows it
  and must not hide it.
- An update, a pin and a rollback accept a patch version.
- A patch's changelog is read from its own asset.
- A module that asks for core X.Y.Z keeps loading on X.Y.Z-pN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AneTzAHPPkYWPQjwyZ2td9
2026-10-11 14:56:19 +00:00

61 lines
2.9 KiB
PHP

<?php
use XcVm\Cli\Commands\UpdateCommand;
use PHPUnit\Framework\TestCase;
/**
* UpdateCommand::pinned() — the release MAIN names to a load balancer.
*
* A MAIN on the dev channel names its nightly (x.y.z-dev.N); dropping it sent
* the LB to its own channel lookup instead of MAIN's build.
*/
class UpdateCommandPinnedTest extends TestCase {
public function testAcceptsReleaseAndNightlyTags(): void {
$this->assertSame('1.2.3', UpdateCommand::pinned(' 1.2.3 '));
$this->assertSame('1.2.3-dev.7', UpdateCommand::pinned('1.2.3-dev.7'));
$this->assertSame('1.2.3-p2', UpdateCommand::pinned('1.2.3-p2'), 'a patch build of a release');
$this->assertNull(UpdateCommand::pinned('1.2.3-p2; rm -rf /'));
$this->assertNull(UpdateCommand::pinned('1.2.3-beta.1'));
}
/** cron:servers: a load balancer stays listed until it is on MAIN's release, and is told when it is back. */
public function testAListedLoadBalancerIsToldWhenItIsBackAndNotARolledBackOne(): void {
if (!defined('XC_VM_VERSION')) {
define('XC_VM_VERSION', '2.6.0');
}
$rNow = 1800000000;
$rLb = ['is_main' => 0, 'server_type' => 0, 'enabled' => 1, 'status' => 1, 'last_check_ago' => $rNow - 30, 'xc_vm_version' => '0.0.1'];
$rServers = [
1 => ['id' => 1, 'is_main' => 1] + $rLb,
2 => ['id' => 2] + $rLb,
3 => ['id' => 3, 'xc_vm_version' => XC_VM_VERSION] + $rLb,
4 => ['id' => 4, 'server_type' => 1] + $rLb,
5 => ['id' => 5, 'status' => 5] + $rLb,
6 => ['id' => 6, 'last_check_ago' => $rNow - 600] + $rLb,
7 => ['id' => 7, 'enabled' => 0] + $rLb,
8 => ['id' => 8, 'xc_vm_version' => null] + $rLb,
10 => ['id' => 10] + $rLb,
];
// Listed and never told (0). Back and behind (2), or its release
// unknown (8): told. Still updating (5), offline (6) or disabled (7):
// waited for. MAIN, one on MAIN's release by now (3), a proxy (4) and
// one that is gone (9): dropped.
$rNever = array_fill_keys([1, 2, 3, 4, 5, 6, 7, 8, 9], 0);
$this->assertSame(['tell' => [2, 8], 'wait' => [2 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0]], UpdateCommand::lbsToTell($rServers, $rNever, $rNow));
// Told an hour ago: its update is on its way, it only stays listed.
// Told more than a day ago and still behind: that update expired while
// it was away, so it is told again.
$this->assertSame(['tell' => [], 'wait' => [2 => $rNow - 3600]], UpdateCommand::lbsToTell($rServers, [2 => $rNow - 3600], $rNow));
$this->assertSame(['tell' => [2], 'wait' => [2 => $rNow - 90000]], UpdateCommand::lbsToTell($rServers, ['2' => $rNow - 90000], $rNow));
// 10 is behind and online but not listed: it reached MAIN's release,
// then an admin rolled it back, and it stays there.
$this->assertSame(['tell' => [], 'wait' => []], UpdateCommand::lbsToTell($rServers, [], $rNow));
}
public function testRejectsAnythingElse(): void {
foreach ([null, '', '1.2', '1.2.3-beta.1', '1.2.3-dev', '1.2.3;id', 123] as $rBad) {
$this->assertNull(UpdateCommand::pinned($rBad));
}
}
}