refactor: namespace cache keys to prevent collisions #50
4 changed files with 179 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
6
TODO.md
6
TODO.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
15
config.php
15
config.php
|
|
@ -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,
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
148
tests/Feature/ForAgentsControllerTest.php
Normal file
148
tests/Feature/ForAgentsControllerTest.php
Normal 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');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue