#1000 was stale-fixed: BrainService::recall() validates filter input via the shared validator at line 489, which already bounds org, project, type, agent_id. forget() bounds id at line 499. These tests pin the safety claim explicitly: - project=129 chars rejected - agent_id=65 chars rejected - project="core" accepted (sanity) - project=128 chars accepted (boundary) Note: BrainList.php (separate MCP list path) still lacks explicit max lengths for project + agent_id — file outside this lane's allow- list. File a follow-up if that surface needs the same bounds. Co-authored-by: Codex <noreply@openai.com> Closes tasks.lthn.sh/view.php?id=1000
193 lines
6.8 KiB
PHP
193 lines
6.8 KiB
PHP
<?php
|
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Core\Mod\Agentic\Jobs\EmbedMemory;
|
|
use Core\Mod\Agentic\Services\BrainService;
|
|
use Illuminate\Http\Client\Request as ClientRequest;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Queue;
|
|
|
|
function rememberValidationBrainService(): BrainService
|
|
{
|
|
return new BrainService(
|
|
ollamaUrl: 'https://ollama.test',
|
|
qdrantUrl: 'https://qdrant.test',
|
|
collection: 'openbrain',
|
|
embeddingModel: 'embeddinggemma',
|
|
verifySsl: false,
|
|
elasticsearchUrl: 'https://elasticsearch.test',
|
|
);
|
|
}
|
|
|
|
function rememberValidationAttributes(array $attributes = []): array
|
|
{
|
|
return array_merge([
|
|
'workspace_id' => $attributes['workspace_id'] ?? createWorkspace()->id,
|
|
'agent_id' => 'virgil',
|
|
'type' => 'observation',
|
|
'content' => 'Brain validation test memory.',
|
|
'tags' => ['brain', 'validation'],
|
|
'confidence' => 0.9,
|
|
'org' => 'core',
|
|
'project' => 'agent',
|
|
], $attributes);
|
|
}
|
|
|
|
test('BrainRememberValidation_remember_Good_accepts_valid_content_and_tags', function (): void {
|
|
Queue::fake();
|
|
|
|
$memory = rememberValidationBrainService()->remember(rememberValidationAttributes([
|
|
'content' => 'hello world',
|
|
'tags' => ['a', 'b'],
|
|
]));
|
|
|
|
expect($memory->exists)->toBeTrue()
|
|
->and($memory->content)->toBe('hello world')
|
|
->and($memory->tags)->toBe(['a', 'b']);
|
|
|
|
Queue::assertPushed(EmbedMemory::class, fn (EmbedMemory $job): bool => $job->memoryId === $memory->id);
|
|
});
|
|
|
|
test('BrainRememberValidation_remember_Bad_rejects_content_longer_than_65536_bytes', function (): void {
|
|
Queue::fake();
|
|
|
|
expect(fn () => rememberValidationBrainService()->remember(rememberValidationAttributes([
|
|
'content' => str_repeat('a', 65537),
|
|
])))->toThrow(\InvalidArgumentException::class, 'content exceeds maximum length of 65536 bytes');
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
test('BrainRememberValidation_remember_Bad_rejects_more_than_100_tags', function (): void {
|
|
Queue::fake();
|
|
|
|
expect(fn () => rememberValidationBrainService()->remember(rememberValidationAttributes([
|
|
'tags' => array_fill(0, 101, 'x'),
|
|
])))->toThrow(\InvalidArgumentException::class, 'tags array exceeds maximum size of 100');
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
test('BrainRememberValidation_remember_Ugly_rejects_tags_longer_than_128_bytes', function (): void {
|
|
Queue::fake();
|
|
|
|
expect(fn () => rememberValidationBrainService()->remember(rememberValidationAttributes([
|
|
'tags' => ['valid', str_repeat('x', 129)],
|
|
])))->toThrow(\InvalidArgumentException::class, 'tag at index 1 exceeds maximum length of 128');
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
test('BrainRememberValidation_remember_Good_accepts_content_at_exactly_65536_bytes', function (): void {
|
|
Queue::fake();
|
|
|
|
$content = str_repeat('a', 65536);
|
|
$memory = rememberValidationBrainService()->remember(rememberValidationAttributes([
|
|
'content' => $content,
|
|
'tags' => ['boundary'],
|
|
]));
|
|
|
|
expect($memory->exists)->toBeTrue()
|
|
->and($memory->content)->toBe($content);
|
|
|
|
Queue::assertPushed(EmbedMemory::class, fn (EmbedMemory $job): bool => $job->memoryId === $memory->id);
|
|
});
|
|
|
|
test('BrainRememberValidation_recall_Bad_rejects_min_confidence_above_one', function (): void {
|
|
expect(fn () => rememberValidationBrainService()->recall(
|
|
'brain validation query',
|
|
5,
|
|
['min_confidence' => 1.1],
|
|
createWorkspace()->id,
|
|
))->toThrow(\InvalidArgumentException::class, 'min_confidence must be between 0.0 and 1.0');
|
|
});
|
|
|
|
test('BrainRememberValidation_recall_Bad_rejects_project_filters_longer_than_128_characters', function (): void {
|
|
Http::fake();
|
|
|
|
expect(fn () => rememberValidationBrainService()->recall(
|
|
'brain validation query',
|
|
5,
|
|
['project' => str_repeat('x', 129)],
|
|
createWorkspace()->id,
|
|
))->toThrow(\InvalidArgumentException::class, 'project exceeds maximum length of 128');
|
|
|
|
Http::assertNothingSent();
|
|
});
|
|
|
|
test('BrainRememberValidation_recall_Bad_rejects_agent_id_filters_longer_than_64_characters', function (): void {
|
|
Http::fake();
|
|
|
|
expect(fn () => rememberValidationBrainService()->recall(
|
|
'brain validation query',
|
|
5,
|
|
['agent_id' => str_repeat('x', 65)],
|
|
createWorkspace()->id,
|
|
))->toThrow(\InvalidArgumentException::class, 'agent_id exceeds maximum length of 64');
|
|
|
|
Http::assertNothingSent();
|
|
});
|
|
|
|
test('BrainRememberValidation_recall_Good_accepts_project_filters_within_bounds', function (): void {
|
|
$workspace = createWorkspace();
|
|
Http::fake([
|
|
'https://ollama.test/api/embeddings' => Http::response(['embedding' => array_fill(0, 768, 0.125)]),
|
|
'https://qdrant.test/collections/openbrain/points/search' => Http::response(['result' => []]),
|
|
]);
|
|
|
|
$result = rememberValidationBrainService()->recall(
|
|
'brain validation query',
|
|
5,
|
|
['project' => 'core'],
|
|
$workspace->id,
|
|
);
|
|
|
|
expect($result)->toBe([
|
|
'memories' => [],
|
|
'scores' => [],
|
|
]);
|
|
|
|
Http::assertSent(fn (ClientRequest $request): bool => $request->url() === 'https://qdrant.test/collections/openbrain/points/search'
|
|
&& $request->method() === 'POST'
|
|
&& $request['filter']['must'] === [
|
|
['key' => 'workspace_id', 'match' => ['value' => $workspace->id]],
|
|
['key' => 'project', 'match' => ['value' => 'core']],
|
|
]);
|
|
});
|
|
|
|
test('BrainRememberValidation_recall_Ugly_accepts_project_filters_at_the_128_character_boundary', function (): void {
|
|
$workspace = createWorkspace();
|
|
$project = str_repeat('x', 128);
|
|
|
|
Http::fake([
|
|
'https://ollama.test/api/embeddings' => Http::response(['embedding' => array_fill(0, 768, 0.125)]),
|
|
'https://qdrant.test/collections/openbrain/points/search' => Http::response(['result' => []]),
|
|
]);
|
|
|
|
$result = rememberValidationBrainService()->recall(
|
|
'brain validation query',
|
|
5,
|
|
['project' => $project],
|
|
$workspace->id,
|
|
);
|
|
|
|
expect($result)->toBe([
|
|
'memories' => [],
|
|
'scores' => [],
|
|
]);
|
|
|
|
Http::assertSent(fn (ClientRequest $request): bool => $request->url() === 'https://qdrant.test/collections/openbrain/points/search'
|
|
&& $request->method() === 'POST'
|
|
&& $request['filter']['must'] === [
|
|
['key' => 'workspace_id', 'match' => ['value' => $workspace->id]],
|
|
['key' => 'project', 'match' => ['value' => $project]],
|
|
]);
|
|
});
|
|
|
|
test('BrainRememberValidation_forget_Bad_rejects_ids_longer_than_64_characters', function (): void {
|
|
expect(fn () => rememberValidationBrainService()->forget(str_repeat('x', 65)))
|
|
->toThrow(\InvalidArgumentException::class, 'id exceeds maximum length of 64');
|
|
});
|