Merge branch 'main' of ssh://forge.lthn.ai:2223/core/php-agentic
Some checks failed
CI / PHP 8.3 (push) Failing after 1m36s
CI / PHP 8.4 (push) Failing after 1m39s

This commit is contained in:
Claude 2026-02-23 06:26:58 +00:00
commit 2f3314a418
No known key found for this signature in database
GPG key ID: AF404715446AEB41
4 changed files with 179 additions and 5 deletions

View file

@ -18,13 +18,22 @@ class ForAgentsController extends Controller
{
public function __invoke(): JsonResponse
{
// Cache for 1 hour since this is static data
$data = Cache::remember('agentic.for-agents.json', 3600, function () {
$ttl = (int) config('mcp.cache.for_agents_ttl', 3600);
$data = Cache::remember($this->cacheKey(), $ttl, function () {
return $this->getAgentData();
});
return response()->json($data)
->header('Cache-Control', 'public, max-age=3600');
->header('Cache-Control', "public, max-age={$ttl}");
}
/**
* Namespaced cache key, configurable to prevent cross-module collisions.
*/
public function cacheKey(): string
{
return (string) config('mcp.cache.for_agents_key', 'agentic.for-agents.json');
}
private function getAgentData(): array

View file

@ -143,10 +143,12 @@ Production-quality task list for the AI agent orchestration package.
- Updated blade template to use `permissions`, `getMaskedKey()`, `togglePermission()`
- Added integration tests in `tests/Feature/ApiKeyManagerTest.php`
- [ ] **CQ-003: ForAgentsController cache key not namespaced**
- [x] **CQ-003: ForAgentsController cache key not namespaced** (FIXED 2026-02-23)
- Location: `Controllers/ForAgentsController.php`
- Issue: `Cache::remember('agentic.for-agents.json', ...)` could collide
- Fix: Add workspace prefix or use config-based key
- Fix: Cache key and TTL now driven by `mcp.cache.for_agents_key` / `mcp.cache.for_agents_ttl` config
- Added `cacheKey()` public method and config entries in `config.php`
- Tests added in `tests/Feature/ForAgentsControllerTest.php`
### Performance

View file

@ -56,4 +56,19 @@ return [
'drafts_path' => 'app/Mod/Agentic/Resources/drafts',
],
/*
|--------------------------------------------------------------------------
| Cache Keys
|--------------------------------------------------------------------------
|
| Namespaced cache keys used by agentic endpoints. Override these in your
| application config to prevent collisions with other modules.
|
*/
'cache' => [
'for_agents_key' => 'agentic.for-agents.json',
'for_agents_ttl' => 3600,
],
];

View file

@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
/**
* Tests for ForAgentsController cache key namespacing (CQ-003).
*
* Verifies that the cache key is config-based to prevent cross-module collisions,
* and that cache invalidation uses the same namespaced key.
*/
use Core\Mod\Agentic\Controllers\ForAgentsController;
use Illuminate\Support\Facades\Cache;
// =========================================================================
// Cache Key Tests
// =========================================================================
describe('ForAgentsController cache key', function () {
it('uses the default namespaced cache key', function () {
$controller = new ForAgentsController();
expect($controller->cacheKey())->toBe('agentic.for-agents.json');
});
it('uses a custom cache key when configured', function () {
config(['mcp.cache.for_agents_key' => 'custom-module.for-agents.json']);
$controller = new ForAgentsController();
expect($controller->cacheKey())->toBe('custom-module.for-agents.json');
});
it('returns to default key after config is cleared', function () {
config(['mcp.cache.for_agents_key' => null]);
$controller = new ForAgentsController();
expect($controller->cacheKey())->toBe('agentic.for-agents.json');
});
});
// =========================================================================
// Cache Behaviour Tests
// =========================================================================
describe('ForAgentsController cache behaviour', function () {
it('stores data under the namespaced cache key', function () {
Cache::fake();
$controller = new ForAgentsController();
$controller();
$key = $controller->cacheKey();
expect(Cache::has($key))->toBeTrue();
});
it('returns cached data on subsequent calls', function () {
Cache::fake();
$controller = new ForAgentsController();
$first = $controller();
$second = $controller();
expect($first->getContent())->toBe($second->getContent());
});
it('respects the configured TTL', function () {
config(['mcp.cache.for_agents_ttl' => 7200]);
Cache::fake();
$controller = new ForAgentsController();
$response = $controller();
expect($response->headers->get('Cache-Control'))->toContain('max-age=7200');
});
it('uses default TTL of 3600 when not configured', function () {
config(['mcp.cache.for_agents_ttl' => null]);
Cache::fake();
$controller = new ForAgentsController();
$response = $controller();
expect($response->headers->get('Cache-Control'))->toContain('max-age=3600');
});
it('can be invalidated using the namespaced key', function () {
Cache::fake();
$controller = new ForAgentsController();
$controller();
$key = $controller->cacheKey();
expect(Cache::has($key))->toBeTrue();
Cache::forget($key);
expect(Cache::has($key))->toBeFalse();
});
it('stores data under the custom key when configured', function () {
config(['mcp.cache.for_agents_key' => 'tenant-a.for-agents.json']);
Cache::fake();
$controller = new ForAgentsController();
$controller();
expect(Cache::has('tenant-a.for-agents.json'))->toBeTrue();
expect(Cache::has('agentic.for-agents.json'))->toBeFalse();
});
});
// =========================================================================
// Response Structure Tests
// =========================================================================
describe('ForAgentsController response', function () {
it('returns a JSON response', function () {
Cache::fake();
$controller = new ForAgentsController();
$response = $controller();
expect($response->headers->get('Content-Type'))->toContain('application/json');
});
it('response contains platform information', function () {
Cache::fake();
$controller = new ForAgentsController();
$response = $controller();
$data = json_decode($response->getContent(), true);
expect($data)->toHaveKey('platform')
->and($data['platform'])->toHaveKey('name');
});
it('response contains capabilities', function () {
Cache::fake();
$controller = new ForAgentsController();
$response = $controller();
$data = json_decode($response->getContent(), true);
expect($data)->toHaveKey('capabilities')
->and($data['capabilities'])->toHaveKey('mcp_servers');
});
});