Merge pull request 'refactor: namespace cache keys to prevent collisions' (#50) from refactor/namespace-cache-keys into main
Reviewed-on: #50
This commit is contained in:
commit
00a7e2f4ef
4 changed files with 179 additions and 5 deletions
|
|
@ -18,13 +18,22 @@ class ForAgentsController extends Controller
|
||||||
{
|
{
|
||||||
public function __invoke(): JsonResponse
|
public function __invoke(): JsonResponse
|
||||||
{
|
{
|
||||||
// Cache for 1 hour since this is static data
|
$ttl = (int) config('mcp.cache.for_agents_ttl', 3600);
|
||||||
$data = Cache::remember('agentic.for-agents.json', 3600, function () {
|
|
||||||
|
$data = Cache::remember($this->cacheKey(), $ttl, function () {
|
||||||
return $this->getAgentData();
|
return $this->getAgentData();
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json($data)
|
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
|
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()`
|
- Updated blade template to use `permissions`, `getMaskedKey()`, `togglePermission()`
|
||||||
- Added integration tests in `tests/Feature/ApiKeyManagerTest.php`
|
- 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`
|
- Location: `Controllers/ForAgentsController.php`
|
||||||
- Issue: `Cache::remember('agentic.for-agents.json', ...)` could collide
|
- 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
|
### Performance
|
||||||
|
|
||||||
|
|
|
||||||
15
config.php
15
config.php
|
|
@ -56,4 +56,19 @@ return [
|
||||||
'drafts_path' => 'app/Mod/Agentic/Resources/drafts',
|
'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