Skip to content

Commit f70ef1d

Browse files
committed
-
1 parent 865c2a3 commit f70ef1d

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

.github/workflows/integration-tests.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ jobs:
104104
composer-options: "--no-scripts"
105105
working-directory: demo
106106

107+
- name: Link local packages
108+
working-directory: demo
109+
run: ../link
110+
107111
- run: composer run-script auto-scripts --no-interaction
108112
working-directory: demo
109113

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Store\Tests\Command;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\AI\Platform\Vector\Vector;
16+
use Symfony\AI\Store\Command\RetrieveCommand;
17+
use Symfony\AI\Store\Document\Metadata;
18+
use Symfony\AI\Store\Document\VectorDocument;
19+
use Symfony\AI\Store\Exception\RuntimeException;
20+
use Symfony\AI\Store\RetrieverInterface;
21+
use Symfony\Component\Console\Tester\CommandTester;
22+
use Symfony\Component\DependencyInjection\ServiceLocator;
23+
use Symfony\Component\Uid\Uuid;
24+
25+
/**
26+
* @author Oskar Stark <[email protected]>
27+
*/
28+
final class RetrieveCommandTest extends TestCase
29+
{
30+
public function testCommandIsConfigured(): void
31+
{
32+
$command = new RetrieveCommand(new ServiceLocator([]));
33+
34+
$this->assertSame('ai:store:retrieve', $command->getName());
35+
$this->assertSame('Retrieve documents from a store', $command->getDescription());
36+
37+
$definition = $command->getDefinition();
38+
$this->assertTrue($definition->hasArgument('retriever'));
39+
$this->assertTrue($definition->hasArgument('query'));
40+
$this->assertTrue($definition->hasOption('limit'));
41+
42+
$retrieverArgument = $definition->getArgument('retriever');
43+
$this->assertSame('Name of the retriever to use', $retrieverArgument->getDescription());
44+
$this->assertTrue($retrieverArgument->isRequired());
45+
46+
$queryArgument = $definition->getArgument('query');
47+
$this->assertSame('Search query', $queryArgument->getDescription());
48+
$this->assertFalse($queryArgument->isRequired());
49+
50+
$limitOption = $definition->getOption('limit');
51+
$this->assertSame('Maximum number of results to return', $limitOption->getDescription());
52+
$this->assertSame('10', $limitOption->getDefault());
53+
}
54+
55+
public function testCommandCannotRetrieveFromNonExistingRetriever(): void
56+
{
57+
$command = new RetrieveCommand(new ServiceLocator([]));
58+
59+
$tester = new CommandTester($command);
60+
61+
$this->expectException(RuntimeException::class);
62+
$this->expectExceptionMessage('The "foo" retriever does not exist.');
63+
$tester->execute([
64+
'retriever' => 'foo',
65+
'query' => 'test query',
66+
]);
67+
}
68+
69+
public function testCommandCanRetrieveDocuments(): void
70+
{
71+
$metadata = new Metadata();
72+
$metadata->setText('Test document content');
73+
$metadata->setSource('test-source.txt');
74+
75+
$document = new VectorDocument(
76+
Uuid::v4(),
77+
new Vector([0.1, 0.2, 0.3]),
78+
$metadata,
79+
0.95,
80+
);
81+
82+
$retriever = $this->createMock(RetrieverInterface::class);
83+
$retriever->expects($this->once())
84+
->method('retrieve')
85+
->with('test query', ['maxItems' => 10])
86+
->willReturn([$document]);
87+
88+
$command = new RetrieveCommand(new ServiceLocator([
89+
'blog' => static fn (): RetrieverInterface => $retriever,
90+
]));
91+
92+
$tester = new CommandTester($command);
93+
$tester->execute([
94+
'retriever' => 'blog',
95+
'query' => 'test query',
96+
]);
97+
98+
$display = $tester->getDisplay();
99+
$this->assertStringContainsString('Retrieving documents using "blog" retriever', $display);
100+
$this->assertStringContainsString('Searching for: "test query"', $display);
101+
$this->assertStringContainsString('Result #1', $display);
102+
$this->assertStringContainsString('0.95', $display);
103+
$this->assertStringContainsString('test-source.txt', $display);
104+
$this->assertStringContainsString('Test document content', $display);
105+
$this->assertStringContainsString('Found 1 result(s) using "blog" retriever.', $display);
106+
}
107+
108+
public function testCommandCanRetrieveDocumentsWithCustomLimit(): void
109+
{
110+
$documents = [];
111+
for ($i = 0; $i < 3; ++$i) {
112+
$metadata = new Metadata();
113+
$metadata->setText('Document '.$i);
114+
$documents[] = new VectorDocument(
115+
Uuid::v4(),
116+
new Vector([0.1, 0.2, 0.3]),
117+
$metadata,
118+
0.9 - ($i * 0.1),
119+
);
120+
}
121+
122+
$retriever = $this->createMock(RetrieverInterface::class);
123+
$retriever->expects($this->once())
124+
->method('retrieve')
125+
->with('my query', ['maxItems' => 5])
126+
->willReturn($documents);
127+
128+
$command = new RetrieveCommand(new ServiceLocator([
129+
'products' => static fn (): RetrieverInterface => $retriever,
130+
]));
131+
132+
$tester = new CommandTester($command);
133+
$tester->execute([
134+
'retriever' => 'products',
135+
'query' => 'my query',
136+
'--limit' => '5',
137+
]);
138+
139+
$display = $tester->getDisplay();
140+
$this->assertStringContainsString('Result #1', $display);
141+
$this->assertStringContainsString('Result #2', $display);
142+
$this->assertStringContainsString('Result #3', $display);
143+
$this->assertStringContainsString('Found 3 result(s) using "products" retriever.', $display);
144+
}
145+
146+
public function testCommandShowsWarningWhenNoResultsFound(): void
147+
{
148+
$retriever = $this->createMock(RetrieverInterface::class);
149+
$retriever->expects($this->once())
150+
->method('retrieve')
151+
->with('unknown query', ['maxItems' => 10])
152+
->willReturn([]);
153+
154+
$command = new RetrieveCommand(new ServiceLocator([
155+
'articles' => static fn (): RetrieverInterface => $retriever,
156+
]));
157+
158+
$tester = new CommandTester($command);
159+
$tester->execute([
160+
'retriever' => 'articles',
161+
'query' => 'unknown query',
162+
]);
163+
164+
$display = $tester->getDisplay();
165+
$this->assertStringContainsString('No results found.', $display);
166+
}
167+
168+
public function testCommandThrowsExceptionOnRetrieverError(): void
169+
{
170+
$retriever = $this->createMock(RetrieverInterface::class);
171+
$retriever->expects($this->once())
172+
->method('retrieve')
173+
->willThrowException(new \Exception('Connection failed'));
174+
175+
$command = new RetrieveCommand(new ServiceLocator([
176+
'docs' => static fn (): RetrieverInterface => $retriever,
177+
]));
178+
179+
$tester = new CommandTester($command);
180+
181+
$this->expectException(RuntimeException::class);
182+
$this->expectExceptionMessage('An error occurred while retrieving with "docs": Connection failed');
183+
$tester->execute([
184+
'retriever' => 'docs',
185+
'query' => 'test',
186+
]);
187+
}
188+
189+
public function testCommandTruncatesLongText(): void
190+
{
191+
$longText = str_repeat('a', 300);
192+
$metadata = new Metadata();
193+
$metadata->setText($longText);
194+
195+
$document = new VectorDocument(
196+
Uuid::v4(),
197+
new Vector([0.1, 0.2, 0.3]),
198+
$metadata,
199+
0.8,
200+
);
201+
202+
$retriever = $this->createMock(RetrieverInterface::class);
203+
$retriever->expects($this->once())
204+
->method('retrieve')
205+
->willReturn([$document]);
206+
207+
$command = new RetrieveCommand(new ServiceLocator([
208+
'test' => static fn (): RetrieverInterface => $retriever,
209+
]));
210+
211+
$tester = new CommandTester($command);
212+
$tester->execute([
213+
'retriever' => 'test',
214+
'query' => 'search',
215+
]);
216+
217+
$display = $tester->getDisplay();
218+
$this->assertStringContainsString(str_repeat('a', 200).'...', $display);
219+
$this->assertStringNotContainsString(str_repeat('a', 201), $display);
220+
}
221+
222+
public function testCommandHandlesDocumentWithoutSourceOrText(): void
223+
{
224+
$document = new VectorDocument(
225+
Uuid::v4(),
226+
new Vector([0.1, 0.2, 0.3]),
227+
new Metadata(),
228+
0.75,
229+
);
230+
231+
$retriever = $this->createMock(RetrieverInterface::class);
232+
$retriever->expects($this->once())
233+
->method('retrieve')
234+
->willReturn([$document]);
235+
236+
$command = new RetrieveCommand(new ServiceLocator([
237+
'minimal' => static fn (): RetrieverInterface => $retriever,
238+
]));
239+
240+
$tester = new CommandTester($command);
241+
$tester->execute([
242+
'retriever' => 'minimal',
243+
'query' => 'test',
244+
]);
245+
246+
$display = $tester->getDisplay();
247+
$this->assertStringContainsString('Result #1', $display);
248+
$this->assertStringContainsString('0.75', $display);
249+
$this->assertStringContainsString('Found 1 result(s)', $display);
250+
}
251+
252+
public function testCommandHandlesDocumentWithoutScore(): void
253+
{
254+
$metadata = new Metadata();
255+
$metadata->setText('Some content');
256+
257+
$document = new VectorDocument(
258+
Uuid::v4(),
259+
new Vector([0.1, 0.2, 0.3]),
260+
$metadata,
261+
);
262+
263+
$retriever = $this->createMock(RetrieverInterface::class);
264+
$retriever->expects($this->once())
265+
->method('retrieve')
266+
->willReturn([$document]);
267+
268+
$command = new RetrieveCommand(new ServiceLocator([
269+
'noscore' => static fn (): RetrieverInterface => $retriever,
270+
]));
271+
272+
$tester = new CommandTester($command);
273+
$tester->execute([
274+
'retriever' => 'noscore',
275+
'query' => 'test',
276+
]);
277+
278+
$display = $tester->getDisplay();
279+
$this->assertStringContainsString('n/a', $display);
280+
}
281+
282+
public function testCommandRespectsLimit(): void
283+
{
284+
$documents = [];
285+
for ($i = 0; $i < 10; ++$i) {
286+
$metadata = new Metadata();
287+
$metadata->setText('Document '.$i);
288+
$documents[] = new VectorDocument(
289+
Uuid::v4(),
290+
new Vector([0.1, 0.2, 0.3]),
291+
$metadata,
292+
);
293+
}
294+
295+
$retriever = $this->createMock(RetrieverInterface::class);
296+
$retriever->expects($this->once())
297+
->method('retrieve')
298+
->with('test', ['maxItems' => 3])
299+
->willReturn($documents);
300+
301+
$command = new RetrieveCommand(new ServiceLocator([
302+
'many' => static fn (): RetrieverInterface => $retriever,
303+
]));
304+
305+
$tester = new CommandTester($command);
306+
$tester->execute([
307+
'retriever' => 'many',
308+
'query' => 'test',
309+
'--limit' => '3',
310+
]);
311+
312+
$display = $tester->getDisplay();
313+
$this->assertStringContainsString('Result #1', $display);
314+
$this->assertStringContainsString('Result #2', $display);
315+
$this->assertStringContainsString('Result #3', $display);
316+
$this->assertStringNotContainsString('Result #4', $display);
317+
$this->assertStringContainsString('Found 3 result(s)', $display);
318+
}
319+
}

0 commit comments

Comments
 (0)