Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d6e08a4

Browse files
Implement prose tests for $lookup support
As laid out in the spec: mongodb/specifications@527e22d
1 parent 68f176a commit d6e08a4

File tree

3 files changed

+374
-2
lines changed

3 files changed

+374
-2
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\SpecTests\ClientSideEncryption;
4+
5+
use MongoDB\BSON\Binary;
6+
use MongoDB\Client;
7+
use PHPUnit\Framework\Attributes\Group;
8+
9+
use function base64_decode;
10+
use function file_get_contents;
11+
12+
/**
13+
* Prose test 25: Test lookup
14+
*
15+
* @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#25-test-lookup
16+
*/
17+
#[Group('csfle')]
18+
class Prose25_LookupTest extends FunctionalTestCase
19+
{
20+
private $key1Id;
21+
22+
private const COLL_CSFLE = 'csfle';
23+
private const COLL_CSFLE2 = 'csfle2';
24+
private const COLL_QE = 'qe';
25+
private const COLL_QE2 = 'qe2';
26+
private const COLL_NO_SCHEMA = 'no_schema';
27+
private const COLL_NO_SCHEMA2 = 'no_schema2';
28+
29+
private static string $dataDir = __DIR__ . '/../../specifications/source/client-side-encryption/etc/data/lookup';
30+
31+
public function setUp(): void
32+
{
33+
parent::setUp();
34+
35+
if ($this->isStandalone()) {
36+
$this->markTestSkipped('Lookup tests require replica sets');
37+
}
38+
39+
$this->skipIfServerVersion('<', '7.0.0', 'Lookup encryption tests require MongoDB 7.0 or later');
40+
41+
$key1Document = $this->decodeJson(file_get_contents(self::$dataDir . '/key-doc.json'));
42+
$this->key1Id = $key1Document->_id;
43+
44+
$encryptedClient = $this->getEncryptedClient();
45+
46+
// Drop the key vault collection and insert key1Document with a majority write concern
47+
self::insertKeyVaultData($encryptedClient, [$key1Document]);
48+
49+
$this->refreshCollections($encryptedClient);
50+
}
51+
52+
private function getEncryptedClient(): Client
53+
{
54+
$autoEncryptionOpts = [
55+
'keyVaultNamespace' => 'keyvault.datakeys',
56+
'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]],
57+
];
58+
59+
return self::createTestClient(null, [], [
60+
'autoEncryption' => $autoEncryptionOpts,
61+
/* libmongocrypt caches results from listCollections. Use a new
62+
* client in each test to ensure its encryptedFields is applied. */
63+
'disableClientPersistence' => true,
64+
]);
65+
}
66+
67+
private function refreshCollections(Client $client): void
68+
{
69+
$encryptedDb = $client->getDatabase(self::getDatabaseName());
70+
$unencryptedDb = self::createTestClient()->getDatabase(self::getDatabaseName());
71+
72+
$optionsMap = [
73+
self::COLL_CSFLE => [
74+
'validator' => [
75+
'$jsonSchema' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-csfle.json')),
76+
],
77+
],
78+
self::COLL_CSFLE2 => [
79+
'validator' => [
80+
'$jsonSchema' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-csfle2.json')),
81+
],
82+
],
83+
self::COLL_QE => [
84+
'encryptedFields' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-qe.json')),
85+
],
86+
self::COLL_QE2 => [
87+
'encryptedFields' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-qe2.json')),
88+
],
89+
self::COLL_NO_SCHEMA => [],
90+
self::COLL_NO_SCHEMA2 => [],
91+
];
92+
93+
foreach ($optionsMap as $collectionName => $options) {
94+
$encryptedDb->dropCollection($collectionName);
95+
$encryptedDb->createCollection($collectionName, $options);
96+
97+
$collection = $unencryptedDb->getCollection($collectionName);
98+
99+
$result = $encryptedDb->getCollection($collectionName)->insertOne([$collectionName => $collectionName]);
100+
101+
if ($options) {
102+
$document = $collection->findOne(['_id' => $result->getInsertedId()]);
103+
$this->assertInstanceOf(Binary::class, $document->{$collectionName});
104+
}
105+
}
106+
}
107+
108+
private function assertPipelineReturnsSingleDocument(string $collection, array $pipeline, array $expected): void
109+
{
110+
$this->skipIfServerVersion('<', '8.1.0', 'Lookup test case requires server version 8.1.0 or later');
111+
$this->skipIfClientSideEncryptionIsNotSupported();
112+
113+
$cursor = $this
114+
->getEncryptedClient()
115+
->getCollection(self::getDatabaseName(), $collection)
116+
->aggregate($pipeline);
117+
118+
$cursor->rewind();
119+
$this->assertMatchesDocument(
120+
$expected,
121+
$cursor->current(),
122+
);
123+
$this->assertNull($cursor->next());
124+
}
125+
126+
public function testCase1_CsfleJoinsNoSchema(): void
127+
{
128+
$pipeline = [
129+
[
130+
'$match' => ['csfle' => 'csfle'],
131+
],
132+
[
133+
'$lookup' => [
134+
'from' => 'no_schema',
135+
'as' => 'matched',
136+
'pipeline' => [
137+
[
138+
'$match' => ['no_schema' => 'no_schema'],
139+
],
140+
[
141+
'$project' => ['_id' => 0],
142+
],
143+
],
144+
],
145+
],
146+
[
147+
'$project' => ['_id' => 0],
148+
],
149+
];
150+
$expected = [
151+
'csfle' => 'csfle',
152+
'matched' => [
153+
['no_schema' => 'no_schema'],
154+
],
155+
];
156+
157+
$this->assertPipelineReturnsSingleDocument(self::COLL_CSFLE, $pipeline, $expected);
158+
}
159+
160+
public function testCase2_QeJoinsNoSchema(): void
161+
{
162+
$pipeline = [
163+
[
164+
'$match' => ['qe' => 'qe'],
165+
],
166+
[
167+
'$lookup' => [
168+
'from' => 'no_schema',
169+
'as' => 'matched',
170+
'pipeline' => [
171+
[
172+
'$match' => ['no_schema' => 'no_schema'],
173+
],
174+
[
175+
'$project' => [
176+
'_id' => 0,
177+
'__safeContent__' => 0,
178+
],
179+
],
180+
],
181+
],
182+
],
183+
[
184+
'$project' => [
185+
'_id' => 0,
186+
'__safeContent__' => 0,
187+
],
188+
],
189+
];
190+
$expected = [
191+
'qe' => 'qe',
192+
'matched' => [
193+
['no_schema' => 'no_schema'],
194+
],
195+
];
196+
197+
$this->assertPipelineReturnsSingleDocument(self::COLL_QE, $pipeline, $expected);
198+
}
199+
200+
public function testCase3_NoSchemaJoinsCsfle(): void
201+
{
202+
$pipeline = [['$match' => ['no_schema' => 'no_schema']],
203+
[
204+
'$lookup' => [
205+
'from' => 'csfle',
206+
'as' => 'matched',
207+
'pipeline' => [
208+
[
209+
'$match' => ['csfle' => 'csfle'],
210+
],
211+
[
212+
'$project' => ['_id' => 0],
213+
],
214+
],
215+
],
216+
],
217+
['$project' => ['_id' => 0]],
218+
];
219+
$expected = ['no_schema' => 'no_schema', 'matched' => [['csfle' => 'csfle']]];
220+
221+
$this->assertPipelineReturnsSingleDocument(self::COLL_NO_SCHEMA, $pipeline, $expected);
222+
}
223+
224+
public function testCase4_NoSchemaJoinsQe(): void
225+
{
226+
$pipeline = [
227+
[
228+
'$match' => ['no_schema' => 'no_schema'],
229+
],
230+
[
231+
'$lookup' => [
232+
'from' => 'qe',
233+
'as' => 'matched',
234+
'pipeline' => [
235+
[
236+
'$match' => ['qe' => 'qe'],
237+
],
238+
[
239+
'$project' => [
240+
'_id' => 0,
241+
'__safeContent__' => 0,
242+
],
243+
],
244+
],
245+
],
246+
],
247+
[
248+
'$project' => ['_id' => 0],
249+
],
250+
];
251+
$expected = [
252+
'no_schema' => 'no_schema',
253+
'matched' => [
254+
['qe' => 'qe'],
255+
],
256+
];
257+
258+
$this->assertPipelineReturnsSingleDocument(self::COLL_NO_SCHEMA, $pipeline, $expected);
259+
}
260+
261+
public function testCase5_CsfleJoinsCsfle2(): void
262+
{
263+
$pipeline = [
264+
['$match' => ['csfle' => 'csfle']],
265+
[
266+
'$lookup' => [
267+
'from' => 'csfle2',
268+
'as' => 'matched',
269+
'pipeline' => [
270+
[
271+
'$match' => ['csfle2' => 'csfle2'],
272+
],
273+
[
274+
'$project' => ['_id' => 0],
275+
],
276+
],
277+
],
278+
],
279+
['$project' => ['_id' => 0]],
280+
];
281+
$expected = ['csfle' => 'csfle', 'matched' => [['csfle2' => 'csfle2']]];
282+
283+
$this->assertPipelineReturnsSingleDocument(self::COLL_CSFLE, $pipeline, $expected);
284+
}
285+
286+
public function testCase6_QeJoinsQe2(): void
287+
{
288+
$pipeline = [
289+
['$match' => ['qe' => 'qe']],
290+
[
291+
'$lookup' => [
292+
'from' => 'qe2',
293+
'as' => 'matched',
294+
'pipeline' => [
295+
[
296+
'$match' => ['qe2' => 'qe2'],
297+
],
298+
[
299+
'$project' => [
300+
'_id' => 0,
301+
'__safeContent__' => 0,
302+
],
303+
],
304+
],
305+
],
306+
],
307+
['$project' => ['_id' => 0, '__safeContent__' => 0]],
308+
];
309+
$expected = ['qe' => 'qe', 'matched' => [['qe2' => 'qe2']]];
310+
311+
$this->assertPipelineReturnsSingleDocument(self::COLL_QE, $pipeline, $expected);
312+
}
313+
314+
public function testCase7_NoSchemaJoinsNoSchema2(): void
315+
{
316+
$pipeline = [
317+
['$match' => ['no_schema' => 'no_schema']],
318+
[
319+
'$lookup' => [
320+
'from' => 'no_schema2',
321+
'as' => 'matched',
322+
'pipeline' => [
323+
['$match' => ['no_schema2' => 'no_schema2']],
324+
['$project' => ['_id' => 0]],
325+
],
326+
],
327+
],
328+
['$project' => ['_id' => 0]],
329+
];
330+
$expected = ['no_schema' => 'no_schema', 'matched' => [['no_schema2' => 'no_schema2']]];
331+
332+
$this->assertPipelineReturnsSingleDocument(self::COLL_NO_SCHEMA, $pipeline, $expected);
333+
}
334+
335+
public function testCase8_CsfleJoinsQeFails(): void
336+
{
337+
$this->skipIfServerVersion('<', '8.1.0', 'Lookup test case requires server version 8.1.0 or later');
338+
$this->skipIfClientSideEncryptionIsNotSupported();
339+
340+
$this->expectExceptionMessage('not supported');
341+
342+
$this->getEncryptedClient()
343+
->getCollection(self::getDatabaseName(), self::COLL_CSFLE)
344+
->aggregate([
345+
[
346+
'$match' => ['csfle' => 'qe'],
347+
],
348+
[
349+
'$lookup' => [
350+
'from' => 'qe',
351+
'as' => 'matched',
352+
'pipeline' => [
353+
[
354+
'$match' => ['qe' => 'qe'],
355+
],
356+
[
357+
'$project' => ['_id' => 0],
358+
],
359+
],
360+
],
361+
],
362+
[
363+
'$project' => ['_id' => 0],
364+
],
365+
]);
366+
}
367+
368+
public function testCase9_TestErrorWithLessThan8_1(): void
369+
{
370+
$this->markTestSkipped('Depends on PHPC-2616 to determine crypt shared version.');
371+
}
372+
}

‎tests/drivers-evergreen-tools

‎tests/specifications

Submodule specifications updated 117 files

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /