cumulusci-plus 5.0.35__py3-none-any.whl → 5.0.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. cumulusci/__about__.py +1 -1
  2. cumulusci/cli/cci.py +3 -2
  3. cumulusci/cli/task.py +9 -10
  4. cumulusci/cli/tests/test_org.py +5 -0
  5. cumulusci/cli/tests/test_task.py +34 -0
  6. cumulusci/core/config/__init__.py +1 -0
  7. cumulusci/core/config/org_config.py +2 -1
  8. cumulusci/core/config/project_config.py +12 -0
  9. cumulusci/core/config/scratch_org_config.py +12 -0
  10. cumulusci/core/config/sfdx_org_config.py +4 -1
  11. cumulusci/core/config/tests/test_config.py +1 -0
  12. cumulusci/core/dependencies/base.py +4 -0
  13. cumulusci/cumulusci.yml +18 -1
  14. cumulusci/schema/cumulusci.jsonschema.json +5 -0
  15. cumulusci/tasks/apex/testrunner.py +7 -4
  16. cumulusci/tasks/bulkdata/tests/test_select_utils.py +20 -0
  17. cumulusci/tasks/metadata_etl/__init__.py +2 -0
  18. cumulusci/tasks/metadata_etl/applications.py +256 -0
  19. cumulusci/tasks/metadata_etl/tests/test_applications.py +710 -0
  20. cumulusci/tasks/salesforce/insert_record.py +18 -19
  21. cumulusci/tasks/salesforce/tests/test_enable_prediction.py +4 -2
  22. cumulusci/tasks/salesforce/tests/test_update_external_auth_identity_provider.py +927 -0
  23. cumulusci/tasks/salesforce/tests/test_update_external_credential.py +523 -8
  24. cumulusci/tasks/salesforce/tests/test_update_record.py +512 -0
  25. cumulusci/tasks/salesforce/update_external_auth_identity_provider.py +551 -0
  26. cumulusci/tasks/salesforce/update_external_credential.py +89 -4
  27. cumulusci/tasks/salesforce/update_record.py +217 -0
  28. cumulusci/tasks/sfdmu/sfdmu.py +14 -1
  29. cumulusci/tasks/utility/credentialManager.py +58 -12
  30. cumulusci/tasks/utility/secretsToEnv.py +42 -11
  31. cumulusci/tasks/utility/tests/test_credentialManager.py +586 -0
  32. cumulusci/tasks/utility/tests/test_secretsToEnv.py +1240 -62
  33. cumulusci/utils/yaml/cumulusci_yml.py +1 -0
  34. {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/METADATA +5 -7
  35. {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/RECORD +39 -33
  36. {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/WHEEL +1 -1
  37. {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/entry_points.txt +0 -0
  38. {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/licenses/AUTHORS.rst +0 -0
  39. {cumulusci_plus-5.0.35.dist-info → cumulusci_plus-5.0.45.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,710 @@
1
+ from cumulusci.tasks.metadata_etl.applications import AddProfileActionOverrides
2
+ from cumulusci.tasks.salesforce.tests.util import create_task
3
+ from cumulusci.utils.xml import metadata_tree
4
+
5
+ MD = "{%s}" % metadata_tree.METADATA_NAMESPACE
6
+
7
+
8
+ APPLICATION_XML = """<?xml version="1.0" encoding="UTF-8"?>
9
+ <CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
10
+ <brand>
11
+ <headerColor>#0070D2</headerColor>
12
+ <shouldOverrideOrgTheme>false</shouldOverrideOrgTheme>
13
+ </brand>
14
+ <description>Test Application</description>
15
+ <label>Test App</label>
16
+ <navType>Console</navType>
17
+ {profileActionOverrides}
18
+ <uiType>Lightning</uiType>
19
+ </CustomApplication>
20
+ """
21
+
22
+ PROFILE_ACTION_OVERRIDE = """ <profileActionOverrides>
23
+ <actionName>View</actionName>
24
+ <content>TestRecordPage</content>
25
+ <formFactor>Large</formFactor>
26
+ <pageOrSobjectType>Account</pageOrSobjectType>
27
+ <recordType>PersonAccount.User</recordType>
28
+ <type>Flexipage</type>
29
+ <profile>Admin</profile>
30
+ </profileActionOverrides>
31
+ """
32
+
33
+
34
+ class TestAddProfileActionOverrides:
35
+ def test_adds_profile_action_override(self):
36
+ """Test adding a new profileActionOverride"""
37
+ task = create_task(
38
+ AddProfileActionOverrides,
39
+ {
40
+ "managed": True,
41
+ "api_version": "47.0",
42
+ "applications": [
43
+ {
44
+ "name": "TestApp",
45
+ "overrides": [
46
+ {
47
+ "action_name": "Edit",
48
+ "content": "CustomEditPage",
49
+ "form_factor": "Large",
50
+ "page_or_sobject_type": "Contact",
51
+ "record_type": "Contact.Business",
52
+ "type": "Flexipage",
53
+ "profile": "StandardUser",
54
+ }
55
+ ],
56
+ }
57
+ ],
58
+ },
59
+ )
60
+
61
+ tree = metadata_tree.fromstring(
62
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
63
+ )
64
+ element = tree._element
65
+
66
+ # Verify no existing override
67
+ assert len(element.findall(f".//{MD}profileActionOverrides")) == 0
68
+
69
+ result = task._transform_entity(tree, "TestApp")
70
+
71
+ # Verify override was added
72
+ assert len(result._element.findall(f".//{MD}profileActionOverrides")) == 1
73
+
74
+ override = result._element.find(f".//{MD}profileActionOverrides")
75
+ assert override.find(f"{MD}actionName").text == "Edit"
76
+ assert override.find(f"{MD}content").text == "CustomEditPage"
77
+ assert override.find(f"{MD}formFactor").text == "Large"
78
+ assert override.find(f"{MD}pageOrSobjectType").text == "Contact"
79
+ assert override.find(f"{MD}recordType").text == "Contact.Business"
80
+ assert override.find(f"{MD}type").text == "Flexipage"
81
+ assert override.find(f"{MD}profile").text == "StandardUser"
82
+
83
+ def test_adds_multiple_profile_action_overrides(self):
84
+ """Test adding multiple profileActionOverrides to a single application"""
85
+ task = create_task(
86
+ AddProfileActionOverrides,
87
+ {
88
+ "managed": True,
89
+ "api_version": "47.0",
90
+ "applications": [
91
+ {
92
+ "name": "TestApp",
93
+ "overrides": [
94
+ {
95
+ "action_name": "View",
96
+ "content": "AccountViewPage",
97
+ "form_factor": "Large",
98
+ "page_or_sobject_type": "Account",
99
+ "record_type": "PersonAccount.User",
100
+ "type": "Flexipage",
101
+ "profile": "Admin",
102
+ },
103
+ {
104
+ "action_name": "Edit",
105
+ "content": "ContactEditPage",
106
+ "form_factor": "Small",
107
+ "page_or_sobject_type": "Contact",
108
+ "record_type": None,
109
+ "type": "Visualforce",
110
+ "profile": "StandardUser",
111
+ },
112
+ ],
113
+ }
114
+ ],
115
+ },
116
+ )
117
+
118
+ tree = metadata_tree.fromstring(
119
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
120
+ )
121
+
122
+ result = task._transform_entity(tree, "TestApp")
123
+
124
+ # Verify both overrides were added
125
+ overrides = result._element.findall(f".//{MD}profileActionOverrides")
126
+ assert len(overrides) == 2
127
+
128
+ # Check first override
129
+ override1 = overrides[0]
130
+ assert override1.find(f"{MD}actionName").text == "View"
131
+ assert override1.find(f"{MD}content").text == "AccountViewPage"
132
+ assert override1.find(f"{MD}profile").text == "Admin"
133
+
134
+ # Check second override
135
+ override2 = overrides[1]
136
+ assert override2.find(f"{MD}actionName").text == "Edit"
137
+ assert override2.find(f"{MD}content").text == "ContactEditPage"
138
+ assert override2.find(f"{MD}profile").text == "StandardUser"
139
+ # Verify recordType is not present when None
140
+ assert override2.find(f"{MD}recordType") is None
141
+
142
+ def test_adds_multiple_applications(self):
143
+ """Test adding overrides to multiple applications"""
144
+ task = create_task(
145
+ AddProfileActionOverrides,
146
+ {
147
+ "managed": True,
148
+ "api_version": "47.0",
149
+ "applications": [
150
+ {
151
+ "name": "TestApp",
152
+ "overrides": [
153
+ {
154
+ "action_name": "View",
155
+ "content": "AccountViewPage",
156
+ "form_factor": "Large",
157
+ "page_or_sobject_type": "Account",
158
+ "record_type": None,
159
+ "type": "Flexipage",
160
+ "profile": "Admin",
161
+ }
162
+ ],
163
+ },
164
+ {
165
+ "name": "SecondApp",
166
+ "overrides": [
167
+ {
168
+ "action_name": "Edit",
169
+ "content": "ContactEditPage",
170
+ "form_factor": "Large",
171
+ "page_or_sobject_type": "Contact",
172
+ "record_type": None,
173
+ "type": "Flexipage",
174
+ "profile": "StandardUser",
175
+ }
176
+ ],
177
+ },
178
+ ],
179
+ },
180
+ )
181
+
182
+ # Verify api_names contains both applications
183
+ assert "TestApp" in task.api_names
184
+ assert "SecondApp" in task.api_names
185
+ assert len(task.api_names) == 2
186
+
187
+ # Test first application
188
+ tree1 = metadata_tree.fromstring(
189
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
190
+ )
191
+ result1 = task._transform_entity(tree1, "TestApp")
192
+ overrides1 = result1._element.findall(f".//{MD}profileActionOverrides")
193
+ assert len(overrides1) == 1
194
+ assert overrides1[0].find(f"{MD}pageOrSobjectType").text == "Account"
195
+
196
+ # Test second application
197
+ tree2 = metadata_tree.fromstring(
198
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
199
+ )
200
+ result2 = task._transform_entity(tree2, "SecondApp")
201
+ overrides2 = result2._element.findall(f".//{MD}profileActionOverrides")
202
+ assert len(overrides2) == 1
203
+ assert overrides2[0].find(f"{MD}pageOrSobjectType").text == "Contact"
204
+
205
+ def test_updates_existing_profile_action_override(self):
206
+ """Test updating an existing profileActionOverride"""
207
+ task = create_task(
208
+ AddProfileActionOverrides,
209
+ {
210
+ "managed": True,
211
+ "api_version": "47.0",
212
+ "applications": [
213
+ {
214
+ "name": "TestApp",
215
+ "overrides": [
216
+ {
217
+ "action_name": "View",
218
+ "content": "UpdatedRecordPage",
219
+ "form_factor": "Small",
220
+ "page_or_sobject_type": "Account",
221
+ "record_type": "PersonAccount.User",
222
+ "type": "LightningComponent",
223
+ "profile": "Admin",
224
+ }
225
+ ],
226
+ }
227
+ ],
228
+ },
229
+ )
230
+
231
+ tree = metadata_tree.fromstring(
232
+ APPLICATION_XML.format(
233
+ profileActionOverrides=PROFILE_ACTION_OVERRIDE
234
+ ).encode("utf-8")
235
+ )
236
+ element = tree._element
237
+
238
+ # Verify existing override
239
+ assert len(element.findall(f".//{MD}profileActionOverrides")) == 1
240
+ original = element.find(f".//{MD}profileActionOverrides")
241
+ assert original.find(f"{MD}content").text == "TestRecordPage"
242
+ assert original.find(f"{MD}formFactor").text == "Large"
243
+
244
+ result = task._transform_entity(tree, "TestApp")
245
+
246
+ # Verify still only one override (updated, not added)
247
+ overrides = result._element.findall(f".//{MD}profileActionOverrides")
248
+ assert len(overrides) == 1
249
+
250
+ # Verify override was updated
251
+ override = overrides[0]
252
+ assert override.find(f"{MD}actionName").text == "View"
253
+ assert override.find(f"{MD}content").text == "UpdatedRecordPage"
254
+ assert override.find(f"{MD}formFactor").text == "Small"
255
+ assert override.find(f"{MD}type").text == "LightningComponent"
256
+ assert override.find(f"{MD}profile").text == "Admin"
257
+
258
+ def test_adds_override_when_different_profile(self):
259
+ """Test that overrides with different profiles are treated as distinct"""
260
+ task = create_task(
261
+ AddProfileActionOverrides,
262
+ {
263
+ "managed": True,
264
+ "api_version": "47.0",
265
+ "applications": [
266
+ {
267
+ "name": "TestApp",
268
+ "overrides": [
269
+ {
270
+ "action_name": "View",
271
+ "content": "StandardUserRecordPage",
272
+ "form_factor": "Large",
273
+ "page_or_sobject_type": "Account",
274
+ "record_type": "PersonAccount.User",
275
+ "type": "Flexipage",
276
+ "profile": "StandardUser",
277
+ }
278
+ ],
279
+ }
280
+ ],
281
+ },
282
+ )
283
+
284
+ tree = metadata_tree.fromstring(
285
+ APPLICATION_XML.format(
286
+ profileActionOverrides=PROFILE_ACTION_OVERRIDE
287
+ ).encode("utf-8")
288
+ )
289
+ element = tree._element
290
+
291
+ # Verify one existing override for Admin profile
292
+ assert len(element.findall(f".//{MD}profileActionOverrides")) == 1
293
+
294
+ result = task._transform_entity(tree, "TestApp")
295
+
296
+ # Verify two overrides now (original + new for different profile)
297
+ overrides = result._element.findall(f".//{MD}profileActionOverrides")
298
+ assert len(overrides) == 2
299
+
300
+ # Verify both profiles are present
301
+ profiles = [o.find(f"{MD}profile").text for o in overrides]
302
+ assert "Admin" in profiles
303
+ assert "StandardUser" in profiles
304
+
305
+ def test_namespace_injection_in_overrides(self):
306
+ """Test that namespace injection works for override fields"""
307
+ task = create_task(
308
+ AddProfileActionOverrides,
309
+ {
310
+ "managed": True,
311
+ "api_version": "47.0",
312
+ "applications": [
313
+ {
314
+ "name": "TestApp",
315
+ "overrides": [
316
+ {
317
+ "action_name": "View",
318
+ "content": "%%%NAMESPACED_ORG%%%CustomPage",
319
+ "form_factor": "Large",
320
+ "page_or_sobject_type": "%%%NAMESPACE%%%CustomObject__c",
321
+ "record_type": "%%%NAMESPACE%%%CustomObject__c.%%%NAMESPACE%%%CustomRecordType",
322
+ "type": "Flexipage",
323
+ "profile": "Admin",
324
+ }
325
+ ],
326
+ }
327
+ ],
328
+ },
329
+ )
330
+
331
+ tree = metadata_tree.fromstring(
332
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
333
+ )
334
+
335
+ result = task._transform_entity(tree, "TestApp")
336
+
337
+ override = result._element.find(f".//{MD}profileActionOverrides")
338
+ # Namespace tokens should be processed (even if empty in test environment)
339
+ assert override.find(f"{MD}content") is not None
340
+ assert override.find(f"{MD}pageOrSobjectType") is not None
341
+ assert override.find(f"{MD}recordType") is not None
342
+
343
+ def test_namespace_injection_in_application_name(self):
344
+ """Test that namespace injection works for application names"""
345
+ task = create_task(
346
+ AddProfileActionOverrides,
347
+ {
348
+ "managed": True,
349
+ "api_version": "47.0",
350
+ "applications": [
351
+ {
352
+ "name": "%%%NAMESPACE%%%TestApp",
353
+ "overrides": [
354
+ {
355
+ "action_name": "View",
356
+ "content": "CustomPage",
357
+ "form_factor": "Large",
358
+ "page_or_sobject_type": "Account",
359
+ "record_type": None,
360
+ "type": "Flexipage",
361
+ "profile": "Admin",
362
+ }
363
+ ],
364
+ }
365
+ ],
366
+ },
367
+ )
368
+
369
+ # Verify namespace token is processed in api_names
370
+ # In test environment without namespace, it should be empty string
371
+ assert len(task.api_names) == 1
372
+
373
+ def test_override_without_record_type(self):
374
+ """Test adding override without recordType"""
375
+ task = create_task(
376
+ AddProfileActionOverrides,
377
+ {
378
+ "managed": True,
379
+ "api_version": "47.0",
380
+ "applications": [
381
+ {
382
+ "name": "TestApp",
383
+ "overrides": [
384
+ {
385
+ "action_name": "New",
386
+ "content": "NewContactPage",
387
+ "form_factor": "Large",
388
+ "page_or_sobject_type": "Contact",
389
+ "record_type": None,
390
+ "type": "Flexipage",
391
+ "profile": "Admin",
392
+ }
393
+ ],
394
+ }
395
+ ],
396
+ },
397
+ )
398
+
399
+ tree = metadata_tree.fromstring(
400
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
401
+ )
402
+
403
+ result = task._transform_entity(tree, "TestApp")
404
+
405
+ override = result._element.find(f".//{MD}profileActionOverrides")
406
+ assert override.find(f"{MD}actionName").text == "New"
407
+ assert override.find(f"{MD}pageOrSobjectType").text == "Contact"
408
+ # Verify recordType element is not present
409
+ assert override.find(f"{MD}recordType") is None
410
+
411
+ def test_skips_override_when_no_overrides_provided(self):
412
+ """Test that task returns None when no overrides are provided"""
413
+ task = create_task(
414
+ AddProfileActionOverrides,
415
+ {
416
+ "managed": True,
417
+ "api_version": "47.0",
418
+ "applications": [
419
+ {
420
+ "name": "TestApp",
421
+ "overrides": [],
422
+ }
423
+ ],
424
+ },
425
+ )
426
+
427
+ tree = metadata_tree.fromstring(
428
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
429
+ )
430
+
431
+ result = task._transform_entity(tree, "TestApp")
432
+
433
+ # Task should return metadata even with empty overrides
434
+ # but no overrides should be added
435
+ assert result is not None
436
+ assert len(result._element.findall(f".//{MD}profileActionOverrides")) == 0
437
+
438
+ def test_skips_override_when_no_applications_provided(self):
439
+ """Test that task returns None when no applications are provided"""
440
+ task = create_task(
441
+ AddProfileActionOverrides,
442
+ {
443
+ "managed": True,
444
+ "api_version": "47.0",
445
+ "applications": [],
446
+ },
447
+ )
448
+
449
+ tree = metadata_tree.fromstring(
450
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
451
+ )
452
+
453
+ result = task._transform_entity(tree, "TestApp")
454
+
455
+ # Task should return None when no applications configured
456
+ assert result is None
457
+
458
+ def test_skips_application_when_name_does_not_match(self):
459
+ """Test that overrides are only applied to matching applications"""
460
+ task = create_task(
461
+ AddProfileActionOverrides,
462
+ {
463
+ "managed": True,
464
+ "api_version": "47.0",
465
+ "applications": [
466
+ {
467
+ "name": "DifferentApp",
468
+ "overrides": [
469
+ {
470
+ "action_name": "View",
471
+ "content": "CustomPage",
472
+ "form_factor": "Large",
473
+ "page_or_sobject_type": "Account",
474
+ "record_type": None,
475
+ "type": "Flexipage",
476
+ "profile": "Admin",
477
+ }
478
+ ],
479
+ }
480
+ ],
481
+ },
482
+ )
483
+
484
+ tree = metadata_tree.fromstring(
485
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
486
+ )
487
+
488
+ result = task._transform_entity(tree, "TestApp")
489
+
490
+ # No overrides should be added since application name doesn't match
491
+ assert result is not None
492
+ assert len(result._element.findall(f".//{MD}profileActionOverrides")) == 0
493
+
494
+ def test_different_record_type_none_creates_separate_override(self):
495
+ """Test that overrides with None recordType vs specific recordType are treated as distinct"""
496
+ task = create_task(
497
+ AddProfileActionOverrides,
498
+ {
499
+ "managed": True,
500
+ "api_version": "47.0",
501
+ "applications": [
502
+ {
503
+ "name": "TestApp",
504
+ "overrides": [
505
+ {
506
+ "action_name": "View",
507
+ "content": "GenericAccountPage",
508
+ "form_factor": "Large",
509
+ "page_or_sobject_type": "Account",
510
+ "record_type": None, # No specific recordType
511
+ "type": "Flexipage",
512
+ "profile": "Admin",
513
+ }
514
+ ],
515
+ }
516
+ ],
517
+ },
518
+ )
519
+
520
+ tree = metadata_tree.fromstring(
521
+ APPLICATION_XML.format(
522
+ profileActionOverrides=PROFILE_ACTION_OVERRIDE
523
+ ).encode("utf-8")
524
+ )
525
+
526
+ # Original has recordType "PersonAccount.User"
527
+ original = tree._element.find(f".//{MD}profileActionOverrides")
528
+ assert original.find(f"{MD}recordType").text == "PersonAccount.User"
529
+
530
+ result = task._transform_entity(tree, "TestApp")
531
+
532
+ # Should have two overrides now (one with recordType, one without)
533
+ overrides = result._element.findall(f".//{MD}profileActionOverrides")
534
+ assert len(overrides) == 2
535
+
536
+ # Check that we have one with recordType and one without
537
+ overrides_with_record_type = [
538
+ o for o in overrides if o.find(f"{MD}recordType") is not None
539
+ ]
540
+ overrides_without_record_type = [
541
+ o for o in overrides if o.find(f"{MD}recordType") is None
542
+ ]
543
+ assert len(overrides_with_record_type) == 1
544
+ assert len(overrides_without_record_type) == 1
545
+
546
+ def test_different_record_types_create_separate_overrides(self):
547
+ """Test that overrides with different recordTypes are treated as distinct"""
548
+ task = create_task(
549
+ AddProfileActionOverrides,
550
+ {
551
+ "managed": True,
552
+ "api_version": "47.0",
553
+ "applications": [
554
+ {
555
+ "name": "TestApp",
556
+ "overrides": [
557
+ {
558
+ "action_name": "View",
559
+ "content": "BusinessAccountPage",
560
+ "form_factor": "Large",
561
+ "page_or_sobject_type": "Account",
562
+ "record_type": "Account.Business",
563
+ "type": "Flexipage",
564
+ "profile": "Admin",
565
+ }
566
+ ],
567
+ }
568
+ ],
569
+ },
570
+ )
571
+
572
+ tree = metadata_tree.fromstring(
573
+ APPLICATION_XML.format(
574
+ profileActionOverrides=PROFILE_ACTION_OVERRIDE
575
+ ).encode("utf-8")
576
+ )
577
+
578
+ # Original has PersonAccount.User recordType
579
+ original = tree._element.find(f".//{MD}profileActionOverrides")
580
+ assert original.find(f"{MD}recordType").text == "PersonAccount.User"
581
+
582
+ result = task._transform_entity(tree, "TestApp")
583
+
584
+ # Should have two overrides now (different recordTypes)
585
+ overrides = result._element.findall(f".//{MD}profileActionOverrides")
586
+ assert len(overrides) == 2
587
+
588
+ record_types = [
589
+ o.find(f"{MD}recordType").text
590
+ for o in overrides
591
+ if o.find(f"{MD}recordType") is not None
592
+ ]
593
+ assert "PersonAccount.User" in record_types
594
+ assert "Account.Business" in record_types
595
+
596
+ def test_all_override_properties_are_set(self):
597
+ """Test that all properties of an override are correctly set"""
598
+ task = create_task(
599
+ AddProfileActionOverrides,
600
+ {
601
+ "managed": True,
602
+ "api_version": "47.0",
603
+ "applications": [
604
+ {
605
+ "name": "TestApp",
606
+ "overrides": [
607
+ {
608
+ "action_name": "Clone",
609
+ "content": "CustomClonePage",
610
+ "form_factor": "Small",
611
+ "page_or_sobject_type": "Lead",
612
+ "record_type": "Lead.Enterprise",
613
+ "type": "LightningComponent",
614
+ "profile": "SalesUser",
615
+ }
616
+ ],
617
+ }
618
+ ],
619
+ },
620
+ )
621
+
622
+ tree = metadata_tree.fromstring(
623
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
624
+ )
625
+
626
+ result = task._transform_entity(tree, "TestApp")
627
+
628
+ override = result._element.find(f".//{MD}profileActionOverrides")
629
+
630
+ # Verify all properties are correctly set
631
+ assert override.find(f"{MD}actionName").text == "Clone"
632
+ assert override.find(f"{MD}content").text == "CustomClonePage"
633
+ assert override.find(f"{MD}formFactor").text == "Small"
634
+ assert override.find(f"{MD}pageOrSobjectType").text == "Lead"
635
+ assert override.find(f"{MD}recordType").text == "Lead.Enterprise"
636
+ assert override.find(f"{MD}type").text == "LightningComponent"
637
+ assert override.find(f"{MD}profile").text == "SalesUser"
638
+
639
+ def test_complex_scenario_multiple_apps_and_overrides(self):
640
+ """Test complex scenario with multiple applications and multiple overrides per app"""
641
+ task = create_task(
642
+ AddProfileActionOverrides,
643
+ {
644
+ "managed": True,
645
+ "api_version": "47.0",
646
+ "applications": [
647
+ {
648
+ "name": "AdminConsole",
649
+ "overrides": [
650
+ {
651
+ "action_name": "View",
652
+ "content": "AdminAccountView",
653
+ "form_factor": "Large",
654
+ "page_or_sobject_type": "Account",
655
+ "record_type": None,
656
+ "type": "Flexipage",
657
+ "profile": "Admin",
658
+ },
659
+ {
660
+ "action_name": "Edit",
661
+ "content": "AdminAccountEdit",
662
+ "form_factor": "Large",
663
+ "page_or_sobject_type": "Account",
664
+ "record_type": None,
665
+ "type": "Flexipage",
666
+ "profile": "Admin",
667
+ },
668
+ ],
669
+ },
670
+ {
671
+ "name": "SalesConsole",
672
+ "overrides": [
673
+ {
674
+ "action_name": "View",
675
+ "content": "SalesContactView",
676
+ "form_factor": "Large",
677
+ "page_or_sobject_type": "Contact",
678
+ "record_type": None,
679
+ "type": "Flexipage",
680
+ "profile": "SalesUser",
681
+ }
682
+ ],
683
+ },
684
+ ],
685
+ },
686
+ )
687
+
688
+ # Verify both apps in api_names
689
+ assert "AdminConsole" in task.api_names
690
+ assert "SalesConsole" in task.api_names
691
+
692
+ # Test AdminConsole
693
+ tree1 = metadata_tree.fromstring(
694
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
695
+ )
696
+ result1 = task._transform_entity(tree1, "AdminConsole")
697
+ overrides1 = result1._element.findall(f".//{MD}profileActionOverrides")
698
+ assert len(overrides1) == 2
699
+ actions1 = [o.find(f"{MD}actionName").text for o in overrides1]
700
+ assert "View" in actions1
701
+ assert "Edit" in actions1
702
+
703
+ # Test SalesConsole
704
+ tree2 = metadata_tree.fromstring(
705
+ APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
706
+ )
707
+ result2 = task._transform_entity(tree2, "SalesConsole")
708
+ overrides2 = result2._element.findall(f".//{MD}profileActionOverrides")
709
+ assert len(overrides2) == 1
710
+ assert overrides2[0].find(f"{MD}pageOrSobjectType").text == "Contact"