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