@salesforce/afv-skills 1.41.0 → 1.43.0
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.
- package/package.json +2 -6
- package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +3 -0
- package/skills/automation-sandbox-post-copy-configure/SKILL.md +4 -0
- package/skills/design-systems-slds-validate/SKILL.md +2 -2
- package/skills/dx-app-analytics-query/SKILL.md +1 -3
- package/skills/dx-devops-conflict-resolve/SKILL.md +205 -0
- package/skills/dx-devops-conflict-resolve/examples/conflict-workflows.md +165 -0
- package/skills/dx-devops-conflict-resolve/references/deploy-failure-resolution.md +75 -0
- package/skills/dx-devops-conflict-resolve/references/git-conflict-resolution.md +124 -0
- package/skills/dx-devops-conflict-resolve/scripts/detect-conflicts.sh +91 -0
- package/skills/dx-devops-conflict-resolve/scripts/diagnose-deploy-failure.sh +156 -0
- package/skills/dx-devops-request-status/SKILL.md +160 -0
- package/skills/dx-devops-request-status/examples/polling-workflows.md +101 -0
- package/skills/dx-devops-request-status/references/cli-commands.md +176 -0
- package/skills/dx-devops-request-status/scripts/poll-status.sh +134 -0
- package/skills/dx-devops-test-failures-analyze/SKILL.md +6 -6
- package/skills/dx-devops-test-pipeline-configure/SKILL.md +7 -7
- package/skills/dx-devops-test-suite-assignments-configure/SKILL.md +6 -6
- package/skills/dx-devops-test-suite-run/SKILL.md +6 -6
- package/skills/dx-devops-work-item-manage/SKILL.md +2 -0
- package/skills/dx-org-manage/references/creating-scratch-org.md +5 -2
- package/skills/dx-org-manage/references/creating-snapshot.md +1 -0
- package/skills/dx-org-shape-manage/SKILL.md +152 -0
- package/skills/dx-org-shape-manage/examples/create_error_output.json +9 -0
- package/skills/dx-org-shape-manage/examples/create_success_output.json +9 -0
- package/skills/dx-org-shape-manage/examples/delete_output.json +11 -0
- package/skills/dx-org-shape-manage/examples/list_inactive_output.json +32 -0
- package/skills/dx-org-shape-manage/examples/list_output.json +24 -0
- package/skills/dx-org-shape-manage/references/cli_flags.md +135 -0
- package/skills/dx-org-switch/SKILL.md +2 -2
- package/skills/dx-org-trial-expiration-check/SKILL.md +2 -0
- package/skills/dx-pkg-post-install-configure/SKILL.md +3 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +211 -0
- package/skills/education-cloud-multi-campus-configure/examples/hierarchy_visualization.md +174 -0
- package/skills/education-cloud-multi-campus-configure/examples/output_examples.md +54 -0
- package/skills/education-cloud-multi-campus-configure/examples/sample_hierarchy_input.csv +29 -0
- package/skills/education-cloud-multi-campus-configure/examples/sample_hierarchy_input_edgecases.csv +16 -0
- package/skills/education-cloud-multi-campus-configure/references/account_recordtype_prerequisite.md +44 -0
- package/skills/education-cloud-multi-campus-configure/references/delta_computation.md +33 -0
- package/skills/education-cloud-multi-campus-configure/references/error_handling.md +198 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +54 -0
- package/skills/education-cloud-multi-campus-configure/references/gotchas.md +16 -0
- package/skills/education-cloud-multi-campus-configure/references/hierarchy_parsing_rules.md +133 -0
- package/skills/education-cloud-multi-campus-configure/references/mcp-invocation.md +75 -0
- package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/SKILL.md +1 -1
- package/skills/experience-cms-brand-apply/SKILL.md +4 -1
- package/skills/experience-cms-brand-create/SKILL.md +197 -0
- package/skills/experience-cms-brand-create/assets/brand-template.json +329 -0
- package/skills/experience-cms-brand-create/references/brand-anatomy.md +120 -0
- package/skills/experience-cms-brand-create/references/disk-contract.md +84 -0
- package/skills/experience-cms-content-generate/SKILL.md +190 -0
- package/skills/experience-cms-content-generate/assets/display-formats.md +84 -0
- package/skills/experience-cms-content-generate/assets/payloads/create-content-bulk.json +11 -0
- package/skills/experience-cms-content-generate/assets/payloads/create-content-single.json +11 -0
- package/skills/experience-cms-content-generate/assets/payloads/publish-content.json +4 -0
- package/skills/experience-cms-content-generate/assets/payloads/update-content.json +9 -0
- package/skills/experience-cms-content-generate/assets/questions.md +241 -0
- package/skills/experience-cms-content-generate/examples/create-content-call.md +81 -0
- package/skills/experience-cms-content-generate/references/bulk-batching.md +56 -0
- package/skills/experience-cms-content-generate/references/content-type-classification.md +88 -0
- package/skills/experience-cms-content-generate/references/content-write-tool.md +95 -0
- package/skills/experience-cms-content-generate/references/delegation-protocol.md +37 -0
- package/skills/experience-cms-content-generate/references/edit-publish-workflow.md +63 -0
- package/skills/experience-cms-content-generate/references/error-recovery.md +72 -0
- package/skills/experience-cms-content-generate/references/identifier-resolution.md +26 -0
- package/skills/experience-cms-content-generate/references/intent-routing.md +45 -0
- package/skills/experience-cms-content-generate/references/principles.md +19 -0
- package/skills/experience-cms-content-generate/references/ux-rules.md +68 -0
- package/skills/experience-cms-content-generate/references/workspace-resolution.md +33 -0
- package/skills/experience-cms-content-type-generate/SKILL.md +449 -0
- package/skills/experience-cms-content-type-generate/assets/discovery-prompts.md +79 -0
- package/skills/experience-cms-content-type-generate/assets/schema-example.json +70 -0
- package/skills/experience-cms-content-type-generate/references/agent-checklist.md +50 -0
- package/skills/experience-cms-content-type-generate/references/anti-patterns.md +39 -0
- package/skills/experience-cms-content-type-generate/references/deployment-errors.md +27 -0
- package/skills/experience-cms-content-type-generate/references/discovery-details.md +106 -0
- package/skills/experience-cms-content-type-generate/references/discovery-query-rules.md +85 -0
- package/skills/experience-cms-content-type-generate/references/edit-fields-loop.md +48 -0
- package/skills/experience-cms-content-type-generate/references/pre-deploy-checklist.md +21 -0
- package/skills/experience-cms-content-type-generate/references/retrieve-and-reconcile.md +105 -0
- package/skills/experience-cms-content-type-generate/references/schema-rules.md +51 -0
- package/skills/experience-cms-content-type-generate/references/schema-summary-format.md +60 -0
- package/skills/experience-lds-graphql-generate/SKILL.md +4 -0
- package/skills/experience-lwc-accessibility-jest-run/SKILL.md +79 -0
- package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +205 -0
- package/skills/experience-lwc-design-generate/SKILL.md +5 -4
- package/skills/experience-lwc-generate/SKILL.md +10 -9
- package/skills/experience-ui-bundle-deploy/references/dev-preview.md +21 -0
- package/skills/experience-ui-bundle-deploy/references/social-login.md +1 -0
- package/skills/experience-ui-bundle-file-upload-generate/SKILL.md +6 -6
- package/skills/experience-ui-bundle-localize/SKILL.md +30 -37
- package/skills/experience-ui-bundle-localize/references/gotchas.md +23 -7
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +48 -4
- package/skills/experience-ui-bundle-localize/references/label-xml.md +22 -8
- package/skills/experience-ui-bundle-localize/references/verifying.md +39 -11
- package/skills/experience-ui-bundle-localize/scripts/detect-bundle-type.sh +118 -15
- package/skills/experience-ui-bundle-localize/scripts/tests/test-detect-bundle-type.sh +187 -0
- package/skills/experience-ui-bundle-mfa-configure/SKILL.md +13 -2
- package/skills/experience-ui-bundle-mfa-configure/references/social-login.md +1 -0
- package/skills/experience-ui-bundle-project-generate/SKILL.md +2 -2
- package/skills/integration-connectivity-generate/SKILL.md +11 -10
- package/skills/integration-eventing-cdc-configure/SKILL.md +6 -6
- package/skills/integration-eventing-subscription-configure/SKILL.md +6 -5
- package/skills/mobile-platform-native-capabilities-integrate/SKILL.md +2 -2
- package/skills/mobile-platform-offline-validate/scripts/package.json +1 -1
- package/skills/platform-agentexchange-partner-offers-configure/SKILL.md +8 -8
- package/skills/platform-custom-application-generate/SKILL.md +2 -0
- package/skills/platform-custom-field-generate/SKILL.md +8 -6
- package/skills/platform-custom-metadata-type-generate/SKILL.md +460 -0
- package/skills/platform-custom-metadata-type-generate/references/cmdt-records.md +251 -0
- package/skills/platform-custom-metadata-type-generate/scripts/sanitize-developer-name.sh +46 -0
- package/skills/platform-custom-setting-generate/SKILL.md +419 -0
- package/skills/platform-dataspace-access-configure/SKILL.md +3 -3
- package/skills/platform-sharing-owd-configure/SKILL.md +3 -0
- package/skills/platform-value-set-generate/SKILL.md +2 -0
- package/skills/service-agentforce-channel-configure/SKILL.md +29 -20
- package/skills/service-agentforce-channel-configure/assets/BotEmailDefinition.botEmailDefinition-meta.xml +36 -0
- package/skills/service-agentforce-channel-configure/assets/email/unfiled$public/AgentforceForServiceEmailTemplate.email +17 -0
- package/skills/service-agentforce-channel-configure/assets/email/unfiled$public/AgentforceForServiceEmailTemplate.email-meta.xml +29 -0
- package/skills/service-agentforce-channel-configure/assets/mdapi-package.xml +25 -0
- package/skills/service-agentforce-channel-configure/assets/settings-mdapi-package.xml +30 -0
- package/skills/service-agentforce-channel-configure/references/agent-wiring.md +20 -0
- package/skills/service-agentforce-channel-configure/references/botemaildefinition.md +107 -0
- package/skills/service-agentforce-channel-configure/references/channel-branch-email.md +203 -50
- package/skills/service-agentforce-channel-configure/references/channel-types.md +30 -3
- package/skills/service-agentforce-channel-configure/references/queue-resolution.md +17 -8
- package/skills/service-agentforce-channel-configure/references/routing-flow.md +14 -3
- package/skills/service-agentforce-channel-configure/scripts/validate-botemaildefinition.py +126 -0
- package/skills/service-agentforce-channel-configure/scripts/validate-emailtemplate.py +133 -0
- package/skills/service-de-channel-activate/SKILL.md +271 -0
- package/skills/service-de-channel-activate/references/gotchas.md +31 -0
- package/skills/service-de-channel-activate/references/phone-verification.md +96 -0
- package/skills/service-de-channel-activate/references/worked-examples.md +14 -0
- package/skills/service-de-channel-consent-configure/SKILL.md +230 -0
- package/skills/service-de-channel-consent-configure/references/gotchas.md +41 -0
- package/skills/service-de-channel-consent-configure/references/language-keywords.md +84 -0
- package/skills/service-de-channel-consent-configure/references/worked-examples.md +134 -0
- package/skills/service-de-channel-create/SKILL.md +369 -0
- package/skills/service-de-channel-create/references/apple.md +59 -0
- package/skills/service-de-channel-create/references/connect-insert.md +84 -0
- package/skills/service-de-channel-create/references/facebook.md +154 -0
- package/skills/service-de-channel-create/references/line.md +66 -0
- package/skills/service-de-channel-create/references/sms.md +98 -0
- package/skills/service-de-channel-create/references/whatsapp.md +98 -0
- package/skills/service-de-channel-create/references/worked-examples.md +70 -0
- package/skills/service-de-channel-routing-configure/SKILL.md +345 -0
- package/skills/service-de-channel-routing-configure/references/asa-routing.md +90 -0
- package/skills/service-de-channel-routing-configure/references/gotchas.md +27 -0
- package/skills/service-de-channel-routing-configure/references/queue-creation.md +123 -0
- package/skills/service-de-channel-routing-configure/references/target-locate.md +98 -0
- package/skills/service-de-channel-routing-configure/references/worked-examples.md +174 -0
- package/skills/service-de-headless-channel-configure/SKILL.md +305 -0
- package/skills/service-de-headless-channel-configure/references/gotchas.md +27 -0
- package/skills/service-de-headless-channel-configure/references/inputs.md +49 -0
- package/skills/service-de-headless-channel-configure/references/output-envelopes.md +53 -0
- package/skills/service-de-headless-channel-configure/references/partial-success.md +24 -0
- package/skills/service-de-headless-channel-configure/references/terms-and-conditions.md +54 -0
- package/skills/service-de-headless-channel-configure/references/worked-examples.md +108 -0
- package/skills/service-de-waba-integrate/SKILL.md +227 -0
- package/skills/service-digital-engagement-channel-configure/scripts/check-api-version.sh +29 -0
- package/skills/service-digital-engagement-messaging-site-integrate/SKILL.md +10 -9
- package/skills/service-digital-engagement-messaging-site-integrate/references/lwr_patch.md +39 -21
- package/skills/service-digital-engagement-messaging-site-integrate/scripts/patch_lwr_bundle.sh +157 -96
- package/skills/service-email-to-case-configure/SKILL.md +190 -0
- package/skills/service-email-to-case-configure/assets/CaseSettings.settings-meta.xml +45 -0
- package/skills/service-email-to-case-configure/examples/CaseSettings-two-addresses.settings-meta.xml +36 -0
- package/skills/service-email-to-case-configure/references/apply-mechanics.md +73 -0
- package/skills/service-email-to-case-configure/references/routing_address_reference.md +73 -0
- package/skills/service-email-to-case-configure/references/troubleshooting.md +25 -0
- package/skills/service-email-to-case-configure/scripts/apply-casesettings.py +1315 -0
- package/skills/service-email-to-case-configure/scripts/check-agent-email-capability.sh +26 -0
- package/skills/service-email-to-case-configure/scripts/tests/__init__.py +0 -0
- package/skills/service-email-to-case-configure/scripts/tests/_bootstrap.py +45 -0
- package/skills/service-email-to-case-configure/scripts/tests/_fakeorg.py +366 -0
- package/skills/service-email-to-case-configure/scripts/tests/_run.py +84 -0
- package/skills/service-email-to-case-configure/scripts/tests/test_apply_org_scenarios.py +655 -0
- package/skills/service-email-to-case-configure/scripts/tests/test_get_session.py +134 -0
- package/skills/service-email-to-case-configure/scripts/tests/test_new_capabilities.py +193 -0
- package/skills/service-email-to-case-configure/scripts/tests/test_validate_casesettings.py +170 -0
- package/skills/service-email-to-case-configure/scripts/validate-casesettings.py +229 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +33 -15
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -47
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +17 -12
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/references/cli-invocation.md +5 -3
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-enable-plan.mjs +11 -3
- package/skills/service-itsm-agentic-setup-agentforce-studio-validate/SKILL.md +8 -6
- package/skills/service-itsm-agentic-setup-agentforce-studio-validate/references/cli-invocation.md +3 -3
- package/skills/service-itsm-agentic-setup-agentforce-studio-validate/scripts/classify-readiness.mjs +11 -3
- package/skills/service-itsm-agentic-setup-configure/SKILL.md +5 -5
- package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +5 -5
- package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +2 -2
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +11 -7
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +20 -7
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +10 -6
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/action-availability.md +7 -7
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +11 -7
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/build-create-body.mjs +24 -17
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-action-availability.mjs +98 -58
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +20 -7
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/strip-release-management.mjs +176 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/SKILL.md +19 -19
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/cli-invocation.md +3 -4
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/helper-contracts.md +6 -6
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/permset-topology.md +3 -10
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-permset-availability.mjs +7 -9
- package/skills/service-itsm-incident-mgmt-configure/SKILL.md +2 -0
- package/skills/service-itsm-incident-priority-configure/SKILL.md +15 -12
- package/skills/service-itsm-incident-priority-configure/examples/matrix-operations.md +27 -1
- package/skills/service-itsm-incident-priority-configure/references/sf-cli-invocation.md +37 -6
- package/skills/service-native-voice-recording-transcription-configure/SKILL.md +146 -0
- package/skills/service-native-voice-recording-transcription-configure/assets/confirmation-output.json +16 -0
- package/skills/service-native-voice-recording-transcription-configure/assets/package.xml +22 -0
- package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +101 -0
- package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +181 -0
- package/skills/data360-activate/README.md +0 -38
- package/skills/data360-activate/SKILL.md +0 -127
- package/skills/data360-connect/README.md +0 -57
- package/skills/data360-connect/SKILL.md +0 -163
- package/skills/data360-connect/examples/connections/heroku-postgres.json +0 -15
- package/skills/data360-connect/examples/connections/ingest-api-connection.json +0 -5
- package/skills/data360-connect/examples/connections/ingest-api-schema.json +0 -31
- package/skills/data360-connect/examples/connections/redshift.json +0 -16
- package/skills/data360-connect/examples/connections/sharepoint-unstructured.json +0 -20
- package/skills/data360-connect/examples/connections/snowflake-connection.json +0 -42
- package/skills/data360-harmonize/README.md +0 -31
- package/skills/data360-harmonize/SKILL.md +0 -126
- package/skills/data360-orchestrate/README.md +0 -120
- package/skills/data360-orchestrate/SKILL.md +0 -263
- package/skills/data360-orchestrate/assets/definitions/activation-target.template.json +0 -5
- package/skills/data360-orchestrate/assets/definitions/activation.template.json +0 -7
- package/skills/data360-orchestrate/assets/definitions/calculated-insight.template.json +0 -7
- package/skills/data360-orchestrate/assets/definitions/data-action-target.template.json +0 -5
- package/skills/data360-orchestrate/assets/definitions/data-action.template.json +0 -5
- package/skills/data360-orchestrate/assets/definitions/data-graph.template.json +0 -21
- package/skills/data360-orchestrate/assets/definitions/data-stream.template.json +0 -55
- package/skills/data360-orchestrate/assets/definitions/dmo.template.json +0 -17
- package/skills/data360-orchestrate/assets/definitions/identity-resolution.template.json +0 -30
- package/skills/data360-orchestrate/assets/definitions/mapping.template.json +0 -14
- package/skills/data360-orchestrate/assets/definitions/relationship.template.json +0 -12
- package/skills/data360-orchestrate/assets/definitions/search-index.template.json +0 -9
- package/skills/data360-orchestrate/assets/definitions/segment.template.json +0 -16
- package/skills/data360-orchestrate/references/feature-readiness.md +0 -157
- package/skills/data360-orchestrate/references/plugin-setup.md +0 -138
- package/skills/data360-orchestrate/scripts/bootstrap-plugin.sh +0 -53
- package/skills/data360-orchestrate/scripts/diagnose-org.mjs +0 -511
- package/skills/data360-orchestrate/scripts/generate-manifest.mjs +0 -68
- package/skills/data360-orchestrate/scripts/verify-plugin.sh +0 -58
- package/skills/data360-prepare/README.md +0 -50
- package/skills/data360-prepare/SKILL.md +0 -203
- package/skills/data360-prepare/examples/ingestion-api/.env.example +0 -8
- package/skills/data360-prepare/examples/ingestion-api/README.md +0 -48
- package/skills/data360-prepare/examples/ingestion-api/send-data.py +0 -144
- package/skills/data360-query/README.md +0 -43
- package/skills/data360-query/SKILL.md +0 -128
- package/skills/data360-query/examples/search-indexes/hybrid-structured.json +0 -44
- package/skills/data360-query/examples/search-indexes/vector-knowledge.json +0 -43
- package/skills/data360-segment/README.md +0 -35
- package/skills/data360-segment/SKILL.md +0 -123
- package/skills/service-helpagent-coordinate/references/channel-help-portal.md +0 -22
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-1-1-non-text-content.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-3-1-i-lists.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-3-1-ii-tables.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-3-1-iii-form-labels.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-3-1-iv-regions.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-3-1-v-groups.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-3-5-identify-input.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-1-4-3-contrast.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-2-1-1-keyboard.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-2-4-4-link-purpose.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-2-4-6-headings-labels.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-2-5-3-label-in-name.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-2-5-7-dragging-movement.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-3-2-1-on-focus.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-3-2-2-on-input.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-3-3-1-error-identification.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-3-3-2-labels-instructions.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-3-3-3-error-suggestion.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-4-1-2-i-name.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-4-1-2-ii-role.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/reviewers/sc-4-1-2-iii-value.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/vision/sc-1-1-1-non-text-content.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/vision/sc-1-4-1-use-of-color.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/vision/sc-1-4-10-resize-reflow.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/vision/sc-1-4-11-non-text-contrast.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/references/vision/sc-1-4-3-contrast.md +0 -0
- /package/skills/{experience-lwc-accessibility-validate → experience-accessibility-validate}/scripts/contrast-ratio.py +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Preflight: is this org entitled for Agentforce Email-to-Case (the BotEmailDefinition type)?
|
|
3
|
+
#
|
|
4
|
+
# describeMetadata gates BotEmailDefinition on BOTH orgAccess=orgHasAgentforceServiceAgentEmail
|
|
5
|
+
# (the composite ASA-email entitlement) AND API version >= 68. Neither is SOQL-queryable, but a
|
|
6
|
+
# read-only metadata describe surfaces the type only when both hold — so this single call subsumes
|
|
7
|
+
# the entitlement check and the v68 version gate, and needs no agent to exist yet.
|
|
8
|
+
#
|
|
9
|
+
# Exit 0 -> org is entitled; safe to delegate agent creation + channel wiring.
|
|
10
|
+
# Exit 3 -> type absent (not entitled, or below API v68); caller must STOP before creating an agent.
|
|
11
|
+
# Exit !=0/3 -> the org could not be reached/described; treat as inconclusive, not "not entitled".
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
ORG="${1:?usage: check-agent-email-capability.sh <target-org-alias>}"
|
|
15
|
+
|
|
16
|
+
# Resolve the org's TRUE max supported API version and pin the describe to it. describeMetadata
|
|
17
|
+
# filters types by the request's API version (isInContextVersion), so running below v68 would filter
|
|
18
|
+
# BotEmailDefinition out even on an entitled org. Do NOT use `sf org display`'s apiVersion here: that
|
|
19
|
+
# is a cached instanceApiVersion that can lag the org's real max (and is not overridden by
|
|
20
|
+
# --api-version), so an upgraded-but-not-re-authed org could resolve low and yield a false "not
|
|
21
|
+
# entitled". Query /services/data (the live version list) and take the highest.
|
|
22
|
+
API=$(sf api request rest --target-org "$ORG" "/services/data" \
|
|
23
|
+
| python3 -c "import sys, json; print(max((v['version'] for v in json.load(sys.stdin)), key=float))")
|
|
24
|
+
|
|
25
|
+
sf org list metadata-types --target-org "$ORG" --api-version "$API" --json \
|
|
26
|
+
| python3 -c "import sys, json; types = {m['xmlName'] for m in json.load(sys.stdin)['result']['metadataObjects']}; sys.exit(0 if 'BotEmailDefinition' in types else 3)"
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Test bootstrap — loads the hyphenated skill scripts as importable modules.
|
|
2
|
+
|
|
3
|
+
The skill's scripts (``apply-casesettings.py``, ``validate-casesettings.py``)
|
|
4
|
+
have hyphens in their names, so they can't be imported with a normal
|
|
5
|
+
``import``. Every ``test_*.py`` in this directory does::
|
|
6
|
+
|
|
7
|
+
from . import _bootstrap
|
|
8
|
+
apply_mod = _bootstrap.load_apply()
|
|
9
|
+
validate_mod = _bootstrap.load_validate()
|
|
10
|
+
|
|
11
|
+
to get the loaded modules. Loading is cached so repeated calls are cheap and
|
|
12
|
+
return the same module object.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib.util
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# scripts/tests/_bootstrap.py → scripts/
|
|
20
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent.parent
|
|
21
|
+
|
|
22
|
+
_CACHE: dict[str, object] = {}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load(module_name: str, filename: str):
|
|
26
|
+
if module_name in _CACHE:
|
|
27
|
+
return _CACHE[module_name]
|
|
28
|
+
path = _SCRIPTS_DIR / filename
|
|
29
|
+
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
30
|
+
if spec is None or spec.loader is None:
|
|
31
|
+
raise ImportError(f"Could not load {filename} from {path}")
|
|
32
|
+
module = importlib.util.module_from_spec(spec)
|
|
33
|
+
spec.loader.exec_module(module)
|
|
34
|
+
_CACHE[module_name] = module
|
|
35
|
+
return module
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_apply():
|
|
39
|
+
"""Return the loaded ``apply-casesettings.py`` module."""
|
|
40
|
+
return _load("apply_casesettings", "apply-casesettings.py")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_validate():
|
|
44
|
+
"""Return the loaded ``validate-casesettings.py`` module."""
|
|
45
|
+
return _load("validate_casesettings", "validate-casesettings.py")
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""In-memory fake Salesforce org for driving apply-casesettings.py end to end
|
|
2
|
+
without a live org.
|
|
3
|
+
|
|
4
|
+
The skill script funnels ALL network I/O through three seams:
|
|
5
|
+
* ``post_soap(url, envelope, soap_action)`` — readMetadata / updateMetadata
|
|
6
|
+
* ``query_active_user(session_id, instance_url, username)``
|
|
7
|
+
* ``query_queue(session_id, instance_url, name)``
|
|
8
|
+
plus ``get_session`` for the CLI handshake. Patching those four lets the whole
|
|
9
|
+
``main()`` flow run against this fake.
|
|
10
|
+
|
|
11
|
+
``FakeOrg`` holds a CaseSettings state as a nested dict and applies
|
|
12
|
+
``updateMetadata`` with the SAME merge semantics as the platform:
|
|
13
|
+
* top-level scalar fields merge at the field level (an omitted field keeps its
|
|
14
|
+
current value);
|
|
15
|
+
* inside ``emailToCase``, scalar children merge at the field level;
|
|
16
|
+
* the ``routingAddresses`` collection is REPLACED wholesale — but ONLY when the
|
|
17
|
+
payload's ``emailToCase`` actually contains routingAddresses. A payload whose
|
|
18
|
+
``emailToCase`` omits routingAddresses (e.g. Phase A) leaves existing
|
|
19
|
+
addresses untouched.
|
|
20
|
+
* ORDERING drives multi-address preservation. An address already provisioned
|
|
21
|
+
in the org (matched by routingName/emailAddress) is DROPPED from the replaced
|
|
22
|
+
collection if any brand-new address appears AFTER it in document order; new
|
|
23
|
+
addresses always survive. Placing new addresses BEFORE existing ones (what
|
|
24
|
+
the skill's Phase B does) preserves everything. This was proven live: the
|
|
25
|
+
platform-managed read-only fields (emailServicesAddress/isVerified) are NOT
|
|
26
|
+
the cause — existing addresses dropped with those fields stripped and
|
|
27
|
+
survived with them kept; only order mattered. Surviving existing addresses
|
|
28
|
+
keep their minted read-only fields (so a verified address stays verified).
|
|
29
|
+
|
|
30
|
+
The ``drop_one_existing_on_write`` flag injects a fault: the org drops one
|
|
31
|
+
existing address on any multi-address write regardless of order, to exercise the
|
|
32
|
+
skill's preservation guard (its runtime safety net) independent of the ordering
|
|
33
|
+
model above.
|
|
34
|
+
|
|
35
|
+
Reads reflect current state. Active users / queues are configured per test.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import copy
|
|
40
|
+
import xml.etree.ElementTree as ET
|
|
41
|
+
from unittest import mock
|
|
42
|
+
|
|
43
|
+
META_NS = "http://soap.sforce.com/2006/04/metadata"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _q(name: str) -> str:
|
|
47
|
+
return f"{{{META_NS}}}{name}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _local(tag: str) -> str:
|
|
51
|
+
return tag.split("}", 1)[-1] if "}" in tag else tag
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class FakeOrg:
|
|
55
|
+
"""Models a single org's CaseSettings for apply-casesettings.py tests."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, case_settings=None, active_users=None, queues=None,
|
|
58
|
+
is_sandbox=True, org_type="Developer Edition", is_trial=False,
|
|
59
|
+
org_api_version="67.0", cases=None, email_messages=None,
|
|
60
|
+
drop_one_existing_on_write=False):
|
|
61
|
+
# Current CaseSettings state (nested dict; scalars are python str/bool).
|
|
62
|
+
self.state = copy.deepcopy(case_settings) if case_settings else {}
|
|
63
|
+
# Active usernames (exact match, case-insensitive handled below).
|
|
64
|
+
self.active_users = {u.lower() for u in (active_users or [])}
|
|
65
|
+
# Queue DeveloperNames (and Names) that resolve.
|
|
66
|
+
self.queues = set(queues or [])
|
|
67
|
+
# Record of every updateMetadata payload sent, as nested dicts, for
|
|
68
|
+
# asserting exactly what was written (e.g. stripped/kept fields).
|
|
69
|
+
self.writes: list[dict] = []
|
|
70
|
+
self.instance_url = "https://fake.my.salesforce.com"
|
|
71
|
+
self.auth_username = "authuser@fake.example.com"
|
|
72
|
+
# Organization row for the production-gate. is_sandbox=None models an
|
|
73
|
+
# unreadable Organization row (gate treats it as production).
|
|
74
|
+
self.is_sandbox = is_sandbox
|
|
75
|
+
self.org_type = org_type
|
|
76
|
+
self.is_trial = is_trial
|
|
77
|
+
# apiVersion the fake `sf org display` reports (for version derivation).
|
|
78
|
+
self.org_api_version = org_api_version
|
|
79
|
+
# Rows returned by the Act-3 verify-cases queries.
|
|
80
|
+
self.cases = list(cases or [])
|
|
81
|
+
self.email_messages = list(email_messages or [])
|
|
82
|
+
# Fault injection: when True, any multi-address write drops one existing
|
|
83
|
+
# address regardless of ordering, to exercise the skill's preservation
|
|
84
|
+
# guard independently of the (correctly-ordered) happy path.
|
|
85
|
+
self.drop_one_existing_on_write = drop_one_existing_on_write
|
|
86
|
+
|
|
87
|
+
# -- session / lookups -------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def fake_get_session(self, target_org):
|
|
90
|
+
# Mirror the real get_session's version derivation (floor 67.0) so the
|
|
91
|
+
# metadata URL matches what a live run would produce. get_session is
|
|
92
|
+
# patched wholesale in tests, so resolve_api_version is unit-tested
|
|
93
|
+
# directly; this keeps the fake's URL faithful.
|
|
94
|
+
version = self.org_api_version
|
|
95
|
+
try:
|
|
96
|
+
major = int(str(version).split(".")[0])
|
|
97
|
+
if major < 67:
|
|
98
|
+
version = "67.0"
|
|
99
|
+
except (ValueError, IndexError):
|
|
100
|
+
version = "67.0"
|
|
101
|
+
return ("SESSIONID", f"{self.instance_url}/services/Soap/m/{version}",
|
|
102
|
+
self.instance_url, self.auth_username)
|
|
103
|
+
|
|
104
|
+
def fake_query_active_user(self, session_id, instance_url, username):
|
|
105
|
+
if username and username.lower() in self.active_users:
|
|
106
|
+
return username
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
def fake_query_queue(self, session_id, instance_url, name):
|
|
110
|
+
return name if name in self.queues else None
|
|
111
|
+
|
|
112
|
+
def fake_query_org_info(self, session_id, instance_url):
|
|
113
|
+
return self.is_sandbox, self.org_type, self.is_trial
|
|
114
|
+
|
|
115
|
+
def fake_query_email_cases(self, session_id, instance_url,
|
|
116
|
+
supplied_email=None):
|
|
117
|
+
rows = self.cases
|
|
118
|
+
if supplied_email is not None:
|
|
119
|
+
rows = [c for c in rows if c.get("SuppliedEmail") == supplied_email]
|
|
120
|
+
return list(rows)
|
|
121
|
+
|
|
122
|
+
def fake_query_incoming_email_messages(self, session_id, instance_url,
|
|
123
|
+
case_ids):
|
|
124
|
+
ids = set(case_ids)
|
|
125
|
+
return [m for m in self.email_messages
|
|
126
|
+
if m.get("Incoming") and m.get("ParentId") in ids]
|
|
127
|
+
|
|
128
|
+
# -- SOAP -------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def fake_post_soap(self, url, envelope, soap_action):
|
|
131
|
+
if soap_action == "readMetadata":
|
|
132
|
+
return self._read_response()
|
|
133
|
+
if soap_action == "updateMetadata":
|
|
134
|
+
return self._apply_update(envelope)
|
|
135
|
+
raise AssertionError(f"unexpected SOAPAction {soap_action!r}")
|
|
136
|
+
|
|
137
|
+
def _read_response(self):
|
|
138
|
+
"""Build a SOAP-ish response whose .//records mirrors current state."""
|
|
139
|
+
env = ET.Element(_q("Envelope"))
|
|
140
|
+
body = ET.SubElement(env, _q("Body"))
|
|
141
|
+
resp = ET.SubElement(body, _q("readMetadataResponse"))
|
|
142
|
+
result = ET.SubElement(resp, _q("result"))
|
|
143
|
+
records = ET.SubElement(result, _q("records"))
|
|
144
|
+
self._dict_to_xml(records, self.state)
|
|
145
|
+
return env
|
|
146
|
+
|
|
147
|
+
def _apply_update(self, envelope):
|
|
148
|
+
"""Parse the payload, record it, merge it into state, return success."""
|
|
149
|
+
root = ET.fromstring(envelope)
|
|
150
|
+
# find the <metadata> element under updateMetadata
|
|
151
|
+
metadata = None
|
|
152
|
+
for el in root.iter():
|
|
153
|
+
if _local(el.tag) == "metadata":
|
|
154
|
+
metadata = el
|
|
155
|
+
break
|
|
156
|
+
assert metadata is not None, "updateMetadata payload missing <metadata>"
|
|
157
|
+
payload = self._xml_to_dict(metadata)
|
|
158
|
+
self.writes.append(copy.deepcopy(payload))
|
|
159
|
+
self._merge(payload)
|
|
160
|
+
return self._success_response()
|
|
161
|
+
|
|
162
|
+
def _merge(self, payload):
|
|
163
|
+
for key, val in payload.items():
|
|
164
|
+
if key == "fullName":
|
|
165
|
+
continue
|
|
166
|
+
if key == "emailToCase" and isinstance(val, dict):
|
|
167
|
+
self._merge_e2c(val)
|
|
168
|
+
else:
|
|
169
|
+
# top-level scalar / block: field-level overwrite
|
|
170
|
+
self.state[key] = val
|
|
171
|
+
|
|
172
|
+
# Platform-managed read-only fields the org mints on a provisioned address.
|
|
173
|
+
_READONLY_ADDRESS_FIELDS = ("emailServicesAddress", "isVerified")
|
|
174
|
+
|
|
175
|
+
def _addr_key(self, addr):
|
|
176
|
+
"""Identity used to match a payload address against a provisioned one."""
|
|
177
|
+
return addr.get("routingName") or addr.get("emailAddress")
|
|
178
|
+
|
|
179
|
+
def _existing_address_keys(self):
|
|
180
|
+
cur = self.state.get("emailToCase") or {}
|
|
181
|
+
addrs = cur.get("routingAddresses")
|
|
182
|
+
if addrs is None:
|
|
183
|
+
return set()
|
|
184
|
+
if isinstance(addrs, dict):
|
|
185
|
+
addrs = [addrs]
|
|
186
|
+
return {self._addr_key(a) for a in addrs}
|
|
187
|
+
|
|
188
|
+
def _merge_e2c(self, payload_e2c):
|
|
189
|
+
cur = self.state.setdefault("emailToCase", {})
|
|
190
|
+
for key, val in payload_e2c.items():
|
|
191
|
+
if key == "routingAddresses":
|
|
192
|
+
cur["routingAddresses"] = self._resolve_addresses(val)
|
|
193
|
+
else:
|
|
194
|
+
cur[key] = val
|
|
195
|
+
|
|
196
|
+
def _resolve_addresses(self, val):
|
|
197
|
+
"""Model the platform's wholesale routingAddresses REPLACE.
|
|
198
|
+
|
|
199
|
+
The collection is replaced by the payload, but with the live-verified
|
|
200
|
+
ORDERING behavior: an address already provisioned in the org is DROPPED
|
|
201
|
+
if any brand-new address appears AFTER it in document order. New
|
|
202
|
+
addresses always survive. Surviving existing addresses keep the
|
|
203
|
+
platform-minted read-only fields they were provisioned with (so a
|
|
204
|
+
verified address stays verified); brand-new addresses get freshly minted
|
|
205
|
+
fields (unverified). Read-only fields declared in the payload are
|
|
206
|
+
ignored for survival — only order matters."""
|
|
207
|
+
items = [dict(a) for a in (val if isinstance(val, list) else [val])]
|
|
208
|
+
existing_keys = self._existing_address_keys()
|
|
209
|
+
prior = {self._addr_key(a): a
|
|
210
|
+
for a in self._existing_address_list()}
|
|
211
|
+
|
|
212
|
+
# Fault injection: drop one existing address regardless of order.
|
|
213
|
+
if self.drop_one_existing_on_write:
|
|
214
|
+
for i, addr in enumerate(items):
|
|
215
|
+
if self._addr_key(addr) in existing_keys:
|
|
216
|
+
items.pop(i)
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
survivors = []
|
|
220
|
+
for i, addr in enumerate(items):
|
|
221
|
+
key = self._addr_key(addr)
|
|
222
|
+
is_existing = key in existing_keys
|
|
223
|
+
if is_existing:
|
|
224
|
+
# Dropped if any brand-new address follows this one.
|
|
225
|
+
if any(self._addr_key(later) not in existing_keys
|
|
226
|
+
for later in items[i + 1:]):
|
|
227
|
+
continue
|
|
228
|
+
# Survives: carry forward its previously-minted read-only fields.
|
|
229
|
+
minted = dict(addr)
|
|
230
|
+
for f in self._READONLY_ADDRESS_FIELDS:
|
|
231
|
+
if f in prior.get(key, {}):
|
|
232
|
+
minted[f] = prior[key][f]
|
|
233
|
+
survivors.append(minted)
|
|
234
|
+
else:
|
|
235
|
+
# Brand-new address: platform mints fresh read-only fields.
|
|
236
|
+
minted = {k: v for k, v in addr.items()
|
|
237
|
+
if k not in self._READONLY_ADDRESS_FIELDS}
|
|
238
|
+
email = minted.get("emailAddress", "addr")
|
|
239
|
+
minted["emailServicesAddress"] = f"{email}.svc.example.com"
|
|
240
|
+
minted["isVerified"] = False
|
|
241
|
+
survivors.append(minted)
|
|
242
|
+
return survivors if len(survivors) != 1 else survivors[0]
|
|
243
|
+
|
|
244
|
+
def _existing_address_list(self):
|
|
245
|
+
cur = self.state.get("emailToCase") or {}
|
|
246
|
+
addrs = cur.get("routingAddresses")
|
|
247
|
+
if addrs is None:
|
|
248
|
+
return []
|
|
249
|
+
return [addrs] if isinstance(addrs, dict) else list(addrs)
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _success_response():
|
|
253
|
+
env = ET.Element(_q("Envelope"))
|
|
254
|
+
body = ET.SubElement(env, _q("Body"))
|
|
255
|
+
resp = ET.SubElement(body, _q("updateMetadataResponse"))
|
|
256
|
+
result = ET.SubElement(resp, _q("result"))
|
|
257
|
+
ET.SubElement(result, _q("success")).text = "true"
|
|
258
|
+
ET.SubElement(result, _q("fullName")).text = "Case"
|
|
259
|
+
return env
|
|
260
|
+
|
|
261
|
+
# -- dict <-> xml -----------------------------------------------------
|
|
262
|
+
|
|
263
|
+
def _dict_to_xml(self, parent, value):
|
|
264
|
+
for key, val in value.items():
|
|
265
|
+
if isinstance(val, list):
|
|
266
|
+
for item in val:
|
|
267
|
+
child = ET.SubElement(parent, _q(key))
|
|
268
|
+
self._dict_to_xml(child, item)
|
|
269
|
+
elif isinstance(val, dict):
|
|
270
|
+
child = ET.SubElement(parent, _q(key))
|
|
271
|
+
self._dict_to_xml(child, val)
|
|
272
|
+
else:
|
|
273
|
+
child = ET.SubElement(parent, _q(key))
|
|
274
|
+
child.text = self._scalar_text(val)
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def _scalar_text(val):
|
|
278
|
+
if val is True:
|
|
279
|
+
return "true"
|
|
280
|
+
if val is False:
|
|
281
|
+
return "false"
|
|
282
|
+
return str(val)
|
|
283
|
+
|
|
284
|
+
def _xml_to_dict(self, elem):
|
|
285
|
+
children = list(elem)
|
|
286
|
+
if not children:
|
|
287
|
+
text = (elem.text or "").strip()
|
|
288
|
+
if text.lower() == "true":
|
|
289
|
+
return True
|
|
290
|
+
if text.lower() == "false":
|
|
291
|
+
return False
|
|
292
|
+
return text
|
|
293
|
+
result: dict = {}
|
|
294
|
+
for child in children:
|
|
295
|
+
tag = _local(child.tag)
|
|
296
|
+
sub = self._xml_to_dict(child)
|
|
297
|
+
if tag in result:
|
|
298
|
+
if not isinstance(result[tag], list):
|
|
299
|
+
result[tag] = [result[tag]]
|
|
300
|
+
result[tag].append(sub)
|
|
301
|
+
else:
|
|
302
|
+
result[tag] = sub
|
|
303
|
+
return result
|
|
304
|
+
|
|
305
|
+
# -- patching ---------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
def patch(self, apply_mod):
|
|
308
|
+
"""Return a context manager patching the four seams on apply_mod."""
|
|
309
|
+
return _Patches(self, apply_mod)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class _Patches:
|
|
313
|
+
def __init__(self, org: FakeOrg, apply_mod):
|
|
314
|
+
self.org = org
|
|
315
|
+
self.mod = apply_mod
|
|
316
|
+
self._patchers = []
|
|
317
|
+
|
|
318
|
+
def __enter__(self):
|
|
319
|
+
# get_session has a documented side-effect: unless --api-version was
|
|
320
|
+
# given, it sets the module's API_VERSION global to the org-derived,
|
|
321
|
+
# floored version. The real function is mocked here, so mirror that
|
|
322
|
+
# side-effect on the patched module using its own resolve_api_version.
|
|
323
|
+
mod = self.mod
|
|
324
|
+
org = self.org
|
|
325
|
+
|
|
326
|
+
def fake_get_session(target_org):
|
|
327
|
+
if not getattr(mod, "_API_VERSION_EXPLICIT", False):
|
|
328
|
+
mod.API_VERSION = mod.resolve_api_version(org.org_api_version)
|
|
329
|
+
return org.fake_get_session(target_org)
|
|
330
|
+
|
|
331
|
+
seams = {
|
|
332
|
+
"get_session": fake_get_session,
|
|
333
|
+
"post_soap": self.org.fake_post_soap,
|
|
334
|
+
"query_active_user": self.org.fake_query_active_user,
|
|
335
|
+
"query_queue": self.org.fake_query_queue,
|
|
336
|
+
"query_org_info": self.org.fake_query_org_info,
|
|
337
|
+
"query_email_cases": self.org.fake_query_email_cases,
|
|
338
|
+
"query_incoming_email_messages":
|
|
339
|
+
self.org.fake_query_incoming_email_messages,
|
|
340
|
+
}
|
|
341
|
+
for name, fn in seams.items():
|
|
342
|
+
p = mock.patch.object(self.mod, name, side_effect=fn)
|
|
343
|
+
p.start()
|
|
344
|
+
self._patchers.append(p)
|
|
345
|
+
return self
|
|
346
|
+
|
|
347
|
+
def __exit__(self, *exc):
|
|
348
|
+
for p in self._patchers:
|
|
349
|
+
p.stop()
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def last_e2c_write(org: FakeOrg) -> dict:
|
|
354
|
+
"""The emailToCase block of the most recent updateMetadata payload."""
|
|
355
|
+
for w in reversed(org.writes):
|
|
356
|
+
if "emailToCase" in w:
|
|
357
|
+
return w["emailToCase"]
|
|
358
|
+
raise AssertionError("no updateMetadata write carried an emailToCase block")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def top_level_keys_written(org: FakeOrg) -> set:
|
|
362
|
+
"""Union of all top-level keys across every updateMetadata payload."""
|
|
363
|
+
keys: set = set()
|
|
364
|
+
for w in org.writes:
|
|
365
|
+
keys |= set(w.keys())
|
|
366
|
+
return keys
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Helpers to drive apply-casesettings.py main() in tests: write a temp
|
|
2
|
+
CaseSettings input file and invoke main() with an argv, capturing the JSON
|
|
3
|
+
summary it prints. main() calls fail()->sys.exit(1) on error, and argparse may
|
|
4
|
+
sys.exit(2); both surface as SystemExit for the test to assert on.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import io
|
|
9
|
+
import json
|
|
10
|
+
import tempfile
|
|
11
|
+
from contextlib import redirect_stdout
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from unittest import mock
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def write_input(tmpdir: str, xml: str) -> str:
|
|
17
|
+
path = Path(tmpdir) / "Case.settings-meta.xml"
|
|
18
|
+
path.write_text(xml, encoding="utf-8")
|
|
19
|
+
return str(path)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run_main(apply_mod, argv):
|
|
23
|
+
"""Run main() with the given argv (excluding program name). Returns the
|
|
24
|
+
parsed JSON summary dict printed to stdout. Raises SystemExit as-is."""
|
|
25
|
+
buf = io.StringIO()
|
|
26
|
+
with mock.patch("sys.argv", ["apply-casesettings.py", *argv]):
|
|
27
|
+
with redirect_stdout(buf):
|
|
28
|
+
apply_mod.main()
|
|
29
|
+
out = buf.getvalue().strip()
|
|
30
|
+
return json.loads(out) if out else {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Input:
|
|
34
|
+
"""Fluent builder for a CaseSettings source XML string."""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self._top = {}
|
|
38
|
+
self._e2c = {"enableEmailToCase": "true",
|
|
39
|
+
"enableOnDemandEmailToCase": "true"}
|
|
40
|
+
self._addrs = []
|
|
41
|
+
|
|
42
|
+
def top(self, **kv):
|
|
43
|
+
self._top.update(kv)
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def e2c(self, **kv):
|
|
47
|
+
self._e2c.update(kv)
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def no_e2c_defaults(self):
|
|
51
|
+
self._e2c = {"enableEmailToCase": "true",
|
|
52
|
+
"enableOnDemandEmailToCase": "true"}
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def with_default_toggles(self):
|
|
56
|
+
self._e2c.update({
|
|
57
|
+
"enableHtmlEmail": "true",
|
|
58
|
+
"notifyOwnerOnNewCaseEmail": "true",
|
|
59
|
+
"enableE2CDeduplicateAttachments": "true",
|
|
60
|
+
"showWordCountInComposer": "true",
|
|
61
|
+
})
|
|
62
|
+
self._top["enableDraftEmails"] = "true"
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def address(self, **kv):
|
|
66
|
+
self._addrs.append(kv)
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def build(self) -> str:
|
|
70
|
+
lines = ['<?xml version="1.0" encoding="UTF-8"?>',
|
|
71
|
+
'<CaseSettings xmlns="http://soap.sforce.com/2006/04/metadata">']
|
|
72
|
+
for k, v in self._top.items():
|
|
73
|
+
lines.append(f" <{k}>{v}</{k}>")
|
|
74
|
+
lines.append(" <emailToCase>")
|
|
75
|
+
for k, v in self._e2c.items():
|
|
76
|
+
lines.append(f" <{k}>{v}</{k}>")
|
|
77
|
+
for addr in self._addrs:
|
|
78
|
+
lines.append(" <routingAddresses>")
|
|
79
|
+
for k, v in addr.items():
|
|
80
|
+
lines.append(f" <{k}>{v}</{k}>")
|
|
81
|
+
lines.append(" </routingAddresses>")
|
|
82
|
+
lines.append(" </emailToCase>")
|
|
83
|
+
lines.append("</CaseSettings>")
|
|
84
|
+
return "\n".join(lines) + "\n"
|