@salesforce/afv-skills 1.14.0 → 1.16.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/activating-datacloud/SKILL.md +0 -1
- package/skills/analyzing-omnistudio-dependencies/SKILL.md +0 -1
- package/skills/applying-slds/SKILL.md +322 -0
- package/skills/applying-slds/checklists.md +83 -0
- package/skills/applying-slds/examples.md +283 -0
- package/skills/applying-slds/guidance/README.md +83 -0
- package/skills/applying-slds/guidance/blueprints-index.md +213 -0
- package/skills/applying-slds/guidance/icons-guidance.md +186 -0
- package/skills/applying-slds/guidance/overviews/borders.md +236 -0
- package/skills/applying-slds/guidance/overviews/color.md +266 -0
- package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
- package/skills/applying-slds/guidance/overviews/icons.md +240 -0
- package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
- package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
- package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
- package/skills/applying-slds/guidance/overviews/typography.md +323 -0
- package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
- package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
- package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
- package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
- package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
- package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
- package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
- package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
- package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
- package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
- package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
- package/skills/applying-slds/guidance/utilities/borders.md +131 -0
- package/skills/applying-slds/guidance/utilities/box.md +125 -0
- package/skills/applying-slds/guidance/utilities/color.md +165 -0
- package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
- package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
- package/skills/applying-slds/guidance/utilities/floats.md +117 -0
- package/skills/applying-slds/guidance/utilities/grid.md +264 -0
- package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
- package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
- package/skills/applying-slds/guidance/utilities/index.md +205 -0
- package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
- package/skills/applying-slds/guidance/utilities/layout.md +109 -0
- package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
- package/skills/applying-slds/guidance/utilities/margin.md +155 -0
- package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
- package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
- package/skills/applying-slds/guidance/utilities/padding.md +155 -0
- package/skills/applying-slds/guidance/utilities/position.md +177 -0
- package/skills/applying-slds/guidance/utilities/print.md +114 -0
- package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
- package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
- package/skills/applying-slds/guidance/utilities/themes.md +121 -0
- package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
- package/skills/applying-slds/guidance/utilities/typography.md +166 -0
- package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
- package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
- package/skills/applying-slds/metadata/README.md +84 -0
- package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
- package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
- package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
- package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
- package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
- package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
- package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
- package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
- package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
- package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
- package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
- package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
- package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
- package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
- package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
- package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
- package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
- package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
- package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
- package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
- package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
- package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
- package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
- package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
- package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
- package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
- package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
- package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
- package/skills/applying-slds/metadata/hooks-index.json +6272 -0
- package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
- package/skills/applying-slds/metadata/utilities-index.json +21912 -0
- package/skills/applying-slds/references/component-selection.md +112 -0
- package/skills/applying-slds/references/icons-decision-guide.md +124 -0
- package/skills/applying-slds/references/styling-decision-guide.md +228 -0
- package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
- package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
- package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
- package/skills/applying-slds/scripts/search-icons.cjs +174 -0
- package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
- package/skills/building-mobile-apps/SKILL.md +0 -1
- package/skills/building-omnistudio-callable-apex/SKILL.md +0 -1
- package/skills/building-omnistudio-datamapper/SKILL.md +0 -1
- package/skills/building-omnistudio-flexcard/SKILL.md +0 -1
- package/skills/building-omnistudio-integration-procedure/SKILL.md +0 -1
- package/skills/building-omnistudio-omniscript/SKILL.md +0 -1
- package/skills/building-sf-integrations/SKILL.md +0 -1
- package/skills/configuring-connected-apps/SKILL.md +0 -1
- package/skills/connecting-datacloud/SKILL.md +0 -1
- package/skills/creating-b2b-commerce-store/SKILL.md +0 -1
- package/skills/debugging-apex-logs/SKILL.md +0 -1
- package/skills/deploying-metadata/SKILL.md +0 -1
- package/skills/deploying-omnistudio-datapacks/SKILL.md +0 -1
- package/skills/developing-agentforce/SKILL.md +0 -1
- package/skills/fetching-salesforce-docs/SKILL.md +0 -1
- package/skills/generating-custom-lightning-type/SKILL.md +17 -39
- package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
- package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
- package/skills/generating-lwc-components/SKILL.md +0 -1
- package/skills/generating-mermaid-diagrams/SKILL.md +0 -1
- package/skills/generating-visual-diagrams/SKILL.md +0 -1
- package/skills/handling-sf-data/SKILL.md +0 -1
- package/skills/harmonizing-datacloud/SKILL.md +0 -1
- package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +0 -1
- package/skills/investigating-agentforce-architecture/README.md +156 -0
- package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
- package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
- package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
- package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
- package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
- package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
- package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
- package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
- package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
- package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
- package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
- package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
- package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
- package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
- package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
- package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
- package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
- package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
- package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
- package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
- package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
- package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
- package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
- package/skills/investigating-agentforce-d360/README.md +123 -0
- package/skills/investigating-agentforce-d360/SKILL.md +163 -0
- package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
- package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
- package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
- package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
- package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
- package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
- package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
- package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
- package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
- package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
- package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
- package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
- package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
- package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
- package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
- package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
- package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
- package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
- package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
- package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
- package/skills/managing-managed-event-subscription/SKILL.md +152 -0
- package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
- package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
- package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
- package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
- package/skills/modeling-omnistudio-epc-catalog/SKILL.md +0 -1
- package/skills/observing-agentforce/SKILL.md +0 -1
- package/skills/orchestrating-datacloud/SKILL.md +0 -1
- package/skills/preparing-datacloud/SKILL.md +0 -1
- package/skills/querying-soql/SKILL.md +0 -1
- package/skills/retrieving-datacloud/SKILL.md +0 -1
- package/skills/running-apex-tests/SKILL.md +0 -1
- package/skills/running-code-analyzer/SKILL.md +0 -1
- package/skills/segmenting-datacloud/SKILL.md +0 -1
- package/skills/testing-agentforce/SKILL.md +0 -1
- package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
- package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
- package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
- package/skills/validating-slds/SKILL.md +262 -0
- package/skills/validating-slds/references/quality-checks.md +308 -0
- package/skills/validating-slds/references/report-format.md +302 -0
- package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""End-to-end integration tests for ``assemble_dc.assemble``.
|
|
2
|
+
|
|
3
|
+
Drives the full assembler against the synthetic session fixture under
|
|
4
|
+
``tests/fixtures/synthetic_session.py``. Each test materializes the
|
|
5
|
+
fixture into a tmp DATA_ROOT, calls ``assemble_dc.assemble(SID)``, and
|
|
6
|
+
asserts on the resulting tree.
|
|
7
|
+
|
|
8
|
+
These tests cover the orchestration paths that the helper-only
|
|
9
|
+
``test_assemble_dc_helpers.py`` doesn't touch:
|
|
10
|
+
|
|
11
|
+
- declared Step → Generation → Response → Request chain
|
|
12
|
+
- timestamp-window fallback for an un-declared gateway request
|
|
13
|
+
- trace_id extraction from HTML-escaped AttributeText
|
|
14
|
+
- session-identity resolution from AGENT participant
|
|
15
|
+
- malformed dc.<name>.json → parse_warnings recorded, fixture survives
|
|
16
|
+
- minimal-tree path when sessions[] is empty (session_not_found)
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import unittest
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from tempfile import TemporaryDirectory
|
|
24
|
+
from unittest import mock
|
|
25
|
+
|
|
26
|
+
from . import _bootstrap # noqa: F401 — sys.path setup
|
|
27
|
+
|
|
28
|
+
import assemble_dc # type: ignore
|
|
29
|
+
from config import paths # type: ignore
|
|
30
|
+
from .fixtures.synthetic_session import ( # type: ignore
|
|
31
|
+
IDS, make_rows, write_to_disk, session_dir_for,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# -----------------------------------------------------------------------------
|
|
36
|
+
# Helpers
|
|
37
|
+
# -----------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _AssembleHarness:
|
|
41
|
+
"""Context-manager that builds a tmp DATA_ROOT, materializes the
|
|
42
|
+
fixture, patches ``paths.DATA_ROOT``, and yields ``(tree, sdir)``."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, mutate_files=None):
|
|
45
|
+
self.mutate_files = mutate_files
|
|
46
|
+
|
|
47
|
+
def __enter__(self):
|
|
48
|
+
self._tmp = TemporaryDirectory()
|
|
49
|
+
self._tmpdir = Path(self._tmp.name)
|
|
50
|
+
sdir = write_to_disk(self._tmpdir)
|
|
51
|
+
if self.mutate_files:
|
|
52
|
+
self.mutate_files(sdir)
|
|
53
|
+
self._patch = mock.patch.object(paths, "DATA_ROOT", self._tmpdir)
|
|
54
|
+
self._patch.start()
|
|
55
|
+
self.tree, self.sdir = assemble_dc.assemble(IDS.SID)
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def __exit__(self, *exc):
|
|
59
|
+
self._patch.stop()
|
|
60
|
+
self._tmp.cleanup()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _turn(tree: dict) -> dict:
|
|
64
|
+
return next(
|
|
65
|
+
iv for iv in tree["session"]["interactions"]
|
|
66
|
+
if iv.get("type") == "TURN"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# -----------------------------------------------------------------------------
|
|
71
|
+
# Top-level shape
|
|
72
|
+
# -----------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AssembleTreeShapeTests(unittest.TestCase):
|
|
76
|
+
|
|
77
|
+
def test_tree_top_level_keys(self):
|
|
78
|
+
with _AssembleHarness() as h:
|
|
79
|
+
self.assertIn("identity", h.tree)
|
|
80
|
+
self.assertIn("session", h.tree)
|
|
81
|
+
self.assertIn("catalog", h.tree)
|
|
82
|
+
|
|
83
|
+
def test_session_dir_path_is_under_data_root(self):
|
|
84
|
+
with _AssembleHarness() as h:
|
|
85
|
+
# On macOS the tmp path resolves through /private/var → /var
|
|
86
|
+
# (or vice versa). Compare via .resolve() so the symlink
|
|
87
|
+
# discrepancy doesn't fail the assertion.
|
|
88
|
+
expected = session_dir_for(h._tmpdir).resolve()
|
|
89
|
+
self.assertEqual(h.sdir.resolve(), expected)
|
|
90
|
+
|
|
91
|
+
def test_three_interactions_in_chronological_order(self):
|
|
92
|
+
with _AssembleHarness() as h:
|
|
93
|
+
ivs = h.tree["session"]["interactions"]
|
|
94
|
+
types = [iv["type"] for iv in ivs]
|
|
95
|
+
self.assertEqual(types, ["SESSION_START", "TURN", "SESSION_END"])
|
|
96
|
+
|
|
97
|
+
def test_turn_has_three_steps_in_order(self):
|
|
98
|
+
with _AssembleHarness() as h:
|
|
99
|
+
turn = _turn(h.tree)
|
|
100
|
+
names = [s.get("name") for s in turn["steps"]]
|
|
101
|
+
# Step names from the fixture's ssot__Name__c column
|
|
102
|
+
self.assertIn("TopicSelection", names)
|
|
103
|
+
self.assertIn("PrimaryAction", names)
|
|
104
|
+
self.assertIn("FinalGuard", names)
|
|
105
|
+
|
|
106
|
+
def test_turn_has_two_messages_user_and_agent(self):
|
|
107
|
+
with _AssembleHarness() as h:
|
|
108
|
+
turn = _turn(h.tree)
|
|
109
|
+
self.assertEqual(len(turn["messages"]), 2)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# -----------------------------------------------------------------------------
|
|
113
|
+
# Identity resolution
|
|
114
|
+
# -----------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class AssembleIdentityTests(unittest.TestCase):
|
|
118
|
+
|
|
119
|
+
def test_identity_extracted_from_agent_participant(self):
|
|
120
|
+
with _AssembleHarness() as h:
|
|
121
|
+
ident = h.tree["identity"]
|
|
122
|
+
self.assertEqual(ident["org_id_15"], IDS.ORG_ID_15)
|
|
123
|
+
self.assertEqual(ident["agent_api_name"], IDS.AGENT_API)
|
|
124
|
+
self.assertEqual(ident["agent_version"], IDS.AGENT_VERSION)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# -----------------------------------------------------------------------------
|
|
128
|
+
# Trace_id extraction (HTML-escaped fallback)
|
|
129
|
+
# -----------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AssembleTraceIdTests(unittest.TestCase):
|
|
133
|
+
|
|
134
|
+
def test_turn_trace_id_pulled_from_html_escaped_attribute_text(self):
|
|
135
|
+
with _AssembleHarness() as h:
|
|
136
|
+
turn = _turn(h.tree)
|
|
137
|
+
# The fixture intentionally puts trace_id ONLY in AttributeText
|
|
138
|
+
# (HTML-escaped JSON); the primary TelemetryTraceId__c column is
|
|
139
|
+
# empty. This exercises the fallback path in assemble_dc.
|
|
140
|
+
self.assertEqual(turn["trace_id"], IDS.TRACE_ID)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# -----------------------------------------------------------------------------
|
|
144
|
+
# Gateway binding (declared chain + timestamp-window fallback)
|
|
145
|
+
# -----------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class AssembleGatewayBindingTests(unittest.TestCase):
|
|
149
|
+
|
|
150
|
+
def test_action_step_has_declared_gateway_request(self):
|
|
151
|
+
with _AssembleHarness() as h:
|
|
152
|
+
turn = _turn(h.tree)
|
|
153
|
+
action_step = next(
|
|
154
|
+
s for s in turn["steps"] if s.get("name") == "PrimaryAction"
|
|
155
|
+
)
|
|
156
|
+
gw = action_step["gateway_request"]
|
|
157
|
+
self.assertIsNotNone(gw)
|
|
158
|
+
self.assertEqual(gw["binding_method"], "declared")
|
|
159
|
+
self.assertEqual(gw["gateway_request_id"], IDS.GW_REQ_DECLARED)
|
|
160
|
+
self.assertEqual(gw["model"], "gpt-4o")
|
|
161
|
+
self.assertEqual(gw["total_tokens"], 1250)
|
|
162
|
+
|
|
163
|
+
def test_topic_and_guardrail_steps_have_no_gateway_binding(self):
|
|
164
|
+
with _AssembleHarness() as h:
|
|
165
|
+
turn = _turn(h.tree)
|
|
166
|
+
for s in turn["steps"]:
|
|
167
|
+
if s.get("name") in ("TopicSelection", "FinalGuard"):
|
|
168
|
+
self.assertIsNone(s.get("gateway_request"))
|
|
169
|
+
|
|
170
|
+
def test_timestamp_window_pass_picks_up_undeclared_gateway_request(self):
|
|
171
|
+
with _AssembleHarness() as h:
|
|
172
|
+
turn = _turn(h.tree)
|
|
173
|
+
ts_calls = turn.get("timestamp_bound_gateway_calls", [])
|
|
174
|
+
# Exactly one gateway request lives in the TURN window without a
|
|
175
|
+
# declared binding (GW_REQ_WINDOW). The declared one is consumed
|
|
176
|
+
# by step-action and is excluded from the ts-window pass.
|
|
177
|
+
self.assertEqual(len(ts_calls), 1)
|
|
178
|
+
self.assertEqual(
|
|
179
|
+
ts_calls[0]["gateway_request_id"], IDS.GW_REQ_WINDOW,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def test_declared_response_carries_finish_reason(self):
|
|
183
|
+
with _AssembleHarness() as h:
|
|
184
|
+
turn = _turn(h.tree)
|
|
185
|
+
action_step = next(
|
|
186
|
+
s for s in turn["steps"] if s.get("name") == "PrimaryAction"
|
|
187
|
+
)
|
|
188
|
+
# parameters__c is HTML-escaped JSON in the fixture; the
|
|
189
|
+
# renderer is responsible for decoding, but the raw string
|
|
190
|
+
# must be preserved unchanged in the assembled tree.
|
|
191
|
+
params = action_step["gateway_request"]["response"]["parameters__c"]
|
|
192
|
+
self.assertIn("finish_reason", params)
|
|
193
|
+
self.assertIn(""", params)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# -----------------------------------------------------------------------------
|
|
197
|
+
# Malformed-input tolerance (parse_warnings)
|
|
198
|
+
# -----------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class AssembleParseWarningsTests(unittest.TestCase):
|
|
202
|
+
|
|
203
|
+
def test_malformed_dc_file_recorded_in_parse_warnings(self):
|
|
204
|
+
def corrupt_messages(sdir: Path) -> None:
|
|
205
|
+
(sdir / "dc.messages.json").write_text("<<<not json>>>")
|
|
206
|
+
|
|
207
|
+
with _AssembleHarness(mutate_files=corrupt_messages) as h:
|
|
208
|
+
# parse_warnings lives on session.counts (cf. _build_counts).
|
|
209
|
+
warnings = h.tree["session"]["counts"].get("parse_warnings") or []
|
|
210
|
+
self.assertIn("messages", warnings)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# -----------------------------------------------------------------------------
|
|
214
|
+
# session_not_found short-circuit
|
|
215
|
+
# -----------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class AssembleSessionNotFoundTests(unittest.TestCase):
|
|
219
|
+
|
|
220
|
+
def test_empty_sessions_returns_minimal_tree(self):
|
|
221
|
+
def empty_sessions(sdir: Path) -> None:
|
|
222
|
+
(sdir / "dc.sessions.json").write_text("[]")
|
|
223
|
+
|
|
224
|
+
with _AssembleHarness(mutate_files=empty_sessions) as h:
|
|
225
|
+
tree = h.tree
|
|
226
|
+
# Minimal-tree shape signaled by session_shape == 'session_not_found'
|
|
227
|
+
# in the manifest section, plus an empty interactions list.
|
|
228
|
+
self.assertEqual(tree["session"].get("interactions", []), [])
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# -----------------------------------------------------------------------------
|
|
232
|
+
# Public API path: tree dict is JSON-serializable
|
|
233
|
+
# -----------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class AssembleSerializableTests(unittest.TestCase):
|
|
237
|
+
|
|
238
|
+
def test_tree_round_trips_through_json(self):
|
|
239
|
+
with _AssembleHarness() as h:
|
|
240
|
+
blob = json.dumps(h.tree, default=str)
|
|
241
|
+
back = json.loads(blob)
|
|
242
|
+
# Sanity: re-loaded keys match originals
|
|
243
|
+
self.assertEqual(set(back.keys()), set(h.tree.keys()))
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if __name__ == "__main__":
|
|
247
|
+
unittest.main()
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"""Tests for ``dc`` (Data Cloud transport) + ``resolve_session``
|
|
2
|
+
(messaging-id → UUID resolver).
|
|
3
|
+
|
|
4
|
+
Both modules sit at the bottom of the dependency tree and have small,
|
|
5
|
+
well-defined public APIs that are mostly mockable at the subprocess /
|
|
6
|
+
urllib boundary.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import io
|
|
11
|
+
import json
|
|
12
|
+
import subprocess
|
|
13
|
+
import unittest
|
|
14
|
+
import urllib.error
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from tempfile import TemporaryDirectory
|
|
17
|
+
from types import SimpleNamespace
|
|
18
|
+
from unittest import mock
|
|
19
|
+
|
|
20
|
+
from . import _bootstrap # noqa: F401 — sys.path setup
|
|
21
|
+
|
|
22
|
+
import dc # type: ignore
|
|
23
|
+
import resolve_session # type: ignore
|
|
24
|
+
from config import paths # type: ignore
|
|
25
|
+
from .fixtures.synthetic_session import IDS, write_to_disk # type: ignore
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# -----------------------------------------------------------------------------
|
|
29
|
+
# dc.load_sql / dc.parse
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LoadSqlTests(unittest.TestCase):
|
|
34
|
+
|
|
35
|
+
def test_substitutes_placeholders_and_strips(self):
|
|
36
|
+
# discover_sessions has SELECT_LIST/JOINS/WHERE_CLAUSE/LIMIT placeholders.
|
|
37
|
+
sql = dc.load_sql(
|
|
38
|
+
"discover_sessions",
|
|
39
|
+
SELECT_LIST="*",
|
|
40
|
+
JOINS="",
|
|
41
|
+
WHERE_CLAUSE="1=1",
|
|
42
|
+
LIMIT="10",
|
|
43
|
+
)
|
|
44
|
+
self.assertIn("SELECT *", sql)
|
|
45
|
+
self.assertIn("LIMIT 10", sql)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ParseTests(unittest.TestCase):
|
|
49
|
+
|
|
50
|
+
def test_returns_data_array(self):
|
|
51
|
+
self.assertEqual(dc.parse({"data": [{"a": 1}]}), [{"a": 1}])
|
|
52
|
+
|
|
53
|
+
def test_returns_empty_list_when_data_missing(self):
|
|
54
|
+
self.assertEqual(dc.parse({}), [])
|
|
55
|
+
|
|
56
|
+
def test_returns_empty_list_for_falsy_input(self):
|
|
57
|
+
self.assertEqual(dc.parse(None), [])
|
|
58
|
+
self.assertEqual(dc.parse({}), [])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# -----------------------------------------------------------------------------
|
|
62
|
+
# dc.resolve_org — sf CLI shell-out
|
|
63
|
+
# -----------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ResolveOrgTests(unittest.TestCase):
|
|
67
|
+
"""Tests for the two-path access-token retrieval per forcedotcom/cli#3560.
|
|
68
|
+
|
|
69
|
+
Path 1 (primary): ``sf org auth show-access-token --json --no-prompt``
|
|
70
|
+
Path 2 (fallback): ``sf org display`` + ``SF_TEMP_SHOW_SECRETS=true``
|
|
71
|
+
|
|
72
|
+
Most tests mock ``subprocess.run`` with a side-effect callable that
|
|
73
|
+
returns a different stub depending on which sf subcommand was invoked,
|
|
74
|
+
so we can exercise primary success, primary failure → fallback, and
|
|
75
|
+
full-failure paths without spawning real processes.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
REDACTED_TOKEN = "[REDACTED] Use 'sf org auth show-access-token' to view"
|
|
79
|
+
|
|
80
|
+
def _cp(self, stdout: str, *, returncode: int = 0, stderr: str = ""):
|
|
81
|
+
return SimpleNamespace(
|
|
82
|
+
returncode=returncode, stdout=stdout, stderr=stderr,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _display_payload(self, *, access_token: str = "TOKEN_FROM_DISPLAY") -> str:
|
|
86
|
+
return json.dumps({"result": {
|
|
87
|
+
"instanceUrl": "https://example.salesforce.com",
|
|
88
|
+
"accessToken": access_token,
|
|
89
|
+
}})
|
|
90
|
+
|
|
91
|
+
def _show_token_payload(self, *, access_token: str = "TOKEN_FROM_SHOW") -> str:
|
|
92
|
+
return json.dumps({"result": {"accessToken": access_token}})
|
|
93
|
+
|
|
94
|
+
def _route(self, primary_ok=True, primary_unknown=False,
|
|
95
|
+
primary_redacted=False, display_redacted=False):
|
|
96
|
+
"""Build a fake_run that routes by argv shape.
|
|
97
|
+
|
|
98
|
+
- primary_ok=True → show-access-token returns clean token
|
|
99
|
+
- primary_unknown → show-access-token raises CalledProcessError
|
|
100
|
+
(older sf CLI without the subcommand)
|
|
101
|
+
- primary_redacted → show-access-token returns the placeholder
|
|
102
|
+
- display_redacted → org_display token field is the placeholder
|
|
103
|
+
(so fallback also fails — test full-failure)
|
|
104
|
+
"""
|
|
105
|
+
def fake_run(argv, **kwargs):
|
|
106
|
+
# display call
|
|
107
|
+
if "display" in argv:
|
|
108
|
+
return self._cp(
|
|
109
|
+
self._display_payload(
|
|
110
|
+
access_token=(self.REDACTED_TOKEN if display_redacted
|
|
111
|
+
else "TOKEN_FROM_DISPLAY"),
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
# show-access-token call
|
|
115
|
+
if "show-access-token" in argv:
|
|
116
|
+
if primary_unknown:
|
|
117
|
+
raise subprocess.CalledProcessError(
|
|
118
|
+
returncode=1, cmd=argv,
|
|
119
|
+
stderr="show-access-token is not a sf command",
|
|
120
|
+
)
|
|
121
|
+
token = (self.REDACTED_TOKEN if primary_redacted
|
|
122
|
+
else "TOKEN_FROM_SHOW")
|
|
123
|
+
return self._cp(self._show_token_payload(access_token=token))
|
|
124
|
+
raise AssertionError(f"unexpected argv: {argv}")
|
|
125
|
+
return fake_run
|
|
126
|
+
|
|
127
|
+
def test_primary_path_returns_show_token(self):
|
|
128
|
+
"""Happy path — dedicated command returns a clean token."""
|
|
129
|
+
with mock.patch.object(dc.subprocess, "run", side_effect=self._route()):
|
|
130
|
+
url, token = dc.resolve_org("my-org")
|
|
131
|
+
self.assertEqual(url, "https://example.salesforce.com")
|
|
132
|
+
self.assertEqual(token, "TOKEN_FROM_SHOW")
|
|
133
|
+
|
|
134
|
+
def test_primary_unknown_falls_back_to_display_token(self):
|
|
135
|
+
"""Older sf CLI: show-access-token unknown → use display payload."""
|
|
136
|
+
with mock.patch.object(
|
|
137
|
+
dc.subprocess, "run",
|
|
138
|
+
side_effect=self._route(primary_unknown=True),
|
|
139
|
+
):
|
|
140
|
+
url, token = dc.resolve_org("my-org")
|
|
141
|
+
self.assertEqual(url, "https://example.salesforce.com")
|
|
142
|
+
self.assertEqual(token, "TOKEN_FROM_DISPLAY")
|
|
143
|
+
|
|
144
|
+
def test_primary_redacted_falls_back_to_display_token(self):
|
|
145
|
+
"""Edge case: dedicated command returns the placeholder string —
|
|
146
|
+
treat as failure and try the display fallback."""
|
|
147
|
+
with mock.patch.object(
|
|
148
|
+
dc.subprocess, "run",
|
|
149
|
+
side_effect=self._route(primary_redacted=True),
|
|
150
|
+
):
|
|
151
|
+
url, token = dc.resolve_org("my-org")
|
|
152
|
+
self.assertEqual(token, "TOKEN_FROM_DISPLAY")
|
|
153
|
+
|
|
154
|
+
def test_both_paths_redacted_raises(self):
|
|
155
|
+
"""If both paths return the placeholder, surface a clean SystemExit
|
|
156
|
+
rather than handing back the redaction string to downstream callers
|
|
157
|
+
(which would cause INVALID_AUTH_HEADER 401 on every Tooling/REST call)."""
|
|
158
|
+
with mock.patch.object(
|
|
159
|
+
dc.subprocess, "run",
|
|
160
|
+
side_effect=self._route(primary_redacted=True, display_redacted=True),
|
|
161
|
+
):
|
|
162
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
163
|
+
dc.resolve_org("my-org")
|
|
164
|
+
self.assertIn("could not retrieve a usable access token", str(ctx.exception))
|
|
165
|
+
|
|
166
|
+
def test_raises_systemexit_when_sf_cli_missing(self):
|
|
167
|
+
"""sf binary not on PATH — bail with the upgrade hint."""
|
|
168
|
+
with mock.patch.object(
|
|
169
|
+
dc.subprocess, "run", side_effect=FileNotFoundError("sf"),
|
|
170
|
+
):
|
|
171
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
172
|
+
dc.resolve_org("my-org")
|
|
173
|
+
self.assertIn("sf CLI not found", str(ctx.exception))
|
|
174
|
+
|
|
175
|
+
def test_raises_systemexit_on_display_failure(self):
|
|
176
|
+
"""org_display itself failing is fatal — no instanceUrl, no recovery."""
|
|
177
|
+
err = subprocess.CalledProcessError(
|
|
178
|
+
returncode=1, cmd=["sf", "org", "display"], stderr="No AuthInfo found",
|
|
179
|
+
)
|
|
180
|
+
with mock.patch.object(dc.subprocess, "run", side_effect=err):
|
|
181
|
+
with self.assertRaises(SystemExit):
|
|
182
|
+
dc.resolve_org("my-org")
|
|
183
|
+
|
|
184
|
+
def test_primary_path_argv_contains_show_access_token(self):
|
|
185
|
+
"""Tripwire — primary call MUST be `sf org auth show-access-token`
|
|
186
|
+
with `--no-prompt`. Without `--no-prompt` the command blocks on a
|
|
187
|
+
confirmation banner that --json doesn't suppress on its own."""
|
|
188
|
+
captured_argvs: list[list[str]] = []
|
|
189
|
+
|
|
190
|
+
def fake_run(argv, **kwargs):
|
|
191
|
+
captured_argvs.append(argv)
|
|
192
|
+
if "display" in argv:
|
|
193
|
+
return self._cp(self._display_payload())
|
|
194
|
+
return self._cp(self._show_token_payload())
|
|
195
|
+
|
|
196
|
+
with mock.patch.object(dc.subprocess, "run", side_effect=fake_run):
|
|
197
|
+
dc.resolve_org("my-org")
|
|
198
|
+
|
|
199
|
+
# display call should still pass --verbose + SF_TEMP_SHOW_SECRETS
|
|
200
|
+
# (so the fallback path stays viable on this same invocation).
|
|
201
|
+
self.assertTrue(any("display" in a for a in captured_argvs))
|
|
202
|
+
# primary call shape:
|
|
203
|
+
primary = next(a for a in captured_argvs if "show-access-token" in a)
|
|
204
|
+
self.assertIn("--no-prompt", primary)
|
|
205
|
+
self.assertIn("--json", primary)
|
|
206
|
+
self.assertIn("--target-org", primary)
|
|
207
|
+
|
|
208
|
+
def test_display_call_still_passes_verbose_and_show_secrets_env(self):
|
|
209
|
+
"""The legacy fallback path stays armed: org_display still runs
|
|
210
|
+
with `--verbose` (so accessToken is emitted) and
|
|
211
|
+
`SF_TEMP_SHOW_SECRETS=true` (so the token isn't redacted on sf
|
|
212
|
+
CLI versions that still honour the workaround)."""
|
|
213
|
+
captured = {}
|
|
214
|
+
|
|
215
|
+
def fake_run(argv, **kwargs):
|
|
216
|
+
if "display" in argv:
|
|
217
|
+
captured["argv"] = argv
|
|
218
|
+
captured["env"] = kwargs.get("env")
|
|
219
|
+
return self._cp(self._display_payload())
|
|
220
|
+
return self._cp(self._show_token_payload())
|
|
221
|
+
|
|
222
|
+
with mock.patch.object(dc.subprocess, "run", side_effect=fake_run):
|
|
223
|
+
dc.resolve_org("my-org")
|
|
224
|
+
|
|
225
|
+
self.assertIn("--verbose", captured["argv"])
|
|
226
|
+
self.assertIsNotNone(captured["env"])
|
|
227
|
+
self.assertEqual(captured["env"].get("SF_TEMP_SHOW_SECRETS"), "true")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# -----------------------------------------------------------------------------
|
|
231
|
+
# dc.post — HTTP path (urllib mock)
|
|
232
|
+
# -----------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class PostTests(unittest.TestCase):
|
|
236
|
+
|
|
237
|
+
def _fake_response(self, body: bytes):
|
|
238
|
+
# Context-manager mock for urllib.request.urlopen
|
|
239
|
+
cm = mock.MagicMock()
|
|
240
|
+
cm.__enter__.return_value.read.return_value = body
|
|
241
|
+
cm.__exit__.return_value = False
|
|
242
|
+
return cm
|
|
243
|
+
|
|
244
|
+
def test_returns_rows_on_2xx(self):
|
|
245
|
+
body = json.dumps({"data": [{"a": 1}, {"a": 2}]}).encode()
|
|
246
|
+
with mock.patch.object(
|
|
247
|
+
dc.urllib.request, "urlopen", return_value=self._fake_response(body),
|
|
248
|
+
):
|
|
249
|
+
out = dc.post(
|
|
250
|
+
"SELECT 1", "https://x.salesforce.com", "TOKEN", "sessions",
|
|
251
|
+
)
|
|
252
|
+
self.assertEqual(out, [{"a": 1}, {"a": 2}])
|
|
253
|
+
|
|
254
|
+
def test_raises_dcqueryerror_with_query_name_on_http_error(self):
|
|
255
|
+
# HTTPError carries (url, code, msg, hdrs, fp). We need fp.read() to
|
|
256
|
+
# work — the impl calls e.read() to grab the body.
|
|
257
|
+
err = urllib.error.HTTPError(
|
|
258
|
+
url="https://x.salesforce.com",
|
|
259
|
+
code=400, msg="Bad Request",
|
|
260
|
+
hdrs=None,
|
|
261
|
+
fp=io.BytesIO(b"sql parse error: missing FROM"),
|
|
262
|
+
)
|
|
263
|
+
with mock.patch.object(
|
|
264
|
+
dc.urllib.request, "urlopen", side_effect=err,
|
|
265
|
+
):
|
|
266
|
+
with self.assertRaises(dc.DCQueryError) as ctx:
|
|
267
|
+
dc.post("SELECT bad", "https://x", "TOKEN", "sessions")
|
|
268
|
+
msg = str(ctx.exception)
|
|
269
|
+
self.assertIn("sessions", msg)
|
|
270
|
+
self.assertIn("http=400", msg)
|
|
271
|
+
self.assertIn("sql parse error", msg)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# -----------------------------------------------------------------------------
|
|
275
|
+
# resolve_session.is_messaging_id — pure shape check
|
|
276
|
+
# -----------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class IsMessagingIdTests(unittest.TestCase):
|
|
280
|
+
|
|
281
|
+
def test_15_char_0Mw_prefix_matches(self):
|
|
282
|
+
self.assertTrue(resolve_session.is_messaging_id("0MwTESTMSG12345"))
|
|
283
|
+
|
|
284
|
+
def test_18_char_0Mw_prefix_matches(self):
|
|
285
|
+
self.assertTrue(resolve_session.is_messaging_id("0MwTESTMSG12345AAA"))
|
|
286
|
+
|
|
287
|
+
def test_uuid_does_not_match(self):
|
|
288
|
+
# 36 chars, dashes — UUIDs never accidentally pass.
|
|
289
|
+
self.assertFalse(resolve_session.is_messaging_id(IDS.SID))
|
|
290
|
+
|
|
291
|
+
def test_empty_does_not_match(self):
|
|
292
|
+
self.assertFalse(resolve_session.is_messaging_id(""))
|
|
293
|
+
|
|
294
|
+
def test_wrong_prefix_does_not_match(self):
|
|
295
|
+
self.assertFalse(resolve_session.is_messaging_id("FOOVF00000AtTbV"))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# -----------------------------------------------------------------------------
|
|
299
|
+
# resolve_session.resolve_from_disk — scans DATA_ROOT
|
|
300
|
+
# -----------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class ResolveFromDiskTests(unittest.TestCase):
|
|
304
|
+
|
|
305
|
+
def test_uuid_input_passes_through_unchanged(self):
|
|
306
|
+
self.assertEqual(
|
|
307
|
+
resolve_session.resolve_from_disk(IDS.SID), IDS.SID,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def test_finds_uuid_when_messaging_id_in_dc_sessions(self):
|
|
311
|
+
with TemporaryDirectory() as t:
|
|
312
|
+
tmp = Path(t)
|
|
313
|
+
write_to_disk(tmp) # synthetic fixture has the messaging id wired
|
|
314
|
+
with mock.patch.object(
|
|
315
|
+
resolve_session, "DATA_ROOT", tmp,
|
|
316
|
+
):
|
|
317
|
+
out = resolve_session.resolve_from_disk("0MwTESTMSG12345AAA")
|
|
318
|
+
self.assertEqual(out, IDS.SID)
|
|
319
|
+
|
|
320
|
+
def test_returns_none_when_messaging_id_not_present_on_disk(self):
|
|
321
|
+
with TemporaryDirectory() as t:
|
|
322
|
+
tmp = Path(t)
|
|
323
|
+
write_to_disk(tmp)
|
|
324
|
+
with mock.patch.object(
|
|
325
|
+
resolve_session, "DATA_ROOT", tmp,
|
|
326
|
+
):
|
|
327
|
+
out = resolve_session.resolve_from_disk("0Mw000000000000")
|
|
328
|
+
self.assertIsNone(out)
|
|
329
|
+
|
|
330
|
+
def test_returns_none_when_data_root_missing(self):
|
|
331
|
+
with TemporaryDirectory() as t:
|
|
332
|
+
ghost = Path(t) / "no-such"
|
|
333
|
+
with mock.patch.object(
|
|
334
|
+
resolve_session, "DATA_ROOT", ghost,
|
|
335
|
+
):
|
|
336
|
+
out = resolve_session.resolve_from_disk("0MwTESTMSG12345AAA")
|
|
337
|
+
self.assertIsNone(out)
|
|
338
|
+
|
|
339
|
+
def test_skips_archive_dirs(self):
|
|
340
|
+
# Plant a duplicate inside an "<uuid> - archive 1" dir; the resolver
|
|
341
|
+
# should ignore it. Without the skip, the extra row could trigger
|
|
342
|
+
# spurious multi-match.
|
|
343
|
+
with TemporaryDirectory() as t:
|
|
344
|
+
tmp = Path(t)
|
|
345
|
+
write_to_disk(tmp)
|
|
346
|
+
archive = tmp / IDS.ORG_ID_15 / f"{IDS.AGENT_API}__{IDS.AGENT_VERSION}" / f"{IDS.SID} - archive 1"
|
|
347
|
+
archive.mkdir(parents=True)
|
|
348
|
+
(archive / "dc.sessions.json").write_text(json.dumps([{
|
|
349
|
+
"ssot__Id__c": "different-uuid-but-same-msg",
|
|
350
|
+
"ssot__RelatedMessagingSessionId__c": "0MwTESTMSG12345AAA",
|
|
351
|
+
}]))
|
|
352
|
+
with mock.patch.object(resolve_session, "DATA_ROOT", tmp):
|
|
353
|
+
out = resolve_session.resolve_from_disk("0MwTESTMSG12345AAA")
|
|
354
|
+
# Resolver should return the canonical UUID, ignoring the archive
|
|
355
|
+
# row. (If the archive weren't skipped, this would multi-match
|
|
356
|
+
# raise.)
|
|
357
|
+
self.assertEqual(out, IDS.SID)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# -----------------------------------------------------------------------------
|
|
361
|
+
# resolve_session.resolve_disk_or_live — combined path
|
|
362
|
+
# -----------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class ResolveDiskOrLiveTests(unittest.TestCase):
|
|
366
|
+
|
|
367
|
+
def test_uuid_input_passes_through(self):
|
|
368
|
+
self.assertEqual(
|
|
369
|
+
resolve_session.resolve_disk_or_live(IDS.SID), IDS.SID,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def test_disk_hit_returns_uuid_without_dc_call(self):
|
|
373
|
+
with TemporaryDirectory() as t:
|
|
374
|
+
tmp = Path(t)
|
|
375
|
+
write_to_disk(tmp)
|
|
376
|
+
with mock.patch.object(resolve_session, "DATA_ROOT", tmp):
|
|
377
|
+
with mock.patch.object(resolve_session, "_live_lookup") as live:
|
|
378
|
+
out = resolve_session.resolve_disk_or_live(
|
|
379
|
+
"0MwTESTMSG12345AAA", org="my-org",
|
|
380
|
+
)
|
|
381
|
+
live.assert_not_called()
|
|
382
|
+
self.assertEqual(out, IDS.SID)
|
|
383
|
+
|
|
384
|
+
def test_disk_miss_no_org_raises_with_hint(self):
|
|
385
|
+
with TemporaryDirectory() as t:
|
|
386
|
+
tmp = Path(t)
|
|
387
|
+
with mock.patch.object(resolve_session, "DATA_ROOT", tmp):
|
|
388
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
389
|
+
resolve_session.resolve_disk_or_live(
|
|
390
|
+
"0MwTESTMSG12345AAA",
|
|
391
|
+
)
|
|
392
|
+
self.assertIn("cannot resolve messaging id", str(ctx.exception))
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# -----------------------------------------------------------------------------
|
|
396
|
+
# resolve_session.resolve — live DC-backed path
|
|
397
|
+
# -----------------------------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class ResolveLiveTests(unittest.TestCase):
|
|
401
|
+
|
|
402
|
+
def test_single_row_returns_uuid(self):
|
|
403
|
+
with mock.patch.object(
|
|
404
|
+
resolve_session, "_live_lookup",
|
|
405
|
+
return_value=[{"ssot__Id__c": "uuid-1"}],
|
|
406
|
+
):
|
|
407
|
+
out = resolve_session.resolve("0MwTESTMSG12345AAA", org="my-org")
|
|
408
|
+
self.assertEqual(out, "uuid-1")
|
|
409
|
+
|
|
410
|
+
def test_zero_rows_raises(self):
|
|
411
|
+
with mock.patch.object(
|
|
412
|
+
resolve_session, "_live_lookup", return_value=[],
|
|
413
|
+
):
|
|
414
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
415
|
+
resolve_session.resolve("0MwTESTMSG12345AAA", org="my-org")
|
|
416
|
+
self.assertIn("no ssot__AIAgentSession", str(ctx.exception))
|
|
417
|
+
|
|
418
|
+
def test_multi_row_raises_with_candidate_list(self):
|
|
419
|
+
rows = [
|
|
420
|
+
{"ssot__Id__c": "uuid-A", "ssot__StartTimestamp__c": "t"},
|
|
421
|
+
{"ssot__Id__c": "uuid-B", "ssot__StartTimestamp__c": "t"},
|
|
422
|
+
]
|
|
423
|
+
with mock.patch.object(
|
|
424
|
+
resolve_session, "_live_lookup", return_value=rows,
|
|
425
|
+
):
|
|
426
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
427
|
+
resolve_session.resolve("0MwTESTMSG12345AAA", org="my-org")
|
|
428
|
+
self.assertIn("uuid-A", str(ctx.exception))
|
|
429
|
+
self.assertIn("uuid-B", str(ctx.exception))
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
if __name__ == "__main__":
|
|
433
|
+
unittest.main()
|