'sk-test', 'services.aitunnel.base_url' => 'https://api.aitunnel.ru/v1']); Http::fake(['api.aitunnel.ru/*' => Http::response([ 'data' => [ ['index' => 0, 'embedding' => [0.1, 0.2, 0.3]], ['index' => 1, 'embedding' => [0.9, 0.8, 0.7]], ], ], 200)]); $vecs = app(AitunnelEmbedder::class)->embed(['ломбард', 'окна']); expect($vecs)->toBe([[0.1, 0.2, 0.3], [0.9, 0.8, 0.7]]); Http::assertSent(fn ($r) => str_contains($r->url(), '/embeddings') && $r->hasHeader('Authorization', 'Bearer sk-test') && $r['model'] === 'text-embedding-3-small'); }); it('без ключа → пустые векторы (движок отдаст 0% похожести)', function () { config(['services.aitunnel.key' => '']); expect(app(AitunnelEmbedder::class)->embed(['x', 'y']))->toBe([[], []]); }); it('ошибка сети → пустые векторы, не падаем', function () { config(['services.aitunnel.key' => 'sk']); Http::fake(fn () => throw new ConnectionException('timeout')); expect(app(AitunnelEmbedder::class)->embed(['x']))->toBe([[]]); }); it('пустой вход → пустой выход', function () { expect(app(AitunnelEmbedder::class)->embed([]))->toBe([]); });