Commit f2c10219 authored by Jeremy Mikola's avatar Jeremy Mikola

Merge pull request #28

parents 694c2117 424fc2cb
......@@ -28,6 +28,7 @@ install:
- sudo apt-get -y install gdb
before_script:
- phpenv config-rm xdebug.ini
- if dpkg --compare-versions ${SERVER_VERSION} le "2.4"; then export SERVER_SERVICE=mongodb; else export SERVER_SERVICE=mongod; fi
- if ! nc -z localhost 27017; then sudo service ${SERVER_SERVICE} start; fi
- mongod --version
......
......@@ -124,11 +124,11 @@ class Collection
* Gets the number of documents matching the filter.
*
* @see Count::__construct() for supported options
* @param array $filter Query by which to filter documents
* @param array $options Command options
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @return integer
*/
public function count(array $filter = array(), array $options = array())
public function count($filter = array(), array $options = array())
{
$operation = new Count($this->dbname, $this->collname, $filter, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
......@@ -228,11 +228,11 @@ class Collection
*
* @see Distinct::__construct() for supported options
* @param string $fieldName Field for which to return distinct values
* @param array $filter Query by which to filter documents
* @param array $options Command options
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @return mixed[]
*/
public function distinct($fieldName, array $filter = array(), array $options = array())
public function distinct($fieldName, $filter = array(), array $options = array())
{
$operation = new Distinct($this->dbname, $this->collname, $fieldName, $filter, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
......@@ -292,11 +292,11 @@ class Collection
*
* @see Find::__construct() for supported options
* @see http://docs.mongodb.org/manual/core/read-operations-introduction/
* @param array $filter Query by which to filter documents
* @param array $options Additional options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return Cursor
*/
public function find(array $filter = array(), array $options = array())
public function find($filter = array(), array $options = array())
{
$operation = new Find($this->dbname, $this->collname, $filter, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
......@@ -309,11 +309,11 @@ class Collection
*
* @see FindOne::__construct() for supported options
* @see http://docs.mongodb.org/manual/core/read-operations-introduction/
* @param array $filter The find query to execute
* @param array $options Additional options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return object|null
*/
public function findOne(array $filter = array(), array $options = array())
public function findOne($filter = array(), array $options = array())
{
$operation = new FindOne($this->dbname, $this->collname, $filter, $options);
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
......
......@@ -59,6 +59,24 @@ class Aggregate implements Executable
*/
public function __construct($databaseName, $collectionName, array $pipeline, array $options = array())
{
if (empty($pipeline)) {
throw new InvalidArgumentException('$pipeline is empty');
}
$expectedIndex = 0;
foreach ($pipeline as $i => $operation) {
if ($i !== $expectedIndex) {
throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($operation) && ! is_object($operation)) {
throw new InvalidArgumentTypeException(sprintf('$pipeline[%d]', $i), $operation, 'array or object');
}
$expectedIndex += 1;
}
$options += array(
'allowDiskUse' => false,
'useCursor' => true,
......@@ -84,20 +102,6 @@ class Aggregate implements Executable
throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false');
}
$expectedIndex = 0;
foreach ($pipeline as $i => $op) {
if ($i !== $expectedIndex) {
throw new InvalidArgumentException(sprintf('$pipeline is not a list (unexpected index: "%s")', $i));
}
if ( ! is_array($op) && ! is_object($op)) {
throw new InvalidArgumentTypeException(sprintf('$pipeline[%d]', $i), $op, 'array or object');
}
$expectedIndex += 1;
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->pipeline = $pipeline;
......
......@@ -45,8 +45,12 @@ class Count implements Executable
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, array $filter = array(), array $options = array())
public function __construct($databaseName, $collectionName, $filter = array(), array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if (isset($options['hint'])) {
if (is_array($options['hint']) || is_object($options['hint'])) {
$options['hint'] = \MongoDB\generate_index_name($options['hint']);
......
......@@ -39,8 +39,12 @@ class Distinct implements Executable
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $fieldName, array $filter = array(), array $options = array())
public function __construct($databaseName, $collectionName, $fieldName, $filter = array(), array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
......
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Operation\Aggregate;
class AggregateTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage $pipeline is empty
*/
public function testConstructorPipelineArgumentMustNotBeEmpty()
{
new Aggregate($this->getDatabaseName(), $this->getCollectionName(), array());
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage $pipeline is not a list (unexpected index: "1")
*/
public function testConstructorPipelineArgumentMustBeAList()
{
new Aggregate($this->getDatabaseName(), $this->getCollectionName(), array(1 => array('$match' => array('x' => 1))));
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOptionTypeChecks(array $options)
{
new Aggregate($this->getDatabaseName(), $this->getCollectionName(), array(array('$match' => array('x' => 1))), $options);
}
public function provideInvalidConstructorOptions()
{
$options = array();
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = array('allowDiskUse' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('batchSize' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('maxTimeMS' => $value);
}
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = array('useCursor' => $value);
}
return $options;
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage "batchSize" option should not be used if "useCursor" is false
*/
public function testConstructorBatchSizeOptionRequiresUseCursor()
{
new Aggregate(
$this->getDatabaseName(),
$this->getCollectionName(),
array(array('$match' => array('x' => 1))),
array('batchSize' => 100, 'useCursor' => false)
);
}
}
This diff is collapsed.
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Operation\Count;
class CountTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentTypeCheck($filter)
{
new Count($this->getDatabaseName(), $this->getCollectionName(), $filter);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOptionTypeChecks(array $options)
{
new Count($this->getDatabaseName(), $this->getCollectionName(), array(), $options);
}
public function provideInvalidConstructorOptions()
{
$options = array();
foreach ($this->getInvalidHintValues() as $value) {
$options[][] = array('hint' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('limit' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('maxTimeMS' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('skip' => $value);
}
return $options;
}
private function getInvalidHintValues()
{
return array(123, 3.14, true);
}
}
......@@ -8,27 +8,45 @@ class DeleteTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentType($filter)
public function testConstructorFilterArgumentTypeCheck($filter)
{
new Delete($this->getDatabaseName(), $this->getCollectionName(), $filter, 0);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @dataProvider provideInvalidDocumentArguments
* @expectedExceptionMessage $limit must be 0 or 1
* @dataProvider provideInvalidLimitValues
*/
public function testConstructorLimitArgumentMustBeOneOrZero()
public function testConstructorLimitArgumentMustBeOneOrZero($limit)
{
new Delete($this->getDatabaseName(), $this->getCollectionName(), array(), 2);
new Delete($this->getDatabaseName(), $this->getCollectionName(), array(), $limit);
}
public function provideInvalidLimitValues()
{
return $this->wrapValuesForDataProvider(array_merge($this->getInvalidIntegerValues(), array(-1, 2)));
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorWriteConcernOptionType()
public function testConstructorOptionTypeChecks(array $options)
{
new Delete($this->getDatabaseName(), $this->getCollectionName(), array(), 1, $options);
}
public function provideInvalidConstructorOptions()
{
new Delete($this->getDatabaseName(), $this->getCollectionName(), array(), 1, array('writeConcern' => null));
$options = array();
foreach ($this->getInvalidWriteConcernValues() as $value) {
$options[][] = array('writeConcern' => $value);
}
return $options;
}
}
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Operation\Distinct;
class DistinctTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentTypeCheck($filter)
{
new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', $filter);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOptionTypeChecks(array $options)
{
new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', array(), $options);
}
public function provideInvalidConstructorOptions()
{
$options = array();
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('maxTimeMS' => $value);
}
return $options;
}
}
<?php
namespace MongoDB\Tests\Operation;
use MongoDB\Operation\Find;
use stdClass;
class FindTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentTypeCheck($filter)
{
new Find($this->getDatabaseName(), $this->getCollectionName(), $filter);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOptionTypeChecks(array $options)
{
new Find($this->getDatabaseName(), $this->getCollectionName(), array(), $options);
}
public function provideInvalidConstructorOptions()
{
$options = array();
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = array('allowPartialResults' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('batchSize' => $value);
}
foreach ($this->getInvalidStringValues() as $value) {
$options[][] = array('comment' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('cursorType' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('limit' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('maxTimeMS' => $value);
}
foreach ($this->getInvalidDocumentValues() as $value) {
$options[][] = array('modifiers' => $value);
}
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = array('oplogReplay' => $value);
}
foreach ($this->getInvalidDocumentValues() as $value) {
$options[][] = array('projection' => $value);
}
foreach ($this->getInvalidIntegerValues() as $value) {
$options[][] = array('skip' => $value);
}
foreach ($this->getInvalidDocumentValues() as $value) {
$options[][] = array('sort' => $value);
}
return $options;
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @dataProvider provideInvalidConstructorCursorTypeOptions
*/
public function testConstructorCursorTypeOption($cursorType)
{
new Find($this->getDatabaseName(), $this->getCollectionName(), array(), array('cursorType' => $cursorType));
}
public function provideInvalidConstructorCursorTypeOptions()
{
return $this->wrapValuesForDataProvider(array(-1, 0, 4));
}
}
......@@ -8,6 +8,7 @@ class InsertManyTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage $documents is empty
*/
public function testConstructorDocumentsMustNotBeEmpty()
{
......@@ -16,6 +17,7 @@ class InsertManyTest extends TestCase
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage $documents is not a list (unexpected index: "1")
*/
public function testConstructorDocumentsMustBeAList()
{
......@@ -24,27 +26,35 @@ class InsertManyTest extends TestCase
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @expectedExceptionMessageRegExp /Expected \$documents\[0\] to have type "array or object" but found "[\w ]+"/
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorDocumentsElementType($document)
public function testConstructorDocumentsArgumentElementTypeChecks($document)
{
new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array($document));
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidBooleanArguments
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorOrderedOptionType($ordered)
public function testConstructorOptionTypeChecks(array $options)
{
new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array(array('x' => 1)), array('ordered' => $ordered));
new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array(array('x' => 1)), $options);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
*/
public function testConstructorWriteConcernOptionType()
public function provideInvalidConstructorOptions()
{
new InsertMany($this->getDatabaseName(), $this->getCollectionName(), array(array('x' => 1)), array('writeConcern' => null));
$options = array();
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = array('ordered' => $value);
}
foreach ($this->getInvalidWriteConcernValues() as $value) {
$options[][] = array('writeConcern' => $value);
}
return $options;
}
}
......@@ -8,18 +8,30 @@ class InsertOneTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorDocumentArgumentType($document)
public function testConstructorDocumentArgumentTypeCheck($document)
{
new InsertOne($this->getDatabaseName(), $this->getCollectionName(), $document);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorWriteConcernOptionType()
public function testConstructorOptionTypeChecks(array $options)
{
new InsertOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('writeConcern' => null));
new InsertOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $options);
}
public function provideInvalidConstructorOptions()
{
$options = array();
foreach ($this->getInvalidWriteConcernValues() as $value) {
$options[][] = array('writeConcern' => $value);
}
return $options;
}
}
......@@ -8,24 +8,25 @@ class ReplaceOneTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentType($filter)
public function testConstructorFilterArgumentTypeCheck($filter)
{
new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), $filter, array('y' => 1));
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorReplacementArgumentType($replacement)
public function testConstructorReplacementArgumentTypeCheck($replacement)
{
new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $replacement);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage First key in $replacement argument is an update operator
*/
public function testConstructorReplacementArgumentRequiresNoOperators()
{
......
......@@ -10,24 +10,43 @@ use stdClass;
*/
abstract class TestCase extends BaseTestCase
{
public function provideInvalidDocumentArguments()
public function provideInvalidDocumentValues()
{
return array(
array(null),
array(123),
array('foo'),
array(true),
);
return $this->wrapValuesForDataProvider($this->getInvalidDocumentValues());
}
public function provideInvalidBooleanArguments()
public function provideInvalidBooleanValues()
{
return array(
array(null),
array(123),
array('foo'),
array(array()),
array(new stdClass()),
);
return $this->wrapValuesForDataProvider($this->getInvalidBooleanValues());
}
protected function getInvalidBooleanValues()
{
return array(123, 3.14, 'foo', array(), new stdClass);
}
protected function getInvalidDocumentValues()
{
return array(123, 3.14, 'foo', true);
}
protected function getInvalidIntegerValues()
{
return array(3.14, 'foo', true, array(), new stdClass);
}
protected function getInvalidStringValues()
{
return array(123, 3.14, true, array(), new stdClass);
}
protected function getInvalidWriteConcernValues()
{
return array(123, 3.14, 'foo', true, array(), new stdClass);
}
protected function wrapValuesForDataProvider(array $values)
{
return array_map(function($value) { return array($value); }, $values);
}
}
......@@ -8,24 +8,25 @@ class UpdateManyTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentType($filter)
public function testConstructorFilterArgumentTypeCheck($filter)
{
new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), $filter, array('$set' => array('x' => 1)));
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorUpdateArgumentType($update)
public function testConstructorUpdateArgumentTypeCheck($update)
{
new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $update);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage First key in $update argument is not an update operator
*/
public function testConstructorUpdateArgumentRequiresOperators()
{
......
......@@ -8,24 +8,25 @@ class UpdateOneTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentType($filter)
public function testConstructorFilterArgumentTypeCheck($filter)
{
new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), $filter, array('$set' => array('x' => 1)));
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorUpdateArgumentType($update)
public function testConstructorUpdateArgumentTypeCheck($update)
{
new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $update);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentException
* @expectedExceptionMessage First key in $update argument is not an update operator
*/
public function testConstructorUpdateArgumentRequiresOperators()
{
......
......@@ -8,45 +8,49 @@ class UpdateTest extends TestCase
{
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @expectedExceptionMessageRegExp /Expected \$filter to have type "array or object" but found "[\w ]+"/
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorFilterArgumentType($filter)
public function testConstructorFilterArgumentTypeCheck($filter)
{
new Update($this->getDatabaseName(), $this->getCollectionName(), $filter, array('$set' => array('x' => 1)));
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDocumentArguments
* @expectedExceptionMessageRegExp /Expected \$update to have type "array or object" but found "[\w ]+"/
* @dataProvider provideInvalidDocumentValues
*/
public function testConstructorUpdateArgumentType($update)
public function testConstructorUpdateArgumentTypeCheck($update)
{
new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), $update);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidBooleanArguments
* @dataProvider provideInvalidConstructorOptions
*/
public function testConstructorMultiOptionType($multi)
public function testConstructorOptionTypeChecks(array $options)
{
new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1), array('multi' => $multi));
new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1), $options);
}
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidBooleanArguments
*/
public function testConstructorUpsertOptionType($upsert)
public function provideInvalidConstructorOptions()
{
new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1), array('upsert' => $upsert));
}
$options = array();
/**
* @expectedException MongoDB\Exception\InvalidArgumentTypeException
*/
public function testConstructorWriteConcernOptionType()
{
new Update($this->getDatabaseName(), $this->getCollectionName(), array('x' => 1), array('y' => 1), array('writeConcern' => null));
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = array('multi' => $value);
}
foreach ($this->getInvalidBooleanValues() as $value) {
$options[][] = array('upsert' => $value);
}
foreach ($this->getInvalidWriteConcernValues() as $value) {
$options[][] = array('writeConcern' => $value);
}
return $options;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment