Pull merge.
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Database / ConditionTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Database;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\Query\Condition;
7 use Drupal\Core\Database\Query\PlaceholderInterface;
8 use Drupal\Tests\UnitTestCase;
9 use Prophecy\Argument;
10
11 /**
12  * @coversDefaultClass \Drupal\Core\Database\Query\Condition
13  *
14  * @group Database
15  */
16 class ConditionTest extends UnitTestCase {
17
18   /**
19    * Provides a list of known operations and the expected output.
20    *
21    * @return array
22    *   - Expected result for the string version of the condition.
23    *   - The field name to input in the condition.
24    */
25   public function providerSimpleCondition() {
26     return [
27       ['name = :db_condition_placeholder_0', 'name'],
28       ['name123 = :db_condition_placeholder_0', 'name-123'],
29     ];
30   }
31
32   /**
33    * @covers ::compile
34    * @dataProvider providerSimpleCondition()
35    */
36   public function testSimpleCondition($expected, $field_name) {
37     $connection = $this->prophesize(Connection::class);
38     $connection->escapeField($field_name)->will(function ($args) {
39       return preg_replace('/[^A-Za-z0-9_.]+/', '', $args[0]);
40     });
41     $connection->mapConditionOperator('=')->willReturn(['operator' => '=']);
42     $connection = $connection->reveal();
43
44     $query_placeholder = $this->prophesize(PlaceholderInterface::class);
45
46     $counter = 0;
47     $query_placeholder->nextPlaceholder()->will(function () use (&$counter) {
48       return $counter++;
49     });
50     $query_placeholder->uniqueIdentifier()->willReturn(4);
51     $query_placeholder = $query_placeholder->reveal();
52
53     $condition = new Condition('AND');
54     $condition->condition($field_name, ['value']);
55     $condition->compile($connection, $query_placeholder);
56
57     $this->assertEquals($expected, $condition->__toString());
58     $this->assertEquals([':db_condition_placeholder_0' => 'value'], $condition->arguments());
59   }
60
61   /**
62    * @covers ::compile
63    *
64    * @dataProvider dataProviderTestCompileWithKnownOperators()
65    *
66    * @param string $expected
67    *   The expected generated SQL condition.
68    * @param string $field
69    *   The field to pass into the condition() method.
70    * @param mixed $value
71    *   The value to pass into the condition() method.
72    * @param string $operator
73    *   The operator to pass into the condition() method.
74    * @param mixed $expected_arguments
75    *   (optional) The expected set arguments.
76    */
77   public function testCompileWithKnownOperators($expected, $field, $value, $operator, $expected_arguments = NULL) {
78     $connection = $this->prophesize(Connection::class);
79     $connection->escapeField(Argument::any())->will(function ($args) {
80       return preg_replace('/[^A-Za-z0-9_.]+/', '', $args[0]);
81     });
82     $connection->mapConditionOperator(Argument::any())->willReturn(NULL);
83     $connection = $connection->reveal();
84
85     $query_placeholder = $this->prophesize(PlaceholderInterface::class);
86
87     $counter = 0;
88     $query_placeholder->nextPlaceholder()->will(function () use (&$counter) {
89       return $counter++;
90     });
91     $query_placeholder->uniqueIdentifier()->willReturn(4);
92     $query_placeholder = $query_placeholder->reveal();
93
94     $condition = new Condition('AND');
95     $condition->condition($field, $value, $operator);
96     $condition->compile($connection, $query_placeholder);
97
98     $this->assertEquals($expected, $condition->__toString());
99     if (isset($expected_arguments)) {
100       $this->assertEquals($expected_arguments, $condition->arguments());
101     }
102   }
103
104   /**
105    * Provides a list of known operations and the expected output.
106    *
107    * @return array
108    */
109   public function dataProviderTestCompileWithKnownOperators() {
110     // Below are a list of commented out test cases, which should work but
111     // aren't directly supported by core, but instead need manual handling with
112     // prefix/suffix at the moment.
113     $data = [];
114     $data[] = ['name = :db_condition_placeholder_0', 'name', 'value', '='];
115     $data[] = ['name != :db_condition_placeholder_0', 'name', 'value', '!='];
116     $data[] = ['name <> :db_condition_placeholder_0', 'name', 'value', '<>'];
117     $data[] = ['name >= :db_condition_placeholder_0', 'name', 'value', '>='];
118     $data[] = ['name > :db_condition_placeholder_0', 'name', 'value', '>'];
119     $data[] = ['name <= :db_condition_placeholder_0', 'name', 'value', '<='];
120     $data[] = ['name < :db_condition_placeholder_0', 'name', 'value', '<'];
121     // $data[] = ['GREATEST (1, 2, 3)', '', [1, 2, 3], 'GREATEST'];
122     $data[] = ['name IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)', 'name', ['1', '2', '3'], 'IN'];
123     $data[] = ['name NOT IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2)', 'name', ['1', '2', '3'], 'NOT IN'];
124     // $data[] = ['INTERVAL (1, 2, 3)', '', [1, 2, 3], 'INTERVAL'];
125     $data[] = ['name IS NULL', 'name', NULL, 'IS NULL'];
126     $data[] = ['name IS NOT NULL', 'name', NULL, 'IS NOT NULL'];
127     $data[] = ['name IS :db_condition_placeholder_0', 'name', 'TRUE', 'IS'];
128     // $data[] = ['LEAST (1, 2, 3)', '', [1, 2, 3], 'LEAST'];
129     $data[] = ["name LIKE :db_condition_placeholder_0 ESCAPE '\\\\'", 'name', '%muh%', 'LIKE', [':db_condition_placeholder_0' => '%muh%']];
130     $data[] = ["name NOT LIKE :db_condition_placeholder_0 ESCAPE '\\\\'", 'name', '%muh%', 'NOT LIKE', [':db_condition_placeholder_0' => '%muh%']];
131     $data[] = ["name BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1", 'name', [1, 2], 'BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]];
132     $data[] = ["name NOT BETWEEN :db_condition_placeholder_0 AND :db_condition_placeholder_1", 'name', [1, 2], 'NOT BETWEEN', [':db_condition_placeholder_0' => 1, ':db_condition_placeholder_1' => 2]];
133     // $data[] = ['STRCMP (name, :db_condition_placeholder_0)', '', ['test-string'], 'STRCMP', [':db_condition_placeholder_0' => 'test-string']];
134     // $data[] = ['EXISTS', '', NULL, 'EXISTS'];
135     // $data[] = ['name NOT EXISTS', 'name', NULL, 'NOT EXISTS'];
136
137     return $data;
138   }
139
140   /**
141    * @covers ::compile
142    *
143    * @dataProvider providerTestCompileWithSqlInjectionForOperator
144    */
145   public function testCompileWithSqlInjectionForOperator($operator) {
146     $connection = $this->prophesize(Connection::class);
147     $connection->escapeField(Argument::any())->will(function ($args) {
148       return preg_replace('/[^A-Za-z0-9_.]+/', '', $args[0]);
149     });
150     $connection->mapConditionOperator(Argument::any())->willReturn(NULL);
151     $connection = $connection->reveal();
152
153     $query_placeholder = $this->prophesize(PlaceholderInterface::class);
154
155     $counter = 0;
156     $query_placeholder->nextPlaceholder()->will(function () use (&$counter) {
157       return $counter++;
158     });
159     $query_placeholder->uniqueIdentifier()->willReturn(4);
160     $query_placeholder = $query_placeholder->reveal();
161
162     $condition = new Condition('AND');
163     $condition->condition('name', 'value', $operator);
164     $this->setExpectedException(\PHPUnit_Framework_Error::class);
165     $condition->compile($connection, $query_placeholder);
166   }
167
168   public function providerTestCompileWithSqlInjectionForOperator() {
169     $data = [];
170     $data[] = ["IS NOT NULL) ;INSERT INTO {test} (name) VALUES ('test12345678'); -- "];
171     $data[] = ["IS NOT NULL) UNION ALL SELECT name, pass FROM {users_field_data} -- "];
172     $data[] = ["IS NOT NULL) UNION ALL SELECT name FROM {TEST_UPPERCASE} -- "];
173     $data[] = ["= 1 UNION ALL SELECT password FROM user WHERE uid ="];
174
175     return $data;
176   }
177
178 }