CustomGroup - Add metadata about how a custom group relates to entity types
[civicrm-core.git] / tests / phpunit / api / v4 / Action / BasicCustomFieldTest.php
CommitLineData
19b53e5b
C
1<?php
2
380f3545
TO
3/*
4 +--------------------------------------------------------------------+
7d61e75f 5 | Copyright CiviCRM LLC. All rights reserved. |
380f3545 6 | |
7d61e75f
TO
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 |
380f3545
TO
10 +--------------------------------------------------------------------+
11 */
12
13/**
14 *
15 * @package CRM
ca5cec67 16 * @copyright CiviCRM LLC https://civicrm.org/licensing
380f3545
TO
17 */
18
19
19b53e5b
C
20namespace api\v4\Action;
21
22use Civi\Api4\Contact;
23use Civi\Api4\CustomField;
24use Civi\Api4\CustomGroup;
ab8c864e 25use Civi\Api4\OptionGroup;
cbda9790
CW
26use Civi\Api4\Relationship;
27use Civi\Api4\RelationshipCache;
19b53e5b
C
28
29/**
30 * @group headless
31 */
32class BasicCustomFieldTest extends BaseCustomValueTest {
33
06b84fc8
EM
34 /**
35 * @throws \API_Exception
36 */
37 public function testWithSingleField(): void {
fe806431 38 $customGroup = CustomGroup::create(FALSE)
ed3f5877 39 ->addValue('title', 'MyIndividualFields')
c752d94b 40 ->addValue('extends', 'Individual')
19b53e5b
C
41 ->execute()
42 ->first();
43
fe806431 44 CustomField::create(FALSE)
19b53e5b
C
45 ->addValue('label', 'FavColor')
46 ->addValue('custom_group_id', $customGroup['id'])
47 ->addValue('html_type', 'Text')
48 ->addValue('data_type', 'String')
49 ->execute();
50
c752d94b 51 // Individual fields should show up when contact_type = null|Individual but not other contact types
fe806431 52 $getFields = Contact::getFields(FALSE);
a1415a02 53 $this->assertEquals('Custom', $getFields->execute()->indexBy('name')['MyIndividualFields.FavColor']['type']);
c752d94b 54 $this->assertContains('MyIndividualFields.FavColor', $getFields->setValues(['contact_type' => 'Individual'])->execute()->column('name'));
37836cb1 55 $this->assertNotContains('MyIndividualFields.FavColor', $getFields->setValues(['contact_type:name' => 'Household'])->execute()->column('name'));
c752d94b 56
fe806431 57 $contactId = Contact::create(FALSE)
19b53e5b
C
58 ->addValue('first_name', 'Johann')
59 ->addValue('last_name', 'Tester')
60 ->addValue('contact_type', 'Individual')
c752d94b 61 ->addValue('MyIndividualFields.FavColor', 'Red')
19b53e5b
C
62 ->execute()
63 ->first()['id'];
64
fe806431 65 $contact = Contact::get(FALSE)
19b53e5b 66 ->addSelect('first_name')
c752d94b 67 ->addSelect('MyIndividualFields.FavColor')
19b53e5b 68 ->addWhere('id', '=', $contactId)
c752d94b 69 ->addWhere('MyIndividualFields.FavColor', '=', 'Red')
19b53e5b
C
70 ->execute()
71 ->first();
72
c752d94b 73 $this->assertEquals('Red', $contact['MyIndividualFields.FavColor']);
19b53e5b
C
74
75 Contact::update()
76 ->addWhere('id', '=', $contactId)
c752d94b 77 ->addValue('MyIndividualFields.FavColor', 'Blue')
19b53e5b
C
78 ->execute();
79
fe806431 80 $contact = Contact::get(FALSE)
c752d94b 81 ->addSelect('MyIndividualFields.FavColor')
19b53e5b
C
82 ->addWhere('id', '=', $contactId)
83 ->execute()
84 ->first();
85
c752d94b 86 $this->assertEquals('Blue', $contact['MyIndividualFields.FavColor']);
540c0ec9
CW
87
88 // Try setting to null
89 Contact::update()
90 ->addWhere('id', '=', $contactId)
91 ->addValue('MyIndividualFields.FavColor', NULL)
92 ->execute();
93 $contact = Contact::get(FALSE)
94 ->addSelect('MyIndividualFields.FavColor')
95 ->addWhere('id', '=', $contactId)
96 ->execute()
97 ->first();
98 $this->assertEquals(NULL, $contact['MyIndividualFields.FavColor']);
19b53e5b
C
99 }
100
101 public function testWithTwoFields() {
ab8c864e 102 $optionGroupCount = OptionGroup::get(FALSE)->selectRowCount()->execute()->count();
19b53e5b 103
c9e3994d 104 // First custom set
fe806431 105 CustomGroup::create(FALSE)
ed3f5877 106 ->addValue('title', 'MyContactFields')
19b53e5b 107 ->addValue('extends', 'Contact')
c9e3994d
CW
108 ->addChain('field1', CustomField::create()
109 ->addValue('label', 'FavColor')
110 ->addValue('custom_group_id', '$id')
111 ->addValue('html_type', 'Text')
112 ->addValue('data_type', 'String'))
113 ->addChain('field2', CustomField::create()
114 ->addValue('label', 'FavFood')
115 ->addValue('custom_group_id', '$id')
116 ->addValue('html_type', 'Text')
117 ->addValue('data_type', 'String'))
19b53e5b
C
118 ->execute();
119
c9e3994d 120 // Second custom set
fe806431 121 CustomGroup::create(FALSE)
ed3f5877 122 ->addValue('title', 'MyContactFields2')
c9e3994d
CW
123 ->addValue('extends', 'Contact')
124 ->addChain('field1', CustomField::create()
125 ->addValue('label', 'FavColor')
126 ->addValue('custom_group_id', '$id')
127 ->addValue('html_type', 'Text')
128 ->addValue('data_type', 'String'))
129 ->addChain('field2', CustomField::create()
130 ->addValue('label', 'FavFood')
131 ->addValue('custom_group_id', '$id')
132 ->addValue('html_type', 'Text')
f8d39baf 133 ->addValue('is_required', TRUE)
c9e3994d 134 ->addValue('data_type', 'String'))
19b53e5b
C
135 ->execute();
136
ab8c864e
CW
137 // Test that no new option groups have been created (these are text fields with no options)
138 $this->assertEquals($optionGroupCount, OptionGroup::get(FALSE)->selectRowCount()->execute()->count());
139
f8d39baf
CW
140 // Check getFields output
141 $fields = Contact::getFields(FALSE)->execute()->indexBy('name');
142 $this->assertFalse($fields['MyContactFields2.FavColor']['required']);
143 $this->assertTRUE($fields['MyContactFields2.FavColor']['nullable']);
144 // Custom fields are never actually *required* in the api, even if is_required = true
145 $this->assertFalse($fields['MyContactFields2.FavFood']['required']);
146 // But the api will report is_required as not nullable
147 $this->assertFalse($fields['MyContactFields2.FavFood']['nullable']);
148
fe806431 149 $contactId1 = Contact::create(FALSE)
19b53e5b
C
150 ->addValue('first_name', 'Johann')
151 ->addValue('last_name', 'Tester')
152 ->addValue('MyContactFields.FavColor', 'Red')
153 ->addValue('MyContactFields.FavFood', 'Cherry')
154 ->execute()
155 ->first()['id'];
156
fe806431 157 $contactId2 = Contact::create(FALSE)
19b53e5b
C
158 ->addValue('first_name', 'MaryLou')
159 ->addValue('last_name', 'Tester')
160 ->addValue('MyContactFields.FavColor', 'Purple')
161 ->addValue('MyContactFields.FavFood', 'Grapes')
162 ->execute()
163 ->first()['id'];
164
fe806431 165 $contact = Contact::get(FALSE)
19b53e5b
C
166 ->addSelect('first_name')
167 ->addSelect('MyContactFields.FavColor')
168 ->addSelect('MyContactFields.FavFood')
169 ->addWhere('id', '=', $contactId1)
170 ->addWhere('MyContactFields.FavColor', '=', 'Red')
171 ->addWhere('MyContactFields.FavFood', '=', 'Cherry')
172 ->execute()
173 ->first();
19b53e5b
C
174 $this->assertArrayHasKey('MyContactFields.FavColor', $contact);
175 $this->assertEquals('Red', $contact['MyContactFields.FavColor']);
176
2f69b203
CW
177 // By default custom fields are not returned
178 $contact = Contact::get(FALSE)
179 ->addWhere('id', '=', $contactId1)
180 ->addWhere('MyContactFields.FavColor', '=', 'Red')
181 ->addWhere('MyContactFields.FavFood', '=', 'Cherry')
182 ->execute()
183 ->first();
184 $this->assertArrayNotHasKey('MyContactFields.FavColor', $contact);
185
c9e3994d 186 // Update 2nd set and ensure 1st hasn't changed
19b53e5b
C
187 Contact::update()
188 ->addWhere('id', '=', $contactId1)
c9e3994d
CW
189 ->addValue('MyContactFields2.FavColor', 'Orange')
190 ->addValue('MyContactFields2.FavFood', 'Tangerine')
19b53e5b 191 ->execute();
fe806431 192 $contact = Contact::get(FALSE)
c9e3994d 193 ->addSelect('MyContactFields.FavColor', 'MyContactFields2.FavColor', 'MyContactFields.FavFood', 'MyContactFields2.FavFood')
19b53e5b
C
194 ->addWhere('id', '=', $contactId1)
195 ->execute()
196 ->first();
c9e3994d
CW
197 $this->assertEquals('Red', $contact['MyContactFields.FavColor']);
198 $this->assertEquals('Orange', $contact['MyContactFields2.FavColor']);
199 $this->assertEquals('Cherry', $contact['MyContactFields.FavFood']);
200 $this->assertEquals('Tangerine', $contact['MyContactFields2.FavFood']);
19b53e5b 201
c9e3994d
CW
202 // Update 1st set and ensure 2st hasn't changed
203 Contact::update()
204 ->addWhere('id', '=', $contactId1)
205 ->addValue('MyContactFields.FavColor', 'Blue')
206 ->execute();
fe806431 207 $contact = Contact::get(FALSE)
2f69b203 208 ->addSelect('custom.*')
c9e3994d
CW
209 ->addWhere('id', '=', $contactId1)
210 ->execute()
211 ->first();
19b53e5b 212 $this->assertEquals('Blue', $contact['MyContactFields.FavColor']);
c9e3994d
CW
213 $this->assertEquals('Orange', $contact['MyContactFields2.FavColor']);
214 $this->assertEquals('Cherry', $contact['MyContactFields.FavFood']);
215 $this->assertEquals('Tangerine', $contact['MyContactFields2.FavFood']);
19b53e5b 216
fe806431 217 $search = Contact::get(FALSE)
19b53e5b
C
218 ->addClause('OR', ['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes'])
219 ->addSelect('id')
220 ->addOrderBy('id')
221 ->execute()
222 ->indexBy('id');
223
224 $this->assertEquals([$contactId1, $contactId2], array_keys((array) $search));
225
fe806431 226 $search = Contact::get(FALSE)
19b53e5b
C
227 ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes'])
228 ->addSelect('id')
229 ->addOrderBy('id')
230 ->execute()
231 ->indexBy('id');
232
233 $this->assertNotContains($contactId2, array_keys((array) $search));
234
fe806431 235 $search = Contact::get(FALSE)
19b53e5b
C
236 ->addClause('NOT', ['MyContactFields.FavColor', '=', 'Purple'], ['MyContactFields.FavFood', '=', 'Grapes'])
237 ->addSelect('id')
238 ->addOrderBy('id')
239 ->execute()
240 ->indexBy('id');
241
242 $this->assertContains($contactId1, array_keys((array) $search));
243 $this->assertNotContains($contactId2, array_keys((array) $search));
244
fe806431 245 $search = Contact::get(FALSE)
19b53e5b
C
246 ->setWhere([['NOT', ['OR', [['MyContactFields.FavColor', '=', 'Blue'], ['MyContactFields.FavFood', '=', 'Grapes']]]]])
247 ->addSelect('id')
248 ->addOrderBy('id')
249 ->execute()
250 ->indexBy('id');
251
252 $this->assertNotContains($contactId1, array_keys((array) $search));
253 $this->assertNotContains($contactId2, array_keys((array) $search));
254 }
255
cbda9790
CW
256 public function testRelationshipCacheCustomFields() {
257 $cgName = uniqid('RelFields');
258
259 $customGroup = CustomGroup::create(FALSE)
ed3f5877 260 ->addValue('title', $cgName)
cbda9790
CW
261 ->addValue('extends', 'Relationship')
262 ->execute()
263 ->first();
264
265 CustomField::create(FALSE)
266 ->addValue('label', 'PetName')
267 ->addValue('custom_group_id', $customGroup['id'])
268 ->addValue('html_type', 'Text')
269 ->addValue('data_type', 'String')
270 ->execute();
271
07e7a46b
CW
272 // Adding custom field to Relationship entity also adds it to RelationshipCache entity
273 $this->assertCount(1, RelationshipCache::getFields(FALSE)
274 ->addWhere('name', '=', "$cgName.PetName")
275 ->execute()
276 );
277
cbda9790
CW
278 $parent = Contact::create(FALSE)
279 ->addValue('first_name', 'Parent')
280 ->addValue('last_name', 'Tester')
281 ->addValue('contact_type', 'Individual')
282 ->execute()
283 ->first()['id'];
284
285 $child = Contact::create(FALSE)
286 ->addValue('first_name', 'Child')
287 ->addValue('last_name', 'Tester')
288 ->addValue('contact_type', 'Individual')
289 ->execute()
290 ->first()['id'];
291
07e7a46b 292 Relationship::create(FALSE)
cbda9790
CW
293 ->addValue('contact_id_a', $parent)
294 ->addValue('contact_id_b', $child)
295 ->addValue('relationship_type_id', 1)
296 ->addValue("$cgName.PetName", 'Buddy')
297 ->execute();
298
07e7a46b 299 // Test get directly from relationshipCache entity
cbda9790
CW
300 $results = RelationshipCache::get(FALSE)
301 ->addSelect("$cgName.PetName")
302 ->addWhere("$cgName.PetName", '=', 'Buddy')
303 ->execute();
304
305 $this->assertCount(2, $results);
306 $this->assertEquals('Buddy', $results[0]["$cgName.PetName"]);
07e7a46b
CW
307
308 // Test get via bridge INNER join
309 $result = Contact::get(FALSE)
310 ->addSelect('relative.display_name', "relative.$cgName.PetName")
311 ->addJoin('Contact AS relative', 'INNER', 'RelationshipCache')
312 ->addWhere('id', '=', $parent)
313 ->addWhere('relative.relationship_type_id', '=', 1)
314 ->execute()->single();
315 $this->assertEquals('Child Tester', $result['relative.display_name']);
316 $this->assertEquals('Buddy', $result["relative.$cgName.PetName"]);
317
318 // Test get via bridge LEFT join
319 $result = Contact::get(FALSE)
320 ->addSelect('relative.display_name', "relative.$cgName.PetName")
321 ->addJoin('Contact AS relative', 'LEFT', 'RelationshipCache')
322 ->addWhere('id', '=', $parent)
323 ->addWhere('relative.relationship_type_id', '=', 1)
324 ->execute()->single();
325 $this->assertEquals('Child Tester', $result['relative.display_name']);
326 $this->assertEquals('Buddy', $result["relative.$cgName.PetName"]);
cbda9790
CW
327 }
328
b60c8243
CW
329 public function testMultipleJoinsToCustomTable() {
330 $cgName = uniqid('My');
331
332 CustomGroup::create(FALSE)
ed3f5877 333 ->addValue('title', $cgName)
b60c8243
CW
334 ->addValue('extends', 'Contact')
335 ->addChain('field1', CustomField::create()
336 ->addValue('label', 'FavColor')
337 ->addValue('custom_group_id', '$id')
338 ->addValue('html_type', 'Text')
339 ->addValue('data_type', 'String'))
340 ->execute();
341
342 $parent = Contact::create(FALSE)
343 ->addValue('first_name', 'Parent')
344 ->addValue('last_name', 'Tester')
345 ->addValue("$cgName.FavColor", 'Purple')
346 ->execute()
347 ->first()['id'];
348
349 $child = Contact::create(FALSE)
350 ->addValue('first_name', 'Child')
351 ->addValue('last_name', 'Tester')
352 ->addValue("$cgName.FavColor", 'Cyan')
353 ->execute()
354 ->first()['id'];
355
356 Relationship::create(FALSE)
357 ->addValue('contact_id_a', $parent)
358 ->addValue('contact_id_b', $child)
359 ->addValue('relationship_type_id', 1)
360 ->execute();
361
362 $results = Contact::get(FALSE)
363 ->addSelect('first_name', 'child.first_name', "$cgName.FavColor", "child.$cgName.FavColor")
364 ->addWhere('id', '=', $parent)
365 ->addJoin('Contact AS child', 'INNER', 'RelationshipCache', ['id', '=', 'child.far_contact_id'])
366 ->execute();
367
368 $this->assertCount(1, $results);
369 $this->assertEquals('Parent', $results[0]['first_name']);
370 $this->assertEquals('Child', $results[0]['child.first_name']);
371 $this->assertEquals('Purple', $results[0]["$cgName.FavColor"]);
372 $this->assertEquals('Cyan', $results[0]["child.$cgName.FavColor"]);
373 }
374
2986a716 375 /**
376 * Some types are creating a dummy option group even if we don't have
377 * any option values.
378 * @throws \API_Exception
379 */
380 public function testUndesiredOptionGroupCreation(): void {
381 $optionGroupCount = OptionGroup::get(FALSE)->selectRowCount()->execute()->count();
382
383 $customGroup = CustomGroup::create(FALSE)
ed3f5877 384 ->addValue('title', 'MyIndividualFields')
2986a716 385 ->addValue('extends', 'Individual')
386 ->execute()
387 ->first();
388
389 // This one doesn't make sense to have an option group.
390 CustomField::create(FALSE)
391 ->addValue('label', 'FavColor')
392 ->addValue('custom_group_id', $customGroup['id'])
393 ->addValue('html_type', 'Number')
394 ->addValue('data_type', 'Money')
395 ->execute();
396
397 // This one might be ok if we planned to then use the autocreated option
398 // group, but if we go on to create our own after then we have an extra
399 // unused group.
400 CustomField::create(FALSE)
401 ->addValue('label', 'FavMovie')
402 ->addValue('custom_group_id', $customGroup['id'])
403 ->addValue('html_type', 'Select')
404 ->addValue('data_type', 'String')
405 ->execute();
406
407 $this->assertEquals($optionGroupCount, OptionGroup::get(FALSE)->selectRowCount()->execute()->count());
408 }
409
076fe09a
CW
410 public function testUpdateWeights() {
411 $getValues = function($groupName) {
412 return CustomField::get(FALSE)
413 ->addWhere('custom_group_id.name', '=', $groupName)
414 ->addOrderBy('weight')
415 ->execute()->indexBy('name')->column('weight');
416 };
417
418 // Create 2 custom groups. Control group is to ensure updating one doesn't affect the other
419 foreach (['controlGroup', 'experimentalGroup'] as $groupName) {
420 $customGroups[$groupName] = CustomGroup::create(FALSE)
ed3f5877 421 ->addValue('title', $groupName)
076fe09a
CW
422 ->addValue('name', $groupName)
423 ->addValue('extends', 'Individual')
424 ->execute()->first();
425 $sampleData = [
204c3c29 426 ['label' => 'One', 'html_type' => 'Select', 'option_values' => ['a' => 'A', 'b' => 'B']],
076fe09a 427 ['label' => 'Two'],
204c3c29 428 ['label' => 'Three', 'html_type' => 'Select', 'option_values' => ['c' => 'C', 'd' => 'D']],
076fe09a
CW
429 ['label' => 'Four'],
430 ];
431 CustomField::save(FALSE)
432 ->setRecords($sampleData)
433 ->addDefault('custom_group_id.name', $groupName)
434 ->addDefault('html_type', 'Text')
435 ->execute();
436 // Default weights should have been set during create
437 $this->assertEquals(['One' => 1, 'Two' => 2, 'Three' => 3, 'Four' => 4], $getValues($groupName));
438 }
439
53c53da0
CW
440 // Testing custom group weights
441
442 $originalControlGroupWeight = $customGroups['controlGroup']['weight'];
443 $originalExperimentalGroupWeight = $customGroups['experimentalGroup']['weight'];
444
076fe09a 445 // Ensure default weights were set for custom groups
53c53da0
CW
446 $this->assertEquals($originalControlGroupWeight + 1, $originalExperimentalGroupWeight);
447 // Updating custom group weight
448 $newExperimentalGroupWeight = CustomGroup::update(FALSE)
449 ->addValue('id', $customGroups['experimentalGroup']['id'])
450 ->addValue('weight', $originalControlGroupWeight)
451 ->execute()->first()['weight'];
452 // The other group's weight should have auto-adjusted
453 $newControlGroupWeight = CustomGroup::get(FALSE)
454 ->addWhere('id', '=', $customGroups['controlGroup']['id'])
455 ->execute()->first()['weight'];
456 $this->assertEquals($newExperimentalGroupWeight + 1, $newControlGroupWeight);
457
458 // Testing custom field weights
076fe09a
CW
459
460 // Move third option to second position
461 CustomField::update(FALSE)
462 ->addWhere('custom_group_id.name', '=', 'experimentalGroup')
463 ->addWhere('name', '=', 'Three')
464 ->addValue('weight', 2)
465 ->execute();
466 // Experimental group should be updated, control group should not
467 $this->assertEquals(['One' => 1, 'Three' => 2, 'Two' => 3, 'Four' => 4], $getValues('experimentalGroup'));
468 $this->assertEquals(['One' => 1, 'Two' => 2, 'Three' => 3, 'Four' => 4], $getValues('controlGroup'));
ed3f5877
CW
469
470 // Move first option to last position
471 CustomField::update(FALSE)
472 ->addWhere('custom_group_id.name', '=', 'experimentalGroup')
473 ->addWhere('name', '=', 'One')
474 ->addValue('weight', 4)
475 ->execute();
476 // Experimental group should be updated, control group should not
477 $this->assertEquals(['Three' => 1, 'Two' => 2, 'Four' => 3, 'One' => 4], $getValues('experimentalGroup'));
478 $this->assertEquals(['One' => 1, 'Two' => 2, 'Three' => 3, 'Four' => 4], $getValues('controlGroup'));
076fe09a
CW
479 }
480
4c5b9937
CW
481 /**
482 * Ensure custom date fields only return the date part
483 */
484 public function testCustomDateFields(): void {
485 $cgName = uniqid('My');
486
487 CustomGroup::create(FALSE)
488 ->addValue('title', $cgName)
489 ->addValue('extends', 'Contact')
490 ->addChain('field1', CustomField::create()
491 ->addValue('label', 'DateOnly')
492 ->addValue('custom_group_id', '$id')
493 ->addValue('html_type', 'Select Date')
494 ->addValue('data_type', 'Date')
495 ->addValue('date_format', 'mm/dd/yy'))
496 ->addChain('field2', CustomField::create()
497 ->addValue('label', 'DateTime')
498 ->addValue('custom_group_id', '$id')
499 ->addValue('html_type', 'Select Date')
500 ->addValue('data_type', 'Date')
501 ->addValue('date_format', 'mm/dd/yy')
502 ->addValue('time_format', '1'))
503 ->execute();
504
505 $cid = Contact::create(FALSE)
506 ->addValue('first_name', 'Parent')
507 ->addValue('last_name', 'Tester')
508 ->addValue("$cgName.DateOnly", '2025-05-10')
509 ->addValue("$cgName.DateTime", '2025-06-11 12:15:30')
510 ->execute()
511 ->first()['id'];
512 $contact = Contact::get(FALSE)
513 ->addSelect('custom.*')
514 ->addWhere('id', '=', $cid)
515 ->execute()->first();
516 // Date field should only return date part
517 $this->assertEquals('2025-05-10', $contact["$cgName.DateOnly"]);
518 // Date time field should return all
519 $this->assertEquals('2025-06-11 12:15:30', $contact["$cgName.DateTime"]);
520 }
521
3d2f86c5
CW
522 public function testExtendsMetadata() {
523 $field = \Civi\Api4\CustomGroup::getFields(FALSE)
524 ->setLoadOptions(['id', 'name'])
525 ->addWhere('name', '=', 'extends')
526 ->execute()->first();
527 $options = array_column($field['options'], 'name', 'id');
528 $this->assertArrayHasKey('Participant', $options);
529 }
530
19b53e5b 531}