8df90eb87782d3f270592072fe28e8f93d43cb11
[civicrm-core.git] / tests / phpunit / api / v4 / Action / BasicActionsTest.php
1 <?php
2
3 /*
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
6 | |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
11 */
12
13 /**
14 *
15 * @package CRM
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 */
18
19
20 namespace api\v4\Action;
21
22 use api\v4\UnitTestCase;
23 use Civi\Api4\MockBasicEntity;
24
25 /**
26 * @group headless
27 */
28 class BasicActionsTest extends UnitTestCase {
29
30 public function testCrud() {
31 MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
32
33 $id1 = MockBasicEntity::create()->addValue('foo', 'one')->execute()->first()['id'];
34
35 $result = MockBasicEntity::get()->execute();
36 $this->assertCount(1, $result);
37
38 $id2 = MockBasicEntity::create()->addValue('foo', 'two')->execute()->first()['id'];
39
40 $result = MockBasicEntity::get()->selectRowCount()->execute();
41 $this->assertEquals(2, $result->count());
42
43 MockBasicEntity::update()->addWhere('id', '=', $id2)->addValue('foo', 'new')->execute();
44
45 $result = MockBasicEntity::get()->addOrderBy('id', 'DESC')->setLimit(1)->execute();
46 // The object's count() method will account for all results, ignoring limit, while the array results are limited
47 $this->assertCount(2, $result);
48 $this->assertCount(1, (array) $result);
49 $this->assertEquals('new', $result->first()['foo']);
50
51 $result = MockBasicEntity::save()
52 ->addRecord(['id' => $id1, 'foo' => 'one updated', 'weight' => '5'])
53 ->addRecord(['id' => $id2, 'group:label' => 'Second'])
54 ->addRecord(['foo' => 'three'])
55 ->addDefault('color', 'pink')
56 ->setReload(TRUE)
57 ->execute()
58 ->indexBy('id');
59
60 $this->assertTrue(5 === $result[$id1]['weight']);
61 $this->assertEquals('new', $result[$id2]['foo']);
62 $this->assertEquals('two', $result[$id2]['group']);
63 $this->assertEquals('three', $result->last()['foo']);
64 $this->assertCount(3, $result);
65 foreach ($result as $item) {
66 $this->assertEquals('pink', $item['color']);
67 }
68
69 $ent1 = MockBasicEntity::get()->addWhere('id', '=', $id1)->execute()->first();
70 $this->assertEquals('one updated', $ent1['foo']);
71 $this->assertFalse(isset($ent1['group:label']));
72
73 $ent2 = MockBasicEntity::get()->addWhere('group:label', '=', 'Second')->addSelect('group:label', 'group')->execute()->first();
74 $this->assertEquals('two', $ent2['group']);
75 $this->assertEquals('Second', $ent2['group:label']);
76 // We didn't select this
77 $this->assertFalse(isset($ent2['group:name']));
78
79 // With no SELECT, all fields should be returned but not suffixy stuff like group:name
80 $ent2 = MockBasicEntity::get()->addWhere('group:label', '=', 'Second')->execute()->first();
81 $this->assertEquals('two', $ent2['group']);
82 $this->assertFalse(isset($ent2['group:name']));
83 // This one wasn't selected but did get used by the WHERE clause; ensure it isn't returned
84 $this->assertFalse(isset($ent2['group:label']));
85
86 MockBasicEntity::delete()->addWhere('id', '=', $id2);
87 $result = MockBasicEntity::get()->execute();
88 $this->assertEquals('one updated', $result->first()['foo']);
89 }
90
91 public function testReplace() {
92 MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
93
94 $objects = [
95 ['group' => 'one', 'color' => 'red'],
96 ['group' => 'one', 'color' => 'blue'],
97 ['group' => 'one', 'color' => 'green'],
98 ['group' => 'two', 'color' => 'orange'],
99 ];
100
101 foreach ($objects as &$object) {
102 $object['id'] = MockBasicEntity::create()->setValues($object)->execute()->first()['id'];
103 }
104
105 // Keep red, change blue, delete green, and add yellow
106 $replacements = [
107 ['color' => 'red', 'id' => $objects[0]['id']],
108 ['color' => 'not blue', 'id' => $objects[1]['id']],
109 ['color' => 'yellow'],
110 ];
111
112 MockBasicEntity::replace()->addWhere('group', '=', 'one')->setRecords($replacements)->execute();
113
114 $newObjects = MockBasicEntity::get()->addOrderBy('id', 'DESC')->execute()->indexBy('id');
115
116 $this->assertCount(4, $newObjects);
117
118 $this->assertEquals('yellow', $newObjects->first()['color']);
119
120 $this->assertEquals('not blue', $newObjects[$objects[1]['id']]['color']);
121
122 // Ensure group two hasn't been altered
123 $this->assertEquals('orange', $newObjects[$objects[3]['id']]['color']);
124 $this->assertEquals('two', $newObjects[$objects[3]['id']]['group']);
125 }
126
127 public function testBatchFrobnicate() {
128 MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
129
130 $objects = [
131 ['group' => 'one', 'color' => 'red', 'number' => 10],
132 ['group' => 'one', 'color' => 'blue', 'number' => 20],
133 ['group' => 'one', 'color' => 'green', 'number' => 30],
134 ['group' => 'two', 'color' => 'blue', 'number' => 40],
135 ];
136 foreach ($objects as &$object) {
137 $object['id'] = MockBasicEntity::create()->setValues($object)->execute()->first()['id'];
138 }
139
140 $result = MockBasicEntity::batchFrobnicate()->addWhere('color', '=', 'blue')->execute();
141 $this->assertEquals(2, count($result));
142 $this->assertEquals([400, 1600], \CRM_Utils_Array::collect('frobnication', (array) $result));
143 }
144
145 public function testGetFields() {
146 $getFields = MockBasicEntity::getFields()->execute()->indexBy('name');
147
148 $this->assertCount(7, $getFields);
149 $this->assertEquals('Id', $getFields['id']['title']);
150 // Ensure default data type is "String" when not specified
151 $this->assertEquals('String', $getFields['color']['data_type']);
152
153 // Getfields should default to loadOptions = false and reduce them to bool
154 $this->assertTrue($getFields['group']['options']);
155 $this->assertTrue($getFields['fruit']['options']);
156 $this->assertFalse($getFields['id']['options']);
157
158 // Load simple options
159 $getFields = MockBasicEntity::getFields()
160 ->addWhere('name', 'IN', ['group', 'fruit'])
161 ->setLoadOptions(TRUE)
162 ->execute()->indexBy('name');
163
164 $this->assertCount(2, $getFields);
165 $this->assertArrayHasKey('one', $getFields['group']['options']);
166 // Complex options should be reduced to simple array
167 $this->assertArrayHasKey(1, $getFields['fruit']['options']);
168 $this->assertEquals('Banana', $getFields['fruit']['options'][3]);
169
170 // Load complex options
171 $getFields = MockBasicEntity::getFields()
172 ->addWhere('name', 'IN', ['group', 'fruit'])
173 ->setLoadOptions(['id', 'name', 'label', 'color'])
174 ->execute()->indexBy('name');
175
176 // Simple options should be expanded to non-assoc array
177 $this->assertCount(2, $getFields);
178 $this->assertEquals('one', $getFields['group']['options'][0]['id']);
179 $this->assertEquals('First', $getFields['group']['options'][0]['name']);
180 $this->assertEquals('First', $getFields['group']['options'][0]['label']);
181 $this->assertFalse(isset($getFields['group']['options'][0]['color']));
182 // Complex options should give all requested properties
183 $this->assertEquals('Banana', $getFields['fruit']['options'][2]['label']);
184 $this->assertEquals('yellow', $getFields['fruit']['options'][2]['color']);
185 }
186
187 public function testItemsToGet() {
188 $get = MockBasicEntity::get()
189 ->addWhere('color', 'NOT IN', ['yellow'])
190 ->addWhere('color', 'IN', ['red', 'blue'])
191 ->addWhere('color', '!=', 'green')
192 ->addWhere('group', '=', 'one')
193 ->addWhere('size', 'LIKE', 'big')
194 ->addWhere('shape', 'LIKE', '%a');
195
196 $itemsToGet = new \ReflectionMethod($get, '_itemsToGet');
197 $itemsToGet->setAccessible(TRUE);
198
199 $this->assertEquals(['red', 'blue'], $itemsToGet->invoke($get, 'color'));
200 $this->assertEquals(['one'], $itemsToGet->invoke($get, 'group'));
201 $this->assertEquals(['big'], $itemsToGet->invoke($get, 'size'));
202 $this->assertEmpty($itemsToGet->invoke($get, 'shape'));
203 $this->assertEmpty($itemsToGet->invoke($get, 'weight'));
204 }
205
206 public function testFieldsToGet() {
207 $get = MockBasicEntity::get()
208 ->addWhere('color', '!=', 'green');
209
210 $isFieldSelected = new \ReflectionMethod($get, '_isFieldSelected');
211 $isFieldSelected->setAccessible(TRUE);
212
213 // If no "select" is set, should always return true
214 $this->assertTrue($isFieldSelected->invoke($get, 'color'));
215 $this->assertTrue($isFieldSelected->invoke($get, 'shape'));
216 $this->assertTrue($isFieldSelected->invoke($get, 'size', 'color', 'shape'));
217
218 // With a non-empty "select" fieldsToSelect() will return fields needed to evaluate each clause.
219 $get->addSelect('id');
220 $this->assertTrue($isFieldSelected->invoke($get, 'color', 'shape', 'size'));
221 $this->assertTrue($isFieldSelected->invoke($get, 'id'));
222 $this->assertFalse($isFieldSelected->invoke($get, 'shape', 'size', 'weight'));
223 $this->assertFalse($isFieldSelected->invoke($get, 'group'));
224
225 $get->addClause('OR', ['shape', '=', 'round'], ['AND', [['size', '=', 'big'], ['weight', '!=', 'small']]]);
226 $this->assertTrue($isFieldSelected->invoke($get, 'color'));
227 $this->assertTrue($isFieldSelected->invoke($get, 'id'));
228 $this->assertTrue($isFieldSelected->invoke($get, 'shape'));
229 $this->assertTrue($isFieldSelected->invoke($get, 'size'));
230 $this->assertTrue($isFieldSelected->invoke($get, 'group', 'weight'));
231 $this->assertFalse($isFieldSelected->invoke($get, 'group'));
232
233 $get->addOrderBy('group');
234 $this->assertTrue($isFieldSelected->invoke($get, 'group'));
235 }
236
237 public function testWildcardSelect() {
238 MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
239
240 $records = [
241 ['group' => 'one', 'color' => 'red', 'shape' => 'round', 'size' => 'med', 'weight' => 10],
242 ['group' => 'two', 'color' => 'blue', 'shape' => 'round', 'size' => 'med', 'weight' => 20],
243 ];
244 MockBasicEntity::save()->setRecords($records)->execute();
245
246 foreach (MockBasicEntity::get()->addSelect('*')->execute() as $result) {
247 ksort($result);
248 $this->assertEquals(['color', 'group', 'id', 'shape', 'size', 'weight'], array_keys($result));
249 }
250
251 $result = MockBasicEntity::get()
252 ->addSelect('*e', 'weig*ht')
253 ->execute()
254 ->first();
255 $this->assertEquals(['shape', 'size', 'weight'], array_keys($result));
256 }
257
258 public function testContainsOperator() {
259 MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
260
261 $records = [
262 ['group' => 'one', 'fruit:name' => ['apple', 'pear'], 'weight' => 11],
263 ['group' => 'two', 'fruit:name' => ['pear', 'banana'], 'weight' => 12],
264 ];
265 MockBasicEntity::save()->setRecords($records)->execute();
266
267 $result = MockBasicEntity::get()
268 ->addWhere('fruit:name', 'CONTAINS', 'apple')
269 ->execute();
270 $this->assertCount(1, $result);
271 $this->assertEquals('one', $result->first()['group']);
272
273 $result = MockBasicEntity::get()
274 ->addWhere('fruit:name', 'CONTAINS', 'pear')
275 ->execute();
276 $this->assertCount(2, $result);
277
278 $result = MockBasicEntity::get()
279 ->addWhere('group', 'CONTAINS', 'o')
280 ->execute();
281 $this->assertCount(2, $result);
282
283 $result = MockBasicEntity::get()
284 ->addWhere('weight', 'CONTAINS', 1)
285 ->execute();
286 $this->assertCount(2, $result);
287
288 $result = MockBasicEntity::get()
289 ->addWhere('fruit:label', 'CONTAINS', 'Banana')
290 ->execute();
291 $this->assertCount(1, $result);
292 $this->assertEquals('two', $result->first()['group']);
293
294 $result = MockBasicEntity::get()
295 ->addWhere('weight', 'CONTAINS', 2)
296 ->execute();
297 $this->assertCount(1, $result);
298 $this->assertEquals('two', $result->first()['group']);
299 }
300
301 public function testPseudoconstantMatch() {
302 MockBasicEntity::delete()->addWhere('id', '>', 0)->execute();
303
304 $records = [
305 ['group:label' => 'First', 'shape' => 'round', 'fruit:name' => 'banana'],
306 ['group:name' => 'Second', 'shape' => 'square', 'fruit:label' => 'Pear'],
307 ];
308 MockBasicEntity::save()->setRecords($records)->execute();
309
310 $results = MockBasicEntity::get()
311 ->addSelect('*', 'group:label', 'group:name', 'fruit:name', 'fruit:color', 'fruit:label')
312 ->addOrderBy('fruit:color', "DESC")
313 ->execute();
314
315 $this->assertEquals('round', $results[0]['shape']);
316 $this->assertEquals('one', $results[0]['group']);
317 $this->assertEquals('First', $results[0]['group:label']);
318 $this->assertEquals('First', $results[0]['group:name']);
319 $this->assertEquals(3, $results[0]['fruit']);
320 $this->assertEquals('Banana', $results[0]['fruit:label']);
321 $this->assertEquals('banana', $results[0]['fruit:name']);
322 $this->assertEquals('yellow', $results[0]['fruit:color']);
323
324 // Reverse order
325 $results = MockBasicEntity::get()
326 ->addOrderBy('fruit:color')
327 ->execute();
328 $this->assertEquals('two', $results[0]['group']);
329
330 // Cannot match to a non-unique option property like :color on create
331 try {
332 MockBasicEntity::create()->addValue('fruit:color', 'yellow')->execute();
333 }
334 catch (\API_Exception $createError) {
335 }
336 $this->assertContains('Illegal expression', $createError->getMessage());
337 }
338
339 }