agent/php/tests/Feature/Jobs/EmbedMemoryTest.php
Snider 4dc5ed8d14 feat(brain): add EmbedMemory job + indexed_at tracking
Implements the async-embedding pipeline's worker side:

- php/Jobs/EmbedMemory.php — Laravel Job that calls BrainService::embed()
  + qdrantUpsert() and sets indexed_at on success
- php/Migrations/…_add_indexed_at_to_brain_memories.php — nullable
  timestamp + index, portable across pgsql/mariadb (hasColumn guard)
- BrainMemory: +indexed_at fillable + datetime cast + PHPDoc
- BrainService: qdrantUpsert() private→public so the Job can use it;
  elasticIndex() stub added (to be implemented by the ES ticket)
- php/tests/Feature/Jobs/EmbedMemoryTest.php — Pest tests for success
  path and Qdrant-failure path

Co-authored-by: Codex <noreply@openai.com>

Closes tasks.lthn.sh/view.php?id=56
2026-04-23 12:47:10 +01:00

100 lines
3.7 KiB
PHP

<?php
// SPDX-License-Identifier: EUPL-1.2
declare(strict_types=1);
use Core\Mod\Agentic\Jobs\EmbedMemory;
use Core\Mod\Agentic\Models\BrainMemory;
use Core\Mod\Agentic\Services\BrainService;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
function embedMemoryService(): BrainService
{
return new BrainService(
ollamaUrl: 'https://ollama.test',
qdrantUrl: 'https://qdrant.test',
collection: 'openbrain',
embeddingModel: 'embeddinggemma',
verifySsl: false,
);
}
test('EmbedMemory_handle_Good_embeds_upserts_and_marks_indexed', function (): void {
$workspace = createWorkspace();
$embedding = array_fill(0, 768, 0.125);
Http::fake([
'https://ollama.test/api/embeddings' => Http::response(['embedding' => $embedding]),
'https://qdrant.test/collections/openbrain/points' => Http::response(['result' => ['status' => 'ok']]),
]);
$memory = BrainMemory::create([
'workspace_id' => $workspace->id,
'agent_id' => 'virgil',
'type' => 'architecture',
'content' => 'Queue memory indexing through EmbedMemory.',
'tags' => ['brain', 'indexing'],
'project' => 'agent',
'confidence' => 0.95,
'source' => 'ticket-56',
]);
(new EmbedMemory($memory->id))->handle(embedMemoryService());
expect($memory->fresh()->indexed_at)->not->toBeNull();
Http::assertSent(fn (Request $request): bool => $request->url() === 'https://ollama.test/api/embeddings'
&& $request->method() === 'POST'
&& $request['model'] === 'embeddinggemma'
&& $request['prompt'] === 'Queue memory indexing through EmbedMemory.');
Http::assertSent(fn (Request $request): bool => $request->url() === 'https://qdrant.test/collections/openbrain/points'
&& $request->method() === 'PUT'
&& $request['points'][0]['id'] === $memory->id
&& $request['points'][0]['vector'] === $embedding
&& $request['points'][0]['payload']['workspace_id'] === $workspace->id
&& $request['points'][0]['payload']['project'] === 'agent'
&& $request['points'][0]['payload']['agent_id'] === 'virgil'
&& $request['points'][0]['payload']['type'] === 'architecture'
&& $request['points'][0]['payload']['tags'] === ['brain', 'indexing']
&& $request['points'][0]['payload']['confidence'] === 0.95
&& $request['points'][0]['payload']['source'] === 'ticket-56'
&& $request['points'][0]['payload']['content'] === 'Queue memory indexing through EmbedMemory.');
});
test('EmbedMemory_handle_Bad_returns_silently_when_memory_is_missing', function (): void {
Http::fake();
(new EmbedMemory(Str::uuid()->toString()))->handle(embedMemoryService());
Http::assertNothingSent();
});
test('EmbedMemory_handle_Ugly_leaves_memory_unindexed_when_qdrant_fails', function (): void {
$workspace = createWorkspace();
Http::fake([
'https://ollama.test/api/embeddings' => Http::response(['embedding' => array_fill(0, 768, 0.25)]),
'https://qdrant.test/collections/openbrain/points' => Http::response(['error' => 'unavailable'], 500),
]);
$memory = BrainMemory::create([
'workspace_id' => $workspace->id,
'agent_id' => 'virgil',
'type' => 'observation',
'content' => 'Qdrant failures should retry without marking the memory indexed.',
'confidence' => 0.8,
]);
try {
(new EmbedMemory($memory->id))->handle(embedMemoryService());
$this->fail('Expected Qdrant failure to throw.');
} catch (RuntimeException $exception) {
expect($exception->getMessage())->toBe('Qdrant upsert failed: 500');
}
expect($memory->fresh()->indexed_at)->toBeNull();
});