MINI Sh3ll
<?php
namespace MongoDB\Tests\SpecTests;
use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Tests\CommandObserver;
use stdClass;
use function basename;
use function current;
use function explode;
use function file_get_contents;
use function glob;
use function parse_url;
/**
* Atlas Data Lake spec tests.
*
* @see https://github.com/mongodb/specifications/tree/master/source/atlas-data-lake-testing/tests
*/
class AtlasDataLakeSpecTest extends FunctionalTestCase
{
public function setUp(): void
{
parent::setUp();
if (! $this->isAtlasDataLake()) {
$this->markTestSkipped('Server is not Atlas Data Lake');
}
}
/**
* Assert that the expected and actual command documents match.
*
* @param stdClass $expected Expected command document
* @param stdClass $actual Actual command document
*/
public static function assertCommandMatches(stdClass $expected, stdClass $actual): void
{
foreach ($expected as $key => $value) {
if ($value === null) {
static::assertObjectNotHasAttribute($key, $actual);
unset($expected->{$key});
}
}
static::assertDocumentsMatch($expected, $actual);
}
/**
* Execute an individual test case from the specification.
*
* @dataProvider provideTests
* @param stdClass $test Individual "tests[]" document
* @param array $runOn Top-level "runOn" array with server requirements
* @param array $data Top-level "data" array to initialize collection
* @param string $databaseName Name of database under test
* @param string $collectionName Name of collection under test
*/
public function testAtlasDataLake(stdClass $test, ?array $runOn = null, array $data, ?string $databaseName = null, ?string $collectionName = null): void
{
if (isset($runOn)) {
$this->checkServerRequirements($runOn);
}
if (isset($test->skipReason)) {
$this->markTestSkipped($test->skipReason);
}
$databaseName = $databaseName ?? $this->getDatabaseName();
$collectionName = $collectionName ?? $this->getCollectionName();
$context = Context::fromCrud($test, $databaseName, $collectionName);
$this->setContext($context);
/* Note: Atlas Data Lake is read-only, so do not attempt to drop the
* collection under test or insert data fixtures. Necesarry data
* fixtures are already specified in the mongohoused configuration. */
if (isset($test->failPoint)) {
throw new LogicException('ADL tests are not expected to configure fail points');
}
if (isset($test->expectations)) {
$commandExpectations = CommandExpectations::fromCrud((array) $test->expectations);
$commandExpectations->startMonitoring();
}
foreach ($test->operations as $operation) {
Operation::fromCrud($operation)->assert($this, $context);
}
if (isset($commandExpectations)) {
$commandExpectations->stopMonitoring();
$commandExpectations->assert($this, $context);
}
if (isset($test->outcome->collection->data)) {
throw new LogicException('ADL tests are not expected to assert collection data');
}
}
public function provideTests()
{
$testArgs = [];
foreach (glob(__DIR__ . '/atlas_data_lake/*.json') as $filename) {
$json = $this->decodeJson(file_get_contents($filename));
$group = basename($filename, '.json');
$runOn = $json->runOn ?? null;
$data = $json->data ?? [];
// phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
$databaseName = $json->database_name ?? null;
$collectionName = $json->collection_name ?? null;
// phpcs:enable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
foreach ($json->tests as $test) {
$name = $group . ': ' . $test->description;
$testArgs[$name] = [$test, $runOn, $data, $databaseName, $collectionName];
}
}
return $testArgs;
}
/**
* Prose test 1: Connect without authentication
*/
public function testKillCursors(): void
{
$cursorId = null;
$cursorNamespace = null;
(new CommandObserver())->observe(
function (): void {
$client = static::createTestClient();
$client->test->driverdata->find([], ['batchSize' => 2, 'limit' => 3]);
},
function (array $event) use (&$cursorId, &$cursorNamespace): void {
if ($event['started']->getCommandName() === 'find') {
$this->assertArrayHasKey('succeeded', $event);
$reply = $event['succeeded']->getReply();
$this->assertObjectHasAttribute('cursor', $reply);
$this->assertIsObject($reply->cursor);
$this->assertObjectHasAttribute('id', $reply->cursor);
$this->assertIsInt($reply->cursor->id);
$this->assertObjectHasAttribute('ns', $reply->cursor);
$this->assertIsString($reply->cursor->ns);
/* Note: MongoDB\Driver\CursorId is not used here; however,
* we shouldn't have to worry about encoutnering a 64-bit
* cursor IDs on a 32-bit platform mongohoused allocates IDs
* sequentially (starting from 1). */
$cursorId = $reply->cursor->id;
$cursorNamespace = $reply->cursor->ns;
return;
}
/* After the initial find command, expect that killCursors is
* next and that a cursor ID and namespace were collected. */
$this->assertSame('killCursors', $event['started']->getCommandName());
$this->assertIsInt($cursorId);
$this->assertIsString($cursorNamespace);
[$databaseName, $collectionName] = explode('.', $cursorNamespace, 2);
$command = $event['started']->getCommand();
/* Assert that the killCursors command uses the namespace and
* cursor ID from the find command reply. */
$this->assertSame($databaseName, $event['started']->getDatabaseName());
$this->assertSame($databaseName, $command->{'$db'});
$this->assertObjectHasAttribute('killCursors', $command);
$this->assertSame($collectionName, $command->killCursors);
$this->assertObjectHasAttribute('cursors', $command);
$this->assertIsArray($command->cursors);
$this->assertArrayHasKey(0, $command->cursors);
$this->assertSame($cursorId, $command->cursors[0]);
/* Assert that the killCursors command reply indicates that the
* expected cursor ID was killed. */
$reply = $event['succeeded']->getReply();
$this->assertObjectHasAttribute('cursorsKilled', $reply);
$this->assertIsArray($reply->cursorsKilled);
$this->assertArrayHasKey(0, $reply->cursorsKilled);
$this->assertSame($cursorId, $reply->cursorsKilled[0]);
}
);
}
/**
* Prose test 2: Connect without authentication
*/
public function testConnectWithoutAuth(): void
{
/* Parse URI to remove userinfo component. The query string is left
* as-is and must not include authMechanism or credentials. */
$parts = parse_url(static::getUri());
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
$path = $parts['path'] ?? '/';
$query = isset($parts['query']) ? '?' . $parts['query'] : '';
$uri = $parts['scheme'] . '://' . $parts['host'] . $port . $path . $query;
$client = static::createTestClient($uri);
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);
$this->assertInstanceOf(Cursor::class, $cursor);
$this->assertCommandSucceeded(current($cursor->toArray()));
}
/**
* Prose test 3: Connect with SCRAM-SHA-1 authentication
*/
public function testConnectwithSCRAMSHA1(): void
{
$client = static::createTestClient(null, ['authMechanism' => 'SCRAM-SHA-1']);
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);
$this->assertInstanceOf(Cursor::class, $cursor);
$this->assertCommandSucceeded(current($cursor->toArray()));
}
/**
* Prose test 4: Connect with SCRAM-SHA-256 authentication
*/
public function testConnectwithSCRAMSHA256(): void
{
$client = static::createTestClient(null, ['authMechanism' => 'SCRAM-SHA-256']);
$cursor = $client->selectDatabase($this->getDatabaseName())->command(['ping' => 1]);
$this->assertInstanceOf(Cursor::class, $cursor);
$this->assertCommandSucceeded(current($cursor->toArray()));
}
private function isAtlasDataLake(): bool
{
$cursor = $this->manager->executeCommand(
$this->getDatabaseName(),
new Command(['buildInfo' => 1])
);
$document = current($cursor->toArray());
return ! empty($document->dataLake);
}
}
OHA YOOOO