fix(versions): discriminate release caches by repository + assert the UA value
- ReleaseChecker: mix a short hash of clusev.repository into the tag + changelog cache keys so a cached value can never be reused across a host/repo change (the promotion pipeline re-points CLUSEV_REPOSITORY between the private mirror and the public repo). - Strengthen the GitHub User-Agent test to assert the value (Clusev-Panel), not mere presence — Guzzle always sets some UA, so the old check was a tautology. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/v1-foundation
parent
a94efb0b0d
commit
437221788d
|
|
@ -23,7 +23,17 @@ class ReleaseChecker
|
|||
|
||||
private function cacheKey(string $channel): string
|
||||
{
|
||||
return 'clusev:latest-release:'.$channel;
|
||||
return 'clusev:latest-release:'.$this->repoTag().':'.$channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Short discriminator of the configured repository, mixed into every cache key so a cached
|
||||
* tag/changelog can never be reused across a host/repo change (e.g. the promotion pipeline
|
||||
* re-points CLUSEV_REPOSITORY from the private mirror to the public repo).
|
||||
*/
|
||||
private function repoTag(): string
|
||||
{
|
||||
return substr(md5((string) config('clusev.repository')), 0, 8);
|
||||
}
|
||||
|
||||
public function channel(): string
|
||||
|
|
@ -146,7 +156,7 @@ class ReleaseChecker
|
|||
}
|
||||
[$base, $owner, $name] = $parts;
|
||||
|
||||
$key = 'clusev:remote-changelog:'.$ref;
|
||||
$key = 'clusev:remote-changelog:'.$this->repoTag().':'.$ref;
|
||||
$cached = Cache::get($key);
|
||||
if (is_string($cached)) {
|
||||
return $cached;
|
||||
|
|
|
|||
|
|
@ -30,10 +30,16 @@ class ReleaseCheckerTest extends TestCase
|
|||
config()->set('clusev.repository', self::REPO);
|
||||
}
|
||||
|
||||
/** The cache key ReleaseChecker uses for the configured repo (host-discriminated). */
|
||||
private function tagKey(string $channel = 'stable'): string
|
||||
{
|
||||
return 'clusev:latest-release:'.substr(md5(self::REPO), 0, 8).':'.$channel;
|
||||
}
|
||||
|
||||
public function test_update_available_is_true_when_the_cache_holds_a_newer_tag(): void
|
||||
{
|
||||
config()->set('clusev.version', '0.9.10');
|
||||
Cache::put('clusev:latest-release:stable', '0.9.11', now()->addMinutes(30));
|
||||
Cache::put($this->tagKey(), '0.9.11', now()->addMinutes(30));
|
||||
|
||||
$this->assertTrue(app(ReleaseChecker::class)->updateAvailable());
|
||||
}
|
||||
|
|
@ -44,10 +50,10 @@ class ReleaseCheckerTest extends TestCase
|
|||
|
||||
$this->assertFalse(app(ReleaseChecker::class)->updateAvailable(), 'empty cache → no badge');
|
||||
|
||||
Cache::put('clusev:latest-release:stable', '0.9.11', now()->addMinutes(30));
|
||||
Cache::put($this->tagKey(), '0.9.11', now()->addMinutes(30));
|
||||
$this->assertFalse(app(ReleaseChecker::class)->updateAvailable(), 'equal version → no badge');
|
||||
|
||||
Cache::put('clusev:latest-release:stable', '0.9.9', now()->addMinutes(30));
|
||||
Cache::put($this->tagKey(), '0.9.9', now()->addMinutes(30));
|
||||
$this->assertFalse(app(ReleaseChecker::class)->updateAvailable(), 'stale older cache → no badge');
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +61,7 @@ class ReleaseCheckerTest extends TestCase
|
|||
{
|
||||
Http::fake();
|
||||
config()->set('clusev.version', '0.9.10');
|
||||
Cache::put('clusev:latest-release:stable', '0.9.12', now()->addMinutes(30));
|
||||
Cache::put($this->tagKey(), '0.9.12', now()->addMinutes(30));
|
||||
|
||||
$this->assertTrue(app(ReleaseChecker::class)->updateAvailable());
|
||||
Http::assertNothingSent();
|
||||
|
|
@ -68,7 +74,7 @@ class ReleaseCheckerTest extends TestCase
|
|||
$tag = app(ReleaseChecker::class)->refresh('stable');
|
||||
|
||||
$this->assertSame('0.9.13', $tag);
|
||||
$this->assertSame('0.9.13', Cache::get('clusev:latest-release:stable'));
|
||||
$this->assertSame('0.9.13', Cache::get($this->tagKey()));
|
||||
}
|
||||
|
||||
public function test_check_update_command_warms_the_cache(): void
|
||||
|
|
@ -77,7 +83,7 @@ class ReleaseCheckerTest extends TestCase
|
|||
|
||||
$this->artisan('clusev:check-update')->assertSuccessful();
|
||||
|
||||
$this->assertSame('0.9.20', Cache::get('clusev:latest-release:stable'));
|
||||
$this->assertSame('0.9.20', Cache::get($this->tagKey()));
|
||||
}
|
||||
|
||||
public function test_sidebar_shows_the_update_badge_only_when_an_update_is_available(): void
|
||||
|
|
@ -90,7 +96,7 @@ class ReleaseCheckerTest extends TestCase
|
|||
$this->get('/audit')->assertOk()->assertDontSee(__('shell.update_available'));
|
||||
|
||||
// Cached newer release → the sidebar Version item carries the badge.
|
||||
Cache::put('clusev:latest-release:stable', '0.9.11', now()->addMinutes(30));
|
||||
Cache::put($this->tagKey(), '0.9.11', now()->addMinutes(30));
|
||||
$this->get('/audit')->assertOk()->assertSee(__('shell.update_available'));
|
||||
}
|
||||
|
||||
|
|
@ -116,9 +122,10 @@ class ReleaseCheckerTest extends TestCase
|
|||
$tag = app(ReleaseChecker::class)->refresh('stable');
|
||||
|
||||
$this->assertSame('0.9.20', $tag);
|
||||
// The GitHub tag API host/path must be used, with the mandatory User-Agent header.
|
||||
// The GitHub tag API host/path must be used, with OUR explicit User-Agent (GitHub rejects
|
||||
// requests with none; assert the value, not just presence — Guzzle always sets some UA).
|
||||
Http::assertSent(fn ($req) => str_contains($req->url(), 'api.github.com/repos/clusev/clusev/tags')
|
||||
&& $req->hasHeader('User-Agent'));
|
||||
&& $req->hasHeader('User-Agent', 'Clusev-Panel'));
|
||||
}
|
||||
|
||||
public function test_fetch_changelog_uses_raw_githubusercontent_for_a_github_repository(): void
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ class VersionUpdateCheckTest extends TestCase
|
|||
parent::tearDown();
|
||||
}
|
||||
|
||||
/** The cache key ReleaseChecker uses for the configured repo (host-discriminated). */
|
||||
private function tagKey(string $channel = 'stable'): string
|
||||
{
|
||||
return 'clusev:latest-release:'.substr(md5(self::REPO), 0, 8).':'.$channel;
|
||||
}
|
||||
|
||||
public function test_reports_update_available_from_the_remote_tag_api(): void
|
||||
{
|
||||
config()->set('clusev.version', '0.9.4');
|
||||
|
|
@ -173,7 +179,7 @@ class VersionUpdateCheckTest extends TestCase
|
|||
// A stale cache from before an update must not render a "latest" below the installed
|
||||
// version (which would look like the panel is behind a release it has already passed).
|
||||
config()->set('clusev.version', '0.9.10');
|
||||
Cache::put('clusev:latest-release:stable', '0.9.9', now()->addMinutes(30));
|
||||
Cache::put($this->tagKey(), '0.9.9', now()->addMinutes(30));
|
||||
|
||||
Livewire::test(Index::class)->assertViewHas('latestTag', null);
|
||||
}
|
||||
|
|
@ -204,7 +210,7 @@ class VersionUpdateCheckTest extends TestCase
|
|||
{
|
||||
// The cached check (badge already knew) surfaces the update + button on mount, no re-click.
|
||||
config()->set('clusev.version', '0.9.10');
|
||||
Cache::put('clusev:latest-release:stable', '0.9.11', now()->addMinutes(30));
|
||||
Cache::put($this->tagKey(), '0.9.11', now()->addMinutes(30));
|
||||
|
||||
Livewire::test(Index::class)
|
||||
->assertSet('updateState', 'update')
|
||||
|
|
|
|||
Loading…
Reference in New Issue