TestCase.php 8.71 KB
Newer Older
1 2 3 4
<?php

namespace MongoDB\Tests;

5
use InvalidArgumentException;
6 7 8
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
9 10
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
11
use MongoDB\Tests\Compat\PolyfillAssertTrait;
12
use PHPUnit\Framework\TestCase as BaseTestCase;
13
use ReflectionClass;
14
use stdClass;
15
use Traversable;
16 17 18 19 20 21 22 23 24 25 26 27 28 29
use function array_map;
use function array_values;
use function call_user_func;
use function getenv;
use function hash;
use function is_array;
use function is_object;
use function iterator_to_array;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toJSON;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use const E_USER_DEPRECATED;
30

31
abstract class TestCase extends BaseTestCase
32
{
33 34
    use PolyfillAssertTrait;

35 36 37 38 39 40
    /**
     * Return the connection URI.
     *
     * @return string
     */
    public static function getUri()
41
    {
42
        return getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1:27017';
43 44
    }

45 46 47 48 49 50 51 52 53
    /**
     * Asserts that a document has expected values for some fields.
     *
     * Only fields in the expected document will be checked. The actual document
     * may contain additional fields.
     *
     * @param array|object $expectedDocument
     * @param array|object $actualDocument
     */
54
    public function assertMatchesDocument($expectedDocument, $actualDocument)
55 56 57 58 59 60 61 62 63
    {
        $normalizedExpectedDocument = $this->normalizeBSON($expectedDocument);
        $normalizedActualDocument = $this->normalizeBSON($actualDocument);

        $extraKeys = [];

        /* Avoid unsetting fields while we're iterating on the ArrayObject to
         * work around https://bugs.php.net/bug.php?id=70246 */
        foreach ($normalizedActualDocument as $key => $value) {
64
            if (! $normalizedExpectedDocument->offsetExists($key)) {
65 66 67 68 69 70 71 72 73
                $extraKeys[] = $key;
            }
        }

        foreach ($extraKeys as $key) {
            $normalizedActualDocument->offsetUnset($key);
        }

        $this->assertEquals(
74 75
            toJSON(fromPHP($normalizedExpectedDocument)),
            toJSON(fromPHP($normalizedActualDocument))
76 77 78 79 80 81 82 83 84 85 86 87
        );
    }

    /**
     * Asserts that a document has expected values for all fields.
     *
     * The actual document will be compared directly with the expected document
     * and may not contain extra fields.
     *
     * @param array|object $expectedDocument
     * @param array|object $actualDocument
     */
88
    public function assertSameDocument($expectedDocument, $actualDocument)
89 90
    {
        $this->assertEquals(
91 92
            toJSON(fromPHP($this->normalizeBSON($expectedDocument))),
            toJSON(fromPHP($this->normalizeBSON($actualDocument)))
93 94 95
        );
    }

96
    public function assertSameDocuments(array $expectedDocuments, $actualDocuments)
97 98 99 100 101
    {
        if ($actualDocuments instanceof Traversable) {
            $actualDocuments = iterator_to_array($actualDocuments);
        }

102
        if (! is_array($actualDocuments)) {
103 104 105
            throw new InvalidArgumentException('$actualDocuments is not an array or Traversable');
        }

106 107
        $normalizeRootDocuments = function ($document) {
            return toJSON(fromPHP($this->normalizeBSON($document)));
108 109 110 111 112 113 114 115
        };

        $this->assertEquals(
            array_map($normalizeRootDocuments, $expectedDocuments),
            array_map($normalizeRootDocuments, $actualDocuments)
        );
    }

116 117 118 119 120 121 122 123 124 125 126 127 128 129
    public function provideInvalidArrayValues()
    {
        return $this->wrapValuesForDataProvider($this->getInvalidArrayValues());
    }

    public function provideInvalidDocumentValues()
    {
        return $this->wrapValuesForDataProvider($this->getInvalidDocumentValues());
    }

    protected function assertDeprecated(callable $execution)
    {
        $errors = [];

130
        set_error_handler(function ($errno, $errstr) use (&$errors) {
131 132 133 134 135 136 137 138 139 140 141 142
            $errors[] = $errstr;
        }, E_USER_DEPRECATED);

        try {
            call_user_func($execution);
        } finally {
            restore_error_handler();
        }

        $this->assertCount(1, $errors);
    }

143 144 145 146 147
    /**
     * Return the test collection name.
     *
     * @return string
     */
148
    protected function getCollectionName()
149
    {
Jeremy Mikola's avatar
Jeremy Mikola committed
150
        $class = new ReflectionClass($this);
151

Jeremy Mikola's avatar
Jeremy Mikola committed
152
        return sprintf('%s.%s', $class->getShortName(), hash('crc32b', $this->getName()));
153 154 155 156 157 158 159
    }

    /**
     * Return the test database name.
     *
     * @return string
     */
160
    protected function getDatabaseName()
161 162 163 164
    {
        return getenv('MONGODB_DATABASE') ?: 'phplib_test';
    }

165 166 167 168 169 170 171
    /**
     * Return a list of invalid array values.
     *
     * @return array
     */
    protected function getInvalidArrayValues()
    {
172
        return [123, 3.14, 'foo', true, new stdClass()];
173 174 175 176 177 178 179 180 181
    }

    /**
     * Return a list of invalid boolean values.
     *
     * @return array
     */
    protected function getInvalidBooleanValues()
    {
182
        return [123, 3.14, 'foo', [], new stdClass()];
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    }

    /**
     * Return a list of invalid document values.
     *
     * @return array
     */
    protected function getInvalidDocumentValues()
    {
        return [123, 3.14, 'foo', true];
    }

    /**
     * Return a list of invalid integer values.
     *
     * @return array
     */
    protected function getInvalidIntegerValues()
    {
202
        return [3.14, 'foo', true, [], new stdClass()];
203 204
    }

205 206 207 208 209 210 211
    /**
     * Return a list of invalid ReadPreference values.
     *
     * @return array
     */
    protected function getInvalidReadConcernValues()
    {
212
        return [123, 3.14, 'foo', true, [], new stdClass(), new ReadPreference(ReadPreference::RP_PRIMARY), new WriteConcern(1)];
213 214
    }

215 216 217 218 219
    /**
     * Return a list of invalid ReadPreference values.
     *
     * @return array
     */
220 221
    protected function getInvalidReadPreferenceValues()
    {
222
        return [123, 3.14, 'foo', true, [], new stdClass(), new ReadConcern(), new WriteConcern(1)];
223 224
    }

225 226 227 228 229 230 231
    /**
     * Return a list of invalid Session values.
     *
     * @return array
     */
    protected function getInvalidSessionValues()
    {
232
        return [123, 3.14, 'foo', true, [], new stdClass(), new ReadConcern(), new ReadPreference(ReadPreference::RP_PRIMARY), new WriteConcern(1)];
233 234
    }

235 236 237 238 239 240 241
    /**
     * Return a list of invalid string values.
     *
     * @return array
     */
    protected function getInvalidStringValues()
    {
242
        return [123, 3.14, true, [], new stdClass()];
243 244 245 246 247 248 249
    }

    /**
     * Return a list of invalid WriteConcern values.
     *
     * @return array
     */
250 251
    protected function getInvalidWriteConcernValues()
    {
252
        return [123, 3.14, 'foo', true, [], new stdClass(), new ReadConcern(), new ReadPreference(ReadPreference::RP_PRIMARY)];
253 254
    }

255 256 257 258 259
    /**
     * Return the test namespace.
     *
     * @return string
     */
260
    protected function getNamespace()
261 262 263 264
    {
         return sprintf('%s.%s', $this->getDatabaseName(), $this->getCollectionName());
    }

265 266 267 268 269 270 271 272
    /**
     * Wrap a list of values for use as a single-argument data provider.
     *
     * @param array $values List of values
     * @return array
     */
    protected function wrapValuesForDataProvider(array $values)
    {
273 274 275
        return array_map(function ($value) {
            return [$value];
        }, $values);
276
    }
277 278 279 280 281 282 283 284 285 286 287 288 289 290

    /**
     * Normalizes a BSON document or array for use with assertEquals().
     *
     * The argument will be converted to a BSONArray or BSONDocument based on
     * its type and keys. Document fields will be sorted alphabetically. Each
     * value within the array or document will then be normalized recursively.
     *
     * @param array|object $bson
     * @return BSONDocument|BSONArray
     * @throws InvalidArgumentException if $bson is not an array or object
     */
    private function normalizeBSON($bson)
    {
291
        if (! is_array($bson) && ! is_object($bson)) {
292 293 294 295
            throw new InvalidArgumentException('$bson is not an array or object');
        }

        if ($bson instanceof BSONArray || (is_array($bson) && $bson === array_values($bson))) {
296
            if (! $bson instanceof BSONArray) {
297 298 299
                $bson = new BSONArray($bson);
            }
        } else {
300
            if (! $bson instanceof BSONDocument) {
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
                $bson = new BSONDocument((array) $bson);
            }

            $bson->ksort();
        }

        foreach ($bson as $key => $value) {
            if ($value instanceof BSONArray || (is_array($value) && $value === array_values($value))) {
                $bson[$key] = $this->normalizeBSON($value);
                continue;
            }

            if ($value instanceof stdClass || $value instanceof BSONDocument || is_array($value)) {
                $bson[$key] = $this->normalizeBSON($value);
                continue;
            }
        }

        return $bson;
    }
321
}