@salesforce/afv-skills 1.46.0 → 1.48.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 +1 -1
- package/skills/agentforce-observe/SKILL.md +32 -4
- package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
- package/skills/dx-devops-project-manage/SKILL.md +197 -0
- package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
- package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
- package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
- package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
- package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
- package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
- package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
- package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
- package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
- package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
- package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
- package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
- package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
- package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
- package/skills/education-cloud-domain-configure/SKILL.md +234 -0
- package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
- package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
- package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
- package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
- package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
- package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
- package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
- package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +1 -2
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
- package/skills/experience-cms-brand-apply/SKILL.md +5 -5
- package/skills/experience-cms-brand-create/SKILL.md +2 -2
- package/skills/experience-cms-content-generate/SKILL.md +1 -0
- package/skills/experience-cms-content-render/SKILL.md +173 -0
- package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
- package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
- package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
- package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
- package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
- package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
- package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
- package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
- package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
- package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
- package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
- package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
- package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
- package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
- package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
- package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
- package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
- package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
- package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
- package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
- package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
- package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
- package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
- package/skills/experience-cms-content-render/references/package-api.md +106 -0
- package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
- package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
- package/skills/experience-cms-content-render/references/verify.md +49 -0
- package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
- package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
- package/skills/experience-search-coordinate/SKILL.md +198 -0
- package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
- package/skills/experience-search-coordinate/references/content-route.md +313 -0
- package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
- package/skills/experience-search-coordinate/references/media-route.md +172 -0
- package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
- package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
- package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
- package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
- package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
- package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
- package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
- package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
- package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
- package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
- package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
- package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
- package/skills/life-sciences-territory-configure/SKILL.md +217 -0
- package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
- package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
- package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
- package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
- package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
- package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
- package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
- package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
- package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
- package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
- package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
- package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
- package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
- package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
- package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
- package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
- package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
- package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
- package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
- package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
- package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
- package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
- package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
- package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
- package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
- package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
- package/skills/service-omni-agent-users-create/SKILL.md +96 -0
- package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
- package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
- package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
- package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
- package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
- package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
- package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
- package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
- package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
- package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
- package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
- package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
- package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
- package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
- package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
- package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
- package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
- package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
- package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
- package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
- package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
- package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
- package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
- package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
- package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
- package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
- package/skills/service-omni-queue-deploy/SKILL.md +91 -0
- package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
- package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
- package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
- package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
- package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
- package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
- package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
- package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
- package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
- package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
- package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
- package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
- package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
- package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
- package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
- package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
- package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
- package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
- package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
- package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
- package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
- package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
- package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
- package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
- package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
- package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
- package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
- package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
- package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
- package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
- package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
- package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
- package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
- package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
- package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
- package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# configure-and-report.sh - author a WorkSkillRouting rule (field-value -> required Omni Skill) for a related entity via the Metadata API; detect-before-write, idempotent, graceful-degrade when the feature is unavailable. Args, env, exit codes: SKILL.md.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
if [ $# -lt 5 ]; then
|
|
7
|
+
echo '{"error":"Usage: bash configure-and-report.sh <org-alias> <related_entity> <field> <skill> <value> [rule_master_label]"}' >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
ORG="$1"
|
|
12
|
+
RELATED_ENTITY="$2"
|
|
13
|
+
FIELD="$3"
|
|
14
|
+
SKILL="$4"
|
|
15
|
+
VALUE="$5"
|
|
16
|
+
MASTER_LABEL_EXPLICIT=false
|
|
17
|
+
{ [ -n "${6:-}" ] || [ -n "${RULE_MASTER_LABEL:-}" ]; } && MASTER_LABEL_EXPLICIT=true
|
|
18
|
+
IS_ACTIVE_EXPLICIT=false
|
|
19
|
+
[ -n "${IS_ACTIVE:-}" ] && IS_ACTIVE_EXPLICIT=true
|
|
20
|
+
RULE_MASTER_LABEL="${6:-${RULE_MASTER_LABEL:-$RELATED_ENTITY Skill Routing}}"
|
|
21
|
+
|
|
22
|
+
RULE_DN="${RULE_DEVELOPER_NAME:-$RELATED_ENTITY}"
|
|
23
|
+
IS_ACTIVE="${IS_ACTIVE:-true}"
|
|
24
|
+
SKILL_LEVEL="${SKILL_LEVEL:-}"
|
|
25
|
+
SKILL_PRIORITY="${SKILL_PRIORITY:-}"
|
|
26
|
+
IS_ADDITIONAL_SKILL="${IS_ADDITIONAL_SKILL:-}"
|
|
27
|
+
|
|
28
|
+
# ---- validation (reject before any sf call) ----
|
|
29
|
+
if ! [[ "$RELATED_ENTITY" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
|
|
30
|
+
echo "{\"error\":\"Invalid related_entity '$RELATED_ENTITY'. Must be an sObject API name (letters, digits, underscores; max 80).\"}" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
if ! [[ "$FIELD" =~ ^[A-Za-z][A-Za-z0-9_.]{0,120}$ ]]; then
|
|
34
|
+
echo "{\"error\":\"Invalid field '$FIELD'. Use a field API name, optionally Entity.Field (letters, digits, underscores, dots).\"}" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
if ! [[ "$SKILL" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
|
|
38
|
+
echo "{\"error\":\"Invalid skill '$SKILL'. Must be a Skill DeveloperName (letters, digits, underscores; max 80).\"}" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
if ! [[ "$VALUE" =~ ^[A-Za-z0-9][A-Za-z0-9\ _./-]{0,254}$ ]]; then
|
|
42
|
+
echo "{\"error\":\"Invalid value '$VALUE'. Provide a plain field value (letters, digits, space, . _ / -); XML-reserved characters are not supported.\"}" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
if ! [[ "$RULE_DN" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
|
|
46
|
+
echo "{\"error\":\"Invalid RULE_DEVELOPER_NAME '$RULE_DN'.\"}" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
if ! [[ "$RULE_MASTER_LABEL" =~ ^[A-Za-z0-9][A-Za-z0-9\ _-]{0,79}$ ]]; then
|
|
50
|
+
echo "{\"error\":\"Invalid rule_master_label '$RULE_MASTER_LABEL'.\"}" >&2
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
if [ "$IS_ACTIVE" != "true" ] && [ "$IS_ACTIVE" != "false" ]; then
|
|
54
|
+
echo "{\"error\":\"Invalid IS_ACTIVE '$IS_ACTIVE' - must be true or false.\"}" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
if [ -n "$SKILL_LEVEL" ] && { ! [[ "$SKILL_LEVEL" =~ ^[0-9]+$ ]] || [ "$SKILL_LEVEL" -gt 10 ]; }; then
|
|
58
|
+
echo "{\"error\":\"Invalid SKILL_LEVEL '$SKILL_LEVEL' - must be an integer 0..10.\"}" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
if [ -n "$SKILL_PRIORITY" ] && { ! [[ "$SKILL_PRIORITY" =~ ^[0-9]+$ ]] || [ "$SKILL_PRIORITY" -gt 10 ]; }; then
|
|
62
|
+
echo "{\"error\":\"Invalid SKILL_PRIORITY '$SKILL_PRIORITY' - must be an integer 0..10.\"}" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
if [ -n "$IS_ADDITIONAL_SKILL" ] && [ "$IS_ADDITIONAL_SKILL" != "true" ] && [ "$IS_ADDITIONAL_SKILL" != "false" ]; then
|
|
66
|
+
echo "{\"error\":\"Invalid IS_ADDITIONAL_SKILL '$IS_ADDITIONAL_SKILL' - must be true or false.\"}" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# The Metadata API requires WorkSkillRoutingAttribute.field to be entity-qualified.
|
|
71
|
+
# Bare standard fields are otherwise resolved as CustomField metadata and fail to deploy.
|
|
72
|
+
if [[ "$FIELD" != *.* ]]; then
|
|
73
|
+
FIELD="${RELATED_ENTITY}.${FIELD}"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
|
|
77
|
+
echo "{\"error\":\"Org alias '$ORG' is not authenticated. Run: sf org login web -a $ORG -r <my-domain-url>\"}" >&2
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
emit_blocked() {
|
|
82
|
+
local msg="$1"; local target_skill="${2:-}"
|
|
83
|
+
local manual_actions='[]'
|
|
84
|
+
if [ -n "$target_skill" ]; then
|
|
85
|
+
manual_actions=$(jq -n --arg ts "$target_skill" '[{id:"MISSING_PREREQ",title:"Run prerequisite skill",target_skill:$ts}]')
|
|
86
|
+
fi
|
|
87
|
+
jq -n --arg msg "$msg" --arg rdn "$RULE_DN" --arg ent "$RELATED_ENTITY" --argjson ma "$manual_actions" \
|
|
88
|
+
'{skill:"service-omni-work-skill-routing-configure",status:"blocked",
|
|
89
|
+
rule:{developer_name:$rdn,related_entity:$ent,state:"not_deployed"},
|
|
90
|
+
attribute:null,deploy_id:null,manual_actions:$ma,blocking_issue:$msg}'
|
|
91
|
+
exit 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
emit_plan() {
|
|
95
|
+
local detail="$1"
|
|
96
|
+
jq -n --arg detail "$detail" --arg rdn "$RULE_DN" --arg ent "$RELATED_ENTITY" \
|
|
97
|
+
'{skill:"service-omni-work-skill-routing-configure",status:"action_needed",plan_mode:true,
|
|
98
|
+
plan_detail:$detail,rule:{developer_name:$rdn,related_entity:$ent},manual_actions:[],blocking_issue:null}'
|
|
99
|
+
exit 0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Step 1 - Organization + safe_to_write guard
|
|
103
|
+
ORG_JSON=$(sf data query --target-org "$ORG" \
|
|
104
|
+
--query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" --json 2>/dev/null) \
|
|
105
|
+
|| emit_blocked "Failed to query Organization for the safe_to_write guard."
|
|
106
|
+
IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox')
|
|
107
|
+
TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
|
|
108
|
+
ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType')
|
|
109
|
+
SAFE_TO_WRITE=false
|
|
110
|
+
if [ "$IS_SANDBOX" = "true" ] || [ "$TRIAL_EXP" != "null" ] \
|
|
111
|
+
|| [ "$ORG_TYPE" = "Developer Edition" ] || [ "$ORG_TYPE" = "Base Edition" ]; then
|
|
112
|
+
SAFE_TO_WRITE=true
|
|
113
|
+
fi
|
|
114
|
+
[ "$SAFE_TO_WRITE" = "true" ] || emit_blocked "Refusing to author a routing rule on a real production customer org (IsSandbox=$IS_SANDBOX, TrialExpirationDate=$TRIAL_EXP, OrganizationType=$ORG_TYPE). Target a sandbox, trial CDO, or Developer Edition org."
|
|
115
|
+
|
|
116
|
+
# Step 2 - Referenced Skill must exist (a WorkSkillRoutingAttribute.skill points at a Skill DeveloperName)
|
|
117
|
+
SKILL_JSON=$(sf data query --target-org "$ORG" --json \
|
|
118
|
+
--query "SELECT Id FROM Skill WHERE DeveloperName='$SKILL'" 2>/dev/null || echo '{}')
|
|
119
|
+
SKILL_COUNT=$(echo "$SKILL_JSON" | jq -r '.result.records | length' 2>/dev/null || echo 0)
|
|
120
|
+
[ "${SKILL_COUNT:-0}" -ge 1 ] || emit_blocked "Referenced Skill '$SKILL' does not exist; deploy it first so the rule can bind to it." "service-omni-skills-based-routing-configure"
|
|
121
|
+
|
|
122
|
+
# A metadata deploy replaces the full one-per-entity rule. Read and merge the
|
|
123
|
+
# current attribute array so adding Origin does not erase an existing Priority rule.
|
|
124
|
+
MERGE_MODE=true
|
|
125
|
+
[ "${WSR_REPLACE:-}" = "1" ] && MERGE_MODE=false
|
|
126
|
+
EXISTING_ATTRS='[]'
|
|
127
|
+
MERGED_FROM_EXISTING=false
|
|
128
|
+
if [ "$MERGE_MODE" = "true" ]; then
|
|
129
|
+
WSR_META_JSON=$(sf data query --target-org "$ORG" --use-tooling-api --json \
|
|
130
|
+
--query "SELECT Metadata FROM WorkSkillRouting WHERE DeveloperName='$RULE_DN'" 2>/dev/null || true)
|
|
131
|
+
if echo "$WSR_META_JSON" | jq -e '.result.totalSize == 1' >/dev/null 2>&1; then
|
|
132
|
+
MERGED_FROM_EXISTING=true
|
|
133
|
+
EXISTING_ATTRS=$(echo "$WSR_META_JSON" | jq -c '.result.records[0].Metadata.workSkillRoutingAttributes // []')
|
|
134
|
+
PRESERVED_LABEL=$(echo "$WSR_META_JSON" | jq -r '.result.records[0].Metadata.masterLabel // ""')
|
|
135
|
+
PRESERVED_ACTIVE=$(echo "$WSR_META_JSON" | jq -r '.result.records[0].Metadata.isActive // empty')
|
|
136
|
+
[ "$MASTER_LABEL_EXPLICIT" = "true" ] || [ -z "$PRESERVED_LABEL" ] || RULE_MASTER_LABEL="$PRESERVED_LABEL"
|
|
137
|
+
[ "$IS_ACTIVE_EXPLICIT" = "true" ] || [ -z "$PRESERVED_ACTIVE" ] || IS_ACTIVE="$PRESERVED_ACTIVE"
|
|
138
|
+
elif echo "$WSR_META_JSON" | jq -e '.result.totalSize == 0' >/dev/null 2>&1; then
|
|
139
|
+
:
|
|
140
|
+
elif ! echo "$WSR_META_JSON" | grep -qiE 'INVALID_TYPE|not available|no such|Cannot find'; then
|
|
141
|
+
emit_blocked "Could not read existing WorkSkillRouting '$RULE_DN' for a merge. Refusing a destructive whole-rule replace; retry the read or set WSR_REPLACE=1 to overwrite intentionally."
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
NEW_ATTR=$(jq -n \
|
|
146
|
+
--arg field "$FIELD" --arg skill "$SKILL" --arg value "$VALUE" \
|
|
147
|
+
--arg lvl "$SKILL_LEVEL" --arg pri "$SKILL_PRIORITY" --arg add "$IS_ADDITIONAL_SKILL" \
|
|
148
|
+
'{field:$field,skill:$skill,value:$value}
|
|
149
|
+
+ (if $add=="" then {} else {isAdditionalSkill:($add=="true")} end)
|
|
150
|
+
+ (if $lvl=="" then {} else {skillLevel:($lvl|tonumber)} end)
|
|
151
|
+
+ (if $pri=="" then {} else {skillPriority:($pri|tonumber)} end)')
|
|
152
|
+
MERGED_ATTRS=$(jq -c -n --argjson existing "$EXISTING_ATTRS" --argjson new "$NEW_ATTR" \
|
|
153
|
+
'($existing | map(select((.field==$new.field and .value==$new.value) | not))) + [$new]')
|
|
154
|
+
ATTR_COUNT=$(echo "$MERGED_ATTRS" | jq -r 'length')
|
|
155
|
+
PRESERVED_COUNT=$((ATTR_COUNT - 1))
|
|
156
|
+
|
|
157
|
+
# PLAN_ONLY - read-only; report intent and stop before any write.
|
|
158
|
+
if [ "${PLAN_ONLY:-}" = "1" ]; then
|
|
159
|
+
emit_plan "Would deploy WorkSkillRouting '$RULE_DN' on $RELATED_ENTITY (isActive=$IS_ACTIVE) mapping $FIELD='$VALUE' -> Skill '$SKILL'."
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# Step 3 - Materialize the WorkSkillRouting in XSD (alphabetical) order
|
|
163
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
164
|
+
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
|
165
|
+
cp "$SCRIPT_DIR/../assets/sfdx-project.json" "$WORK/sfdx-project.json" 2>/dev/null \
|
|
166
|
+
|| echo '{"packageDirectories":[{"path":"force-app","default":true}],"sourceApiVersion":"66.0"}' > "$WORK/sfdx-project.json"
|
|
167
|
+
WSR_DIR="$WORK/force-app/main/default/workSkillRoutings"
|
|
168
|
+
mkdir -p "$WSR_DIR"
|
|
169
|
+
|
|
170
|
+
ATTR_XML=$(echo "$MERGED_ATTRS" | jq -r '
|
|
171
|
+
def xe: tostring | gsub("&";"&") | gsub("<";"<") | gsub(">";">");
|
|
172
|
+
.[] |
|
|
173
|
+
" <workSkillRoutingAttributes>",
|
|
174
|
+
" <field>\(.field|xe)</field>",
|
|
175
|
+
(if has("isAdditionalSkill") then " <isAdditionalSkill>\(.isAdditionalSkill)</isAdditionalSkill>" else empty end),
|
|
176
|
+
" <skill>\(.skill|xe)</skill>",
|
|
177
|
+
(if .skillLevel != null then " <skillLevel>\(.skillLevel)</skillLevel>" else empty end),
|
|
178
|
+
(if .skillPriority != null then " <skillPriority>\(.skillPriority)</skillPriority>" else empty end),
|
|
179
|
+
" <value>\(.value|xe)</value>",
|
|
180
|
+
" </workSkillRoutingAttributes>"
|
|
181
|
+
')
|
|
182
|
+
|
|
183
|
+
cat > "$WSR_DIR/${RULE_DN}.workSkillRouting-meta.xml" <<XML
|
|
184
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
185
|
+
<WorkSkillRouting xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
186
|
+
<isActive>${IS_ACTIVE}</isActive>
|
|
187
|
+
<masterLabel>${RULE_MASTER_LABEL}</masterLabel>
|
|
188
|
+
<relatedEntity>${RELATED_ENTITY}</relatedEntity>
|
|
189
|
+
${ATTR_XML}
|
|
190
|
+
</WorkSkillRouting>
|
|
191
|
+
XML
|
|
192
|
+
|
|
193
|
+
# Step 4 - Deploy (async + poll to terminal state)
|
|
194
|
+
START_JSON=$(cd "$WORK" && sf project deploy start --target-org "$ORG" \
|
|
195
|
+
--metadata "WorkSkillRouting:${RULE_DN}" --async --json 2>/dev/null || true)
|
|
196
|
+
DEPLOY_ID=$(echo "$START_JSON" | jq -r '.result.id // ""' 2>/dev/null)
|
|
197
|
+
DEPLOY_JSON="$START_JSON"
|
|
198
|
+
|
|
199
|
+
if [ -n "$DEPLOY_ID" ] && [ "$DEPLOY_ID" != "null" ]; then
|
|
200
|
+
POLL_STATUS=""
|
|
201
|
+
for _ in $(seq 1 60); do
|
|
202
|
+
REPORT_JSON=$(cd "$WORK" && sf project deploy report --job-id "$DEPLOY_ID" --target-org "$ORG" --json 2>/dev/null || true)
|
|
203
|
+
CUR_STATUS=$(echo "$REPORT_JSON" | jq -r '.result.status // ""' 2>/dev/null)
|
|
204
|
+
case "$CUR_STATUS" in
|
|
205
|
+
Succeeded|Failed|SucceededPartial|Canceled) DEPLOY_JSON="$REPORT_JSON"; POLL_STATUS="$CUR_STATUS"; break ;;
|
|
206
|
+
esac
|
|
207
|
+
sleep 15
|
|
208
|
+
done
|
|
209
|
+
[ -n "$POLL_STATUS" ] || emit_blocked "WorkSkillRouting deploy job $DEPLOY_ID did not reach a terminal state within the poll budget. Not reporting success."
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
DEPLOY_SUCCESS=$(echo "$DEPLOY_JSON" | jq -r '.result.success // false' 2>/dev/null)
|
|
213
|
+
DEPLOY_ID=$(echo "$DEPLOY_JSON" | jq -r '.result.id // ""' 2>/dev/null)
|
|
214
|
+
|
|
215
|
+
if [ "$DEPLOY_SUCCESS" != "true" ]; then
|
|
216
|
+
FAILURES=$(echo "$DEPLOY_JSON" | jq -r '.result.details.componentFailures // [] | map(.problem) | join("; ")' 2>/dev/null)
|
|
217
|
+
[ -n "$FAILURES" ] && [ "$FAILURES" != "null" ] || FAILURES=$(echo "$DEPLOY_JSON" | jq -r '.message // .result.errorMessage // "Unknown deploy error"' 2>/dev/null)
|
|
218
|
+
case "$FAILURES" in
|
|
219
|
+
*INVALID_TYPE*|*"Cannot find"*|*"not available"*|*"WorkSkillRouting"*)
|
|
220
|
+
emit_blocked "WorkSkillRouting is not available on this org (skills-based routing may be off, or the feature is not provisioned on this trial/CDO): $FAILURES" "service-omni-base-settings-configure" ;;
|
|
221
|
+
*"$SKILL"*)
|
|
222
|
+
emit_blocked "Deploy failed: Skill '$SKILL' could not be resolved by the rule. $FAILURES" "service-omni-skills-based-routing-configure" ;;
|
|
223
|
+
*) emit_blocked "WorkSkillRouting deploy failed: $FAILURES" ;;
|
|
224
|
+
esac
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
STATE_WSR=$(echo "$DEPLOY_JSON" | jq -r --arg fn "$RULE_DN" \
|
|
228
|
+
'.result.files[] | select(.fullName==$fn and .type=="WorkSkillRouting") | .state' 2>/dev/null | head -1)
|
|
229
|
+
STATE_WSR="${STATE_WSR:-unknown}"
|
|
230
|
+
case "$STATE_WSR" in
|
|
231
|
+
Created|Changed|Unchanged) ;;
|
|
232
|
+
*) emit_blocked "Deploy reported success overall but WorkSkillRouting '$RULE_DN' has an unrecognized/absent state \"$STATE_WSR\" (expected Created, Changed, or Unchanged). Not reporting success." ;;
|
|
233
|
+
esac
|
|
234
|
+
|
|
235
|
+
STATUS="reused"
|
|
236
|
+
[ "$STATE_WSR" = "Created" ] && STATUS="created"
|
|
237
|
+
[ "$STATE_WSR" = "Changed" ] && STATUS="updated"
|
|
238
|
+
|
|
239
|
+
jq -n \
|
|
240
|
+
--arg status "$STATUS" \
|
|
241
|
+
--arg rdn "$RULE_DN" --arg lbl "$RULE_MASTER_LABEL" --arg ent "$RELATED_ENTITY" --arg act "$IS_ACTIVE" --arg state "$STATE_WSR" \
|
|
242
|
+
--arg field "$FIELD" --arg skill "$SKILL" --arg value "$VALUE" \
|
|
243
|
+
--arg lvl "$SKILL_LEVEL" --arg pri "$SKILL_PRIORITY" --arg add "$IS_ADDITIONAL_SKILL" \
|
|
244
|
+
--arg deploy_id "$DEPLOY_ID" \
|
|
245
|
+
--argjson merged_from_existing "$MERGED_FROM_EXISTING" \
|
|
246
|
+
--argjson attribute_count "$ATTR_COUNT" \
|
|
247
|
+
--argjson preserved_count "$PRESERVED_COUNT" \
|
|
248
|
+
'{
|
|
249
|
+
skill: "service-omni-work-skill-routing-configure",
|
|
250
|
+
status: $status,
|
|
251
|
+
rule: {developer_name:$rdn, master_label:$lbl, related_entity:$ent, is_active:($act=="true"), state:$state},
|
|
252
|
+
merge: {merged_from_existing:$merged_from_existing, attribute_count:$attribute_count, preserved_count:$preserved_count},
|
|
253
|
+
attribute: {
|
|
254
|
+
field:$field, skill:$skill, value:$value,
|
|
255
|
+
skill_level:(if $lvl=="" then null else ($lvl|tonumber) end),
|
|
256
|
+
skill_priority:(if $pri=="" then null else ($pri|tonumber) end),
|
|
257
|
+
is_additional_skill:(if $add=="" then null else ($add=="true") end)
|
|
258
|
+
},
|
|
259
|
+
deploy_id: (if $deploy_id=="" then null else $deploy_id end),
|
|
260
|
+
manual_actions: [],
|
|
261
|
+
blocking_issue: null
|
|
262
|
+
}'
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Shared paths + fake-`sf` harness for the work-skill-routing contract suite.
|
|
2
|
+
|
|
3
|
+
Drives ``configure-and-report.sh`` via ``subprocess`` and asserts on exit codes + emitted JSON.
|
|
4
|
+
Org-free and network-free. ``jq`` comes from the real PATH.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
SKILLS_ROOT = Path(__file__).resolve().parents[3]
|
|
14
|
+
SKILL_DIR = "service-omni-work-skill-routing-configure"
|
|
15
|
+
SCRIPT = f"{SKILL_DIR}/scripts/configure-and-report.sh"
|
|
16
|
+
|
|
17
|
+
# PRODUCTION org - safe_to_write must refuse before any deploy.
|
|
18
|
+
_FAKE_SF_PROD = r"""#!/usr/bin/env bash
|
|
19
|
+
sub="${1:-} ${2:-}"; args="$*"
|
|
20
|
+
case "$sub" in
|
|
21
|
+
"org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
|
|
22
|
+
"data query")
|
|
23
|
+
if printf '%s' "$args" | grep -q "FROM Organization"; then
|
|
24
|
+
echo '{"status":0,"result":{"records":[{"IsSandbox":false,"TrialExpirationDate":null,"OrganizationType":"Enterprise Edition"}]}}'
|
|
25
|
+
else
|
|
26
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
27
|
+
fi
|
|
28
|
+
exit 0;;
|
|
29
|
+
"project deploy") echo '{"status":1,"result":{"success":false},"message":"FAKE_SF_DEPLOY_SHOULD_NOT_BE_REACHED"}'; exit 1;;
|
|
30
|
+
*) echo '{"status":0,"result":{}}'; exit 0;;
|
|
31
|
+
esac
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# SANDBOX org. Tunables:
|
|
35
|
+
# FAKE_STATE_WSR -> Created|Changed|Unchanged (default Created).
|
|
36
|
+
# FAKE_SKILL_PRESENT -> 1|0 (default 1); 0 blocks on the missing-Skill prereq.
|
|
37
|
+
# FAKE_DEPLOY_FAIL=1 -> deploy fails with a feature-unavailable (INVALID_TYPE) problem.
|
|
38
|
+
# FAKE_RULE_DN -> fullName echoed in the report (default Case).
|
|
39
|
+
_FAKE_SF_SANDBOX = r"""#!/usr/bin/env bash
|
|
40
|
+
sub="${1:-} ${2:-}"; args="$*"
|
|
41
|
+
STATE_WSR="${FAKE_STATE_WSR:-Created}"
|
|
42
|
+
RULE_DN="${FAKE_RULE_DN:-Case}"
|
|
43
|
+
if [ -n "${FAKE_SF_CALLLOG:-}" ]; then printf '%s\n' "$args" >> "$FAKE_SF_CALLLOG"; fi
|
|
44
|
+
case "$sub" in
|
|
45
|
+
"org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
|
|
46
|
+
"data query")
|
|
47
|
+
if printf '%s' "$args" | grep -q "FROM Organization"; then
|
|
48
|
+
echo '{"status":0,"result":{"records":[{"IsSandbox":true,"TrialExpirationDate":null,"OrganizationType":"Developer Edition"}]}}'
|
|
49
|
+
elif printf '%s' "$args" | grep -q "FROM WorkSkillRouting"; then
|
|
50
|
+
if [ "${FAKE_WSR_INCONCLUSIVE:-0}" = "1" ]; then
|
|
51
|
+
echo '{"status":1,"message":"Transient failure reading WorkSkillRouting metadata"}'
|
|
52
|
+
elif [ "${FAKE_WSR_EXISTING:-0}" = "1" ]; then
|
|
53
|
+
if [ "${FAKE_WSR_NULL_OPTIONALS:-0}" = "1" ]; then
|
|
54
|
+
echo '{"status":0,"result":{"totalSize":1,"records":[{"Metadata":{"masterLabel":"Existing Label","isActive":true,"relatedEntity":"Case","workSkillRoutingAttributes":[{"field":"Case.Priority","skill":"Priority_Support","value":"High","skillLevel":null,"skillPriority":null}]}}]}}'
|
|
55
|
+
else
|
|
56
|
+
echo '{"status":0,"result":{"totalSize":1,"records":[{"Metadata":{"masterLabel":"Existing Label","isActive":true,"relatedEntity":"Case","workSkillRoutingAttributes":[{"field":"Case.Priority","skill":"Priority_Support","value":"High"}]}}]}}'
|
|
57
|
+
fi
|
|
58
|
+
else
|
|
59
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
60
|
+
fi
|
|
61
|
+
elif printf '%s' "$args" | grep -q "FROM Skill"; then
|
|
62
|
+
if [ "${FAKE_SKILL_PRESENT:-1}" = "1" ]; then
|
|
63
|
+
echo '{"status":0,"result":{"records":[{"Id":"0C9xx0000000001"}],"totalSize":1}}'
|
|
64
|
+
else
|
|
65
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
66
|
+
fi
|
|
67
|
+
else
|
|
68
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
69
|
+
fi
|
|
70
|
+
exit 0;;
|
|
71
|
+
"project deploy")
|
|
72
|
+
if printf '%s' "$args" | grep -q "report"; then
|
|
73
|
+
if grep -R -q '>null<' force-app/main/default/workSkillRoutings 2>/dev/null; then
|
|
74
|
+
echo '{"status":0,"result":{"success":false,"id":"0AfXX0000000001","status":"Failed","details":{"componentFailures":[{"problem":"null is not a valid value for the type xsd:int"}]}}}'
|
|
75
|
+
elif [ "${FAKE_DEPLOY_FAIL:-0}" = "1" ]; then
|
|
76
|
+
echo '{"status":0,"result":{"success":false,"id":"0AfXX0000000001","status":"Failed","details":{"componentFailures":[{"problem":"INVALID_TYPE: WorkSkillRouting not available"}]}}}'
|
|
77
|
+
else
|
|
78
|
+
printf '{"status":0,"result":{"success":true,"id":"0AfXX0000000001","status":"Succeeded","files":[{"fullName":"%s","type":"WorkSkillRouting","state":"%s"}]}}' "$RULE_DN" "$STATE_WSR"
|
|
79
|
+
fi
|
|
80
|
+
else
|
|
81
|
+
echo '{"status":0,"result":{"id":"0AfXX0000000001","status":"Queued"}}'
|
|
82
|
+
fi
|
|
83
|
+
exit 0;;
|
|
84
|
+
*) echo '{"status":0,"result":{}}'; exit 0;;
|
|
85
|
+
esac
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def run(args, path_prefix=None, scrub_sf=False, extra_env=None):
|
|
90
|
+
env = dict(os.environ)
|
|
91
|
+
if scrub_sf:
|
|
92
|
+
kept = [d for d in env.get("PATH", "").split(os.pathsep)
|
|
93
|
+
if d and not os.path.exists(os.path.join(d, "sf"))]
|
|
94
|
+
env["PATH"] = os.pathsep.join(kept)
|
|
95
|
+
if path_prefix:
|
|
96
|
+
env["PATH"] = path_prefix + os.pathsep + env.get("PATH", "")
|
|
97
|
+
if extra_env:
|
|
98
|
+
env.update({k: str(v) for k, v in extra_env.items()})
|
|
99
|
+
proc = subprocess.run(
|
|
100
|
+
["bash", str(SKILLS_ROOT / SCRIPT), *args],
|
|
101
|
+
capture_output=True, text=True, env=env, cwd=str(SKILLS_ROOT),
|
|
102
|
+
)
|
|
103
|
+
return proc.returncode, (proc.stdout or "") + (proc.stderr or "")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class FakeSf:
|
|
107
|
+
def __init__(self, body=_FAKE_SF_PROD):
|
|
108
|
+
self._body = body
|
|
109
|
+
self._tmp = None
|
|
110
|
+
|
|
111
|
+
def __enter__(self):
|
|
112
|
+
self._tmp = tempfile.mkdtemp(prefix="wsr-fake-sf-")
|
|
113
|
+
sf = Path(self._tmp) / "sf"
|
|
114
|
+
sf.write_text(self._body)
|
|
115
|
+
sf.chmod(0o755)
|
|
116
|
+
return self._tmp
|
|
117
|
+
|
|
118
|
+
def __exit__(self, *exc):
|
|
119
|
+
import shutil
|
|
120
|
+
if self._tmp:
|
|
121
|
+
shutil.rmtree(self._tmp, ignore_errors=True)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Contract tests for service-omni-work-skill-routing-configure/scripts/configure-and-report.sh."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import unittest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
11
|
+
from _bootstrap import ( # noqa: E402
|
|
12
|
+
FakeSf,
|
|
13
|
+
_FAKE_SF_PROD,
|
|
14
|
+
_FAKE_SF_SANDBOX,
|
|
15
|
+
run,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _last_json(out: str) -> dict:
|
|
20
|
+
dec = json.JSONDecoder()
|
|
21
|
+
obj, i, n = None, 0, len(out)
|
|
22
|
+
while i < n:
|
|
23
|
+
if out[i] in "{[":
|
|
24
|
+
try:
|
|
25
|
+
cand, end = dec.raw_decode(out, i)
|
|
26
|
+
if isinstance(cand, dict):
|
|
27
|
+
obj = cand
|
|
28
|
+
i = end
|
|
29
|
+
continue
|
|
30
|
+
except json.JSONDecodeError:
|
|
31
|
+
pass
|
|
32
|
+
i += 1
|
|
33
|
+
assert obj is not None, f"no JSON object found in output:\n{out}"
|
|
34
|
+
return obj
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class InputContractTests(unittest.TestCase):
|
|
38
|
+
def test_missing_args_rejected(self):
|
|
39
|
+
rc, out = run(["myorg", "Case", "Origin"], scrub_sf=True)
|
|
40
|
+
self.assertEqual(rc, 1, out)
|
|
41
|
+
self.assertIn("Usage", out)
|
|
42
|
+
|
|
43
|
+
def test_injection_shaped_skill_rejected(self):
|
|
44
|
+
rc, out = run(["myorg", "Case", "Origin", "Bad; DROP", "Phone"], scrub_sf=True)
|
|
45
|
+
self.assertEqual(rc, 1, out)
|
|
46
|
+
self.assertIn("Invalid skill", out)
|
|
47
|
+
|
|
48
|
+
def test_xml_injection_value_rejected(self):
|
|
49
|
+
rc, out = run(["myorg", "Case", "Origin", "Voice", "<x>&bad"], scrub_sf=True)
|
|
50
|
+
self.assertEqual(rc, 1, out)
|
|
51
|
+
self.assertIn("Invalid value", out)
|
|
52
|
+
|
|
53
|
+
def test_bad_related_entity_rejected(self):
|
|
54
|
+
rc, out = run(["myorg", "Case Origin", "Origin", "Voice", "Phone"], scrub_sf=True)
|
|
55
|
+
self.assertEqual(rc, 1, out)
|
|
56
|
+
self.assertIn("Invalid related_entity", out)
|
|
57
|
+
|
|
58
|
+
def test_out_of_range_skill_level_rejected(self):
|
|
59
|
+
rc, out = run(["myorg", "Case", "Origin", "Voice", "Phone"], scrub_sf=True,
|
|
60
|
+
extra_env={"SKILL_LEVEL": "11"})
|
|
61
|
+
self.assertEqual(rc, 1, out)
|
|
62
|
+
self.assertIn("Invalid SKILL_LEVEL", out)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ProductionGuardTests(unittest.TestCase):
|
|
66
|
+
def test_refuses_production_org(self):
|
|
67
|
+
with FakeSf(_FAKE_SF_PROD) as p:
|
|
68
|
+
rc, out = run(["prodorg", "Case", "Origin", "Voice", "Phone"], path_prefix=p)
|
|
69
|
+
self.assertEqual(rc, 1, out)
|
|
70
|
+
v = _last_json(out)
|
|
71
|
+
self.assertEqual(v["status"], "blocked", out)
|
|
72
|
+
self.assertIn("production", v["blocking_issue"].lower())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ConfigureTests(unittest.TestCase):
|
|
76
|
+
def test_create_deploys_rule(self):
|
|
77
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
78
|
+
rc, out = run(["sandbox", "Case", "Case.Origin", "Voice", "Phone"], path_prefix=p,
|
|
79
|
+
extra_env={"FAKE_STATE_WSR": "Created"})
|
|
80
|
+
self.assertEqual(rc, 0, out)
|
|
81
|
+
v = _last_json(out)
|
|
82
|
+
self.assertEqual(v["status"], "created", out)
|
|
83
|
+
self.assertEqual(v["rule"]["related_entity"], "Case", out)
|
|
84
|
+
self.assertEqual(v["attribute"]["skill"], "Voice", out)
|
|
85
|
+
self.assertEqual(v["attribute"]["value"], "Phone", out)
|
|
86
|
+
|
|
87
|
+
def test_bare_standard_field_is_entity_qualified(self):
|
|
88
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
89
|
+
rc, out = run(["sandbox", "Case", "Priority", "Voice", "High"], path_prefix=p,
|
|
90
|
+
extra_env={"FAKE_STATE_WSR": "Created"})
|
|
91
|
+
self.assertEqual(rc, 0, out)
|
|
92
|
+
self.assertEqual(_last_json(out)["attribute"]["field"], "Case.Priority", out)
|
|
93
|
+
|
|
94
|
+
def test_reused_when_unchanged(self):
|
|
95
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
96
|
+
rc, out = run(["sandbox", "Case", "Origin", "Voice", "Phone"], path_prefix=p,
|
|
97
|
+
extra_env={"FAKE_STATE_WSR": "Unchanged"})
|
|
98
|
+
self.assertEqual(rc, 0, out)
|
|
99
|
+
v = _last_json(out)
|
|
100
|
+
self.assertEqual(v["status"], "reused", out)
|
|
101
|
+
|
|
102
|
+
def test_blocks_when_referenced_skill_missing(self):
|
|
103
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
104
|
+
rc, out = run(["sandbox", "Case", "Origin", "Voice", "Phone"], path_prefix=p,
|
|
105
|
+
extra_env={"FAKE_SKILL_PRESENT": "0"})
|
|
106
|
+
self.assertEqual(rc, 1, out)
|
|
107
|
+
v = _last_json(out)
|
|
108
|
+
self.assertEqual(v["status"], "blocked", out)
|
|
109
|
+
self.assertTrue(any(a.get("target_skill") == "service-omni-skills-based-routing-configure"
|
|
110
|
+
for a in v["manual_actions"]), out)
|
|
111
|
+
|
|
112
|
+
def test_graceful_degrade_when_feature_unavailable(self):
|
|
113
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
114
|
+
rc, out = run(["sandbox", "Case", "Origin", "Voice", "Phone"], path_prefix=p,
|
|
115
|
+
extra_env={"FAKE_DEPLOY_FAIL": "1"})
|
|
116
|
+
self.assertEqual(rc, 1, out)
|
|
117
|
+
v = _last_json(out)
|
|
118
|
+
self.assertEqual(v["status"], "blocked", out)
|
|
119
|
+
self.assertIn("not available", v["blocking_issue"].lower())
|
|
120
|
+
|
|
121
|
+
def test_plan_only_reports_without_writing(self):
|
|
122
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
123
|
+
calllog = str(Path(p) / "calls.log")
|
|
124
|
+
rc, out = run(["sandbox", "Case", "Origin", "Voice", "Phone"], path_prefix=p,
|
|
125
|
+
extra_env={"PLAN_ONLY": "1", "FAKE_SF_CALLLOG": calllog})
|
|
126
|
+
log = Path(calllog).read_text() if Path(calllog).exists() else ""
|
|
127
|
+
self.assertEqual(rc, 0, out)
|
|
128
|
+
v = _last_json(out)
|
|
129
|
+
self.assertTrue(v.get("plan_mode"), out)
|
|
130
|
+
self.assertEqual(v["status"], "action_needed", out)
|
|
131
|
+
self.assertNotIn("project deploy", log, "plan mode must not deploy")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class MergeSafeTests(unittest.TestCase):
|
|
135
|
+
def test_preserves_existing_mapping(self):
|
|
136
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
137
|
+
rc, out = run(["sandbox", "Case", "Origin", "Voice_Support", "Phone"], path_prefix=p,
|
|
138
|
+
extra_env={"FAKE_WSR_EXISTING": "1", "FAKE_STATE_WSR": "Changed"})
|
|
139
|
+
self.assertEqual(rc, 0, out)
|
|
140
|
+
merge = _last_json(out)["merge"]
|
|
141
|
+
self.assertEqual(merge["attribute_count"], 2, out)
|
|
142
|
+
self.assertEqual(merge["preserved_count"], 1, out)
|
|
143
|
+
|
|
144
|
+
def test_omits_null_optional_values_from_preserved_mapping(self):
|
|
145
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
146
|
+
rc, out = run(["sandbox", "Case", "Origin", "Voice_Support", "Phone"], path_prefix=p,
|
|
147
|
+
extra_env={"FAKE_WSR_EXISTING": "1", "FAKE_WSR_NULL_OPTIONALS": "1",
|
|
148
|
+
"FAKE_STATE_WSR": "Changed"})
|
|
149
|
+
self.assertEqual(rc, 0, out)
|
|
150
|
+
self.assertEqual(_last_json(out)["merge"]["preserved_count"], 1, out)
|
|
151
|
+
|
|
152
|
+
def test_inconclusive_read_blocks_destructive_replace(self):
|
|
153
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
154
|
+
rc, out = run(["sandbox", "Case", "Origin", "Voice_Support", "Phone"], path_prefix=p,
|
|
155
|
+
extra_env={"FAKE_WSR_INCONCLUSIVE": "1"})
|
|
156
|
+
self.assertEqual(rc, 1, out)
|
|
157
|
+
self.assertIn("WSR_REPLACE", _last_json(out)["blocking_issue"])
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
unittest.main(verbosity=2)
|