@salesforce/afv-skills 1.59.0 → 1.61.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-architecture-analyze/SKILL.md +66 -17
- package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
- package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
- package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
- package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
- package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
- package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
- package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
- package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
- package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
- package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
- package/skills/agentforce-d360-analyze/SKILL.md +28 -10
- package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
- package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
- package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
- package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
- package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
- package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
- package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
- package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
- package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
- package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
- package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
- package/skills/data360-schema-get/SKILL.md +3 -3
- package/skills/design-systems-slds-validate/references/report-format.md +1 -1
- package/skills/dx-org-manage/SKILL.md +1 -0
- package/skills/dx-org-shape-manage/SKILL.md +1 -0
- package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
- package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
- package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
- package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
- package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
- package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
- package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
- package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
- package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
- package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
- package/skills/experience-content-media-image-generate/SKILL.md +83 -0
- package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
- package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
- package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
- package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
- package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
- package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
- package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
- package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
- package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
- package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
- package/skills/marketing-message-test/SKILL.md +194 -0
- package/skills/marketing-message-test/references/api-contract.md +333 -0
- package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
- package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
- package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
- package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
- package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
- package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
- package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
- package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
- package/skills/platform-apex-logs-debug/SKILL.md +1 -0
- package/skills/platform-metadata-deploy/SKILL.md +1 -0
- package/skills/platform-models-api-configure/SKILL.md +5 -5
- package/skills/platform-soql-query/SKILL.md +1 -0
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
- package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
- package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
- package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
- package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
- package/skills/tableau-next-asset-create/README.md +65 -0
- package/skills/tableau-next-asset-create/SKILL.md +409 -0
- package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
- package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
- package/skills/tableau-next-asset-create/references/alerts.md +111 -0
- package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
- package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
- package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
- package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
- package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
- package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
- package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
- package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
- package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
- package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
- package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
- package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
- package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
- package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
- package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
- package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
- package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
- package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
- package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
- package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
- package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
- package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
- package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
- package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
- package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
- package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
- package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
- package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
- package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
- package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
- package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
- package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
- package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
- package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
- package/skills/service-de-channel-consent-configure/SKILL.md +0 -230
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
"""Tests for ``main._resolve_creds``
|
|
2
|
-
|
|
3
|
-
2026-05-27).
|
|
1
|
+
"""Tests for ``main._resolve_creds`` fail-fast token retrieval
|
|
2
|
+
(forcedotcom/cli#3560).
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
The access token must come ONLY from
|
|
5
|
+
``sf org auth show-access-token --json --no-prompt`` via the
|
|
6
|
+
``show_access_token`` recipe. ``sf org display`` supplies instanceUrl
|
|
7
|
+
only; any ``accessToken`` it carries is never used.
|
|
9
8
|
|
|
10
9
|
We mock ``run_sf`` (not subprocess) since these tests target the
|
|
11
10
|
orchestration logic in ``main.py``, not the recipe loader. The recipe
|
|
12
|
-
loader
|
|
13
|
-
``test_sf_cli.py``.
|
|
11
|
+
loader, subprocess env, and capability preflight have their own
|
|
12
|
+
coverage in ``test_sf_cli.py``.
|
|
14
13
|
"""
|
|
15
14
|
from __future__ import annotations
|
|
16
15
|
|
|
@@ -39,7 +38,14 @@ def _show_token_payload(*, access_token="TOKEN_FROM_SHOW"):
|
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
class ResolveCredsTests(unittest.TestCase):
|
|
42
|
-
"""Cover every branch of
|
|
41
|
+
"""Cover every branch of fail-fast token retrieval."""
|
|
42
|
+
|
|
43
|
+
def setUp(self):
|
|
44
|
+
patcher = mock.patch.object(
|
|
45
|
+
main, "assert_show_access_token_capability", return_value=None,
|
|
46
|
+
)
|
|
47
|
+
patcher.start()
|
|
48
|
+
self.addCleanup(patcher.stop)
|
|
43
49
|
|
|
44
50
|
def _route(self, *, primary_payload=None, primary_exc=None,
|
|
45
51
|
display_payload=None):
|
|
@@ -65,56 +71,55 @@ class ResolveCredsTests(unittest.TestCase):
|
|
|
65
71
|
self.assertEqual(url, "https://example.salesforce.com")
|
|
66
72
|
self.assertEqual(token, "TOKEN_FROM_SHOW")
|
|
67
73
|
|
|
68
|
-
def
|
|
69
|
-
"""
|
|
70
|
-
|
|
74
|
+
def test_primary_unknown_raises_authrequired(self):
|
|
75
|
+
"""show-access-token failing is terminal — the display payload's
|
|
76
|
+
accessToken (non-empty here) must NOT be used as a fallback."""
|
|
71
77
|
with mock.patch.object(
|
|
72
78
|
main, "run_sf",
|
|
73
79
|
side_effect=self._route(
|
|
74
80
|
primary_exc=SfCliError("sf CLI 'show_access_token' failed"),
|
|
75
81
|
),
|
|
76
82
|
):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
self.
|
|
83
|
+
with self.assertRaises(AuthRequired) as ctx:
|
|
84
|
+
main._resolve_creds("my-org")
|
|
85
|
+
self.assertIn("show-access-token", str(ctx.exception))
|
|
86
|
+
self.assertNotIn("TOKEN_FROM_DISPLAY", str(ctx.exception))
|
|
80
87
|
|
|
81
|
-
def
|
|
82
|
-
"""Edge case: dedicated command runs cleanly but returns the
|
|
83
|
-
placeholder string. Treat as failure and use the display fallback."""
|
|
88
|
+
def test_primary_auth_required_propagates_as_authrequired(self):
|
|
84
89
|
with mock.patch.object(
|
|
85
90
|
main, "run_sf",
|
|
86
91
|
side_effect=self._route(
|
|
87
|
-
|
|
92
|
+
primary_exc=AuthRequired("NoOrgAuthenticationError"),
|
|
88
93
|
),
|
|
89
94
|
):
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
with self.assertRaises(AuthRequired) as ctx:
|
|
96
|
+
main._resolve_creds("my-org")
|
|
97
|
+
self.assertIn("show-access-token", str(ctx.exception))
|
|
92
98
|
|
|
93
|
-
def
|
|
94
|
-
"""
|
|
99
|
+
def test_primary_returns_redacted_token_raises(self):
|
|
100
|
+
"""Dedicated command runs cleanly but returns the placeholder
|
|
101
|
+
string — fail fast rather than handing it to Tooling/REST callers
|
|
102
|
+
(which would 401 with INVALID_AUTH_HEADER)."""
|
|
95
103
|
with mock.patch.object(
|
|
96
104
|
main, "run_sf",
|
|
97
105
|
side_effect=self._route(
|
|
98
|
-
primary_payload=_show_token_payload(access_token=
|
|
106
|
+
primary_payload=_show_token_payload(access_token=REDACTED_TOKEN),
|
|
99
107
|
),
|
|
100
108
|
):
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
with self.assertRaises(AuthRequired) as ctx:
|
|
110
|
+
main._resolve_creds("my-org")
|
|
111
|
+
self.assertIn("usable access token", str(ctx.exception))
|
|
103
112
|
|
|
104
|
-
def
|
|
105
|
-
"""If both paths come back redacted/empty, surface AuthRequired
|
|
106
|
-
rather than handing the placeholder to downstream Tooling/REST
|
|
107
|
-
callers (which would 401 with INVALID_AUTH_HEADER)."""
|
|
113
|
+
def test_primary_returns_empty_token_raises(self):
|
|
108
114
|
with mock.patch.object(
|
|
109
115
|
main, "run_sf",
|
|
110
116
|
side_effect=self._route(
|
|
111
|
-
primary_payload=_show_token_payload(access_token=
|
|
112
|
-
display_payload=_display_payload(access_token=REDACTED_TOKEN),
|
|
117
|
+
primary_payload=_show_token_payload(access_token=""),
|
|
113
118
|
),
|
|
114
119
|
):
|
|
115
120
|
with self.assertRaises(AuthRequired) as ctx:
|
|
116
121
|
main._resolve_creds("my-org")
|
|
117
|
-
self.assertIn("
|
|
122
|
+
self.assertIn("usable access token", str(ctx.exception))
|
|
118
123
|
|
|
119
124
|
def test_missing_instance_url_raises_authrequired(self):
|
|
120
125
|
"""display returning empty instanceUrl is a hard failure — no
|
|
@@ -132,8 +137,7 @@ class ResolveCredsTests(unittest.TestCase):
|
|
|
132
137
|
def test_primary_path_recipe_name_is_show_access_token(self):
|
|
133
138
|
"""Tripwire — the primary call MUST go through the
|
|
134
139
|
``show_access_token`` recipe. Without it the patch silently
|
|
135
|
-
regresses to
|
|
136
|
-
already shipped the dedicated command."""
|
|
140
|
+
regresses to reading a (redacted) token from ``sf org display``."""
|
|
137
141
|
recipes_called: list[str] = []
|
|
138
142
|
|
|
139
143
|
def fake_run_sf(name, **params):
|
|
@@ -152,6 +156,150 @@ class ResolveCredsTests(unittest.TestCase):
|
|
|
152
156
|
# (We don't capture params here, but the recipe loader enforces
|
|
153
157
|
# required_params at run_sf time — see test_sf_cli.py.)
|
|
154
158
|
|
|
159
|
+
def test_capability_preflight_failure_raises_authrequired(self):
|
|
160
|
+
"""Missing ``sf org auth show-access-token`` fails fast with the
|
|
161
|
+
preflight's upgrade message, before any token call is made."""
|
|
162
|
+
recipes_called: list[str] = []
|
|
163
|
+
|
|
164
|
+
def fake_run_sf(name, **params):
|
|
165
|
+
recipes_called.append(name)
|
|
166
|
+
return self._route()(name, **params)
|
|
167
|
+
|
|
168
|
+
with mock.patch.object(
|
|
169
|
+
main, "assert_show_access_token_capability",
|
|
170
|
+
side_effect=SfCliError(
|
|
171
|
+
"sf CLI is missing required command "
|
|
172
|
+
"'sf org auth show-access-token'"
|
|
173
|
+
),
|
|
174
|
+
), mock.patch.object(main, "run_sf", side_effect=fake_run_sf):
|
|
175
|
+
with self.assertRaises(AuthRequired) as ctx:
|
|
176
|
+
main._resolve_creds("my-org")
|
|
177
|
+
self.assertIn("missing required command", str(ctx.exception))
|
|
178
|
+
# Preflight runs first: neither `sf org display` nor the token
|
|
179
|
+
# command may be invoked when it fails.
|
|
180
|
+
self.assertEqual(recipes_called, [])
|
|
181
|
+
|
|
182
|
+
def test_preflight_runs_before_org_display(self):
|
|
183
|
+
calls: list[str] = []
|
|
184
|
+
|
|
185
|
+
def fake_run_sf(name, **params):
|
|
186
|
+
calls.append(name)
|
|
187
|
+
return self._route()(name, **params)
|
|
188
|
+
|
|
189
|
+
with mock.patch.object(
|
|
190
|
+
main, "assert_show_access_token_capability",
|
|
191
|
+
side_effect=lambda: calls.append("preflight"),
|
|
192
|
+
), mock.patch.object(main, "run_sf", side_effect=fake_run_sf):
|
|
193
|
+
main._resolve_creds("my-org")
|
|
194
|
+
self.assertEqual(calls, ["preflight", "org_display", "show_access_token"])
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ResolveCredsPreflightCacheTests(unittest.TestCase):
|
|
198
|
+
"""Exercise the REAL preflight (subprocess mocked) through
|
|
199
|
+
``_resolve_creds``: argv order, once-per-process success caching, and
|
|
200
|
+
no caching of failures."""
|
|
201
|
+
|
|
202
|
+
PREFLIGHT_ARGV = ["sf", "org", "auth", "show-access-token", "--help"]
|
|
203
|
+
REAL_HELP = "USAGE\n $ sf org auth show-access-token -o <value>\n"
|
|
204
|
+
|
|
205
|
+
def setUp(self):
|
|
206
|
+
import sf_cli # type: ignore
|
|
207
|
+
self.sf_cli = sf_cli
|
|
208
|
+
sf_cli._show_access_token_capability_ok = False
|
|
209
|
+
self.addCleanup(
|
|
210
|
+
setattr, sf_cli, "_show_access_token_capability_ok", False,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def _patches(self, calls, preflight_effects):
|
|
214
|
+
"""Patch subprocess (preflight) + run_sf (recipes) into one ordered
|
|
215
|
+
``calls`` log. ``preflight_effects`` is consumed per preflight spawn."""
|
|
216
|
+
from types import SimpleNamespace
|
|
217
|
+
effects = list(preflight_effects)
|
|
218
|
+
|
|
219
|
+
def fake_subprocess_run(argv, **kwargs):
|
|
220
|
+
calls.append(list(argv))
|
|
221
|
+
effect = effects.pop(0)
|
|
222
|
+
if isinstance(effect, BaseException):
|
|
223
|
+
raise effect
|
|
224
|
+
if effect == "no-signature":
|
|
225
|
+
# Zero exit but not the command's help (unknown command).
|
|
226
|
+
return SimpleNamespace(
|
|
227
|
+
returncode=0, stdout="USAGE\n $ sf [COMMAND]\n", stderr="",
|
|
228
|
+
)
|
|
229
|
+
return SimpleNamespace(
|
|
230
|
+
returncode=0, stdout=self.REAL_HELP, stderr="",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def fake_run_sf(name, **params):
|
|
234
|
+
calls.append(name)
|
|
235
|
+
if name == "org_display":
|
|
236
|
+
return _display_payload()
|
|
237
|
+
if name == "show_access_token":
|
|
238
|
+
return _show_token_payload()
|
|
239
|
+
raise AssertionError(f"unexpected recipe: {name}")
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
mock.patch.object(
|
|
243
|
+
self.sf_cli.subprocess, "run", side_effect=fake_subprocess_run,
|
|
244
|
+
),
|
|
245
|
+
mock.patch.object(main, "run_sf", side_effect=fake_run_sf),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def test_preflight_argv_is_first_sf_call(self):
|
|
249
|
+
calls: list = []
|
|
250
|
+
p1, p2 = self._patches(calls, ["ok"])
|
|
251
|
+
with p1, p2:
|
|
252
|
+
main._resolve_creds("my-org")
|
|
253
|
+
self.assertEqual(calls[0], self.PREFLIGHT_ARGV)
|
|
254
|
+
self.assertEqual(calls[1:], ["org_display", "show_access_token"])
|
|
255
|
+
|
|
256
|
+
def test_preflight_runs_once_across_two_resolves(self):
|
|
257
|
+
calls: list = []
|
|
258
|
+
p1, p2 = self._patches(calls, ["ok"])
|
|
259
|
+
with p1, p2:
|
|
260
|
+
main._resolve_creds("my-org")
|
|
261
|
+
main._resolve_creds("my-org")
|
|
262
|
+
self.assertEqual(calls.count(self.PREFLIGHT_ARGV), 1)
|
|
263
|
+
self.assertEqual(calls.count("org_display"), 2)
|
|
264
|
+
|
|
265
|
+
def test_missing_sf_fails_preflight_and_skips_display(self):
|
|
266
|
+
calls: list = []
|
|
267
|
+
p1, p2 = self._patches(calls, [FileNotFoundError("sf")])
|
|
268
|
+
with p1, p2:
|
|
269
|
+
with self.assertRaises(AuthRequired) as ctx:
|
|
270
|
+
main._resolve_creds("my-org")
|
|
271
|
+
self.assertIn("not found on PATH", str(ctx.exception))
|
|
272
|
+
self.assertNotIn("org_display", calls)
|
|
273
|
+
|
|
274
|
+
def test_failed_preflight_is_retried_on_next_resolve(self):
|
|
275
|
+
import subprocess as _subprocess
|
|
276
|
+
err = _subprocess.CalledProcessError(
|
|
277
|
+
returncode=1, cmd=self.PREFLIGHT_ARGV,
|
|
278
|
+
stderr="show-access-token is not a sf command",
|
|
279
|
+
)
|
|
280
|
+
calls: list = []
|
|
281
|
+
p1, p2 = self._patches(calls, [err, "ok"])
|
|
282
|
+
with p1, p2:
|
|
283
|
+
with self.assertRaises(AuthRequired):
|
|
284
|
+
main._resolve_creds("my-org")
|
|
285
|
+
self.assertNotIn("org_display", calls)
|
|
286
|
+
url, token = main._resolve_creds("my-org")
|
|
287
|
+
self.assertEqual(token, "TOKEN_FROM_SHOW")
|
|
288
|
+
self.assertEqual(calls.count(self.PREFLIGHT_ARGV), 2)
|
|
289
|
+
|
|
290
|
+
def test_zero_exit_without_help_signature_fails_and_is_retried(self):
|
|
291
|
+
calls: list = []
|
|
292
|
+
p1, p2 = self._patches(calls, ["no-signature", "ok"])
|
|
293
|
+
with p1, p2:
|
|
294
|
+
with self.assertRaises(AuthRequired) as ctx:
|
|
295
|
+
main._resolve_creds("my-org")
|
|
296
|
+
self.assertIn("missing required command", str(ctx.exception))
|
|
297
|
+
self.assertNotIn("org_display", calls)
|
|
298
|
+
self.assertFalse(self.sf_cli._show_access_token_capability_ok)
|
|
299
|
+
url, token = main._resolve_creds("my-org")
|
|
300
|
+
self.assertEqual(token, "TOKEN_FROM_SHOW")
|
|
301
|
+
self.assertEqual(calls.count(self.PREFLIGHT_ARGV), 2)
|
|
302
|
+
|
|
155
303
|
|
|
156
304
|
if __name__ == "__main__":
|
|
157
305
|
unittest.main()
|
|
@@ -122,6 +122,23 @@ class RedactErrorTests(unittest.TestCase):
|
|
|
122
122
|
out = rest_client.redact_error(exc)
|
|
123
123
|
self.assertNotIn("TESTONLY_BEARER_VALUE", out)
|
|
124
124
|
|
|
125
|
+
def test_bare_bearer_token_redacted(self):
|
|
126
|
+
# No `Authorization:` prefix — e.g. curl-style traces or CLI stderr.
|
|
127
|
+
for raw in (
|
|
128
|
+
"request failed: Bearer TESTONLY_BARE_VALUE",
|
|
129
|
+
"headers={'auth': 'bearer TESTONLY_DICT_VALUE'}",
|
|
130
|
+
):
|
|
131
|
+
with self.subTest(raw=raw):
|
|
132
|
+
out = rest_client.redact_text(raw)
|
|
133
|
+
self.assertIn("<redacted>", out)
|
|
134
|
+
self.assertNotIn("TESTONLY_BARE_VALUE", out)
|
|
135
|
+
self.assertNotIn("TESTONLY_DICT_VALUE", out)
|
|
136
|
+
|
|
137
|
+
def test_auth_header_not_double_redacted(self):
|
|
138
|
+
# The auth-header rule runs first; the bare rule must not double-mangle it.
|
|
139
|
+
out = rest_client.redact_text("Authorization: Bearer TESTONLY_HDR")
|
|
140
|
+
self.assertEqual(out, "Authorization: Bearer <redacted>")
|
|
141
|
+
|
|
125
142
|
def test_access_token_querystring_redacted(self):
|
|
126
143
|
exc = RuntimeError("POST /oauth/token accessToken=TESTONLY_TOKEN&foo=1")
|
|
127
144
|
out = rest_client.redact_error(exc)
|
|
@@ -211,13 +211,11 @@ class RunSfTests(unittest.TestCase):
|
|
|
211
211
|
sf_cli.run_sf("org_display", ORG_ALIAS="my-org")
|
|
212
212
|
self.assertIn("invocation failed", str(ctx.exception))
|
|
213
213
|
|
|
214
|
-
def
|
|
215
|
-
"""Tripwire
|
|
214
|
+
def test_does_not_inject_show_secrets_env(self):
|
|
215
|
+
"""Tripwire: run_sf must not rely on SF_TEMP_SHOW_SECRETS.
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
show-access-token' to view"`` in place of the bearer token, and
|
|
220
|
-
every downstream Tooling/REST call returns INVALID_AUTH_HEADER 401.
|
|
217
|
+
sf CLI removes that env var (forcedotcom/cli#3560); tokens come
|
|
218
|
+
only from ``sf org auth show-access-token``.
|
|
221
219
|
"""
|
|
222
220
|
from unittest import mock
|
|
223
221
|
captured = {}
|
|
@@ -232,15 +230,14 @@ class RunSfTests(unittest.TestCase):
|
|
|
232
230
|
with mock.patch.object(sf_cli.subprocess, "run", side_effect=fake_run):
|
|
233
231
|
sf_cli.run_sf("org_display", ORG_ALIAS="my-org")
|
|
234
232
|
|
|
235
|
-
|
|
236
|
-
self.
|
|
233
|
+
env = captured["env"] or {}
|
|
234
|
+
self.assertNotIn("SF_TEMP_SHOW_SECRETS", env)
|
|
237
235
|
|
|
238
236
|
def test_org_display_recipe_includes_verbose_flag(self):
|
|
239
237
|
"""Tripwire for the W-22582511 ``--verbose`` fix.
|
|
240
238
|
|
|
241
|
-
Without ``--verbose``, sf CLI
|
|
242
|
-
``--json`` output
|
|
243
|
-
so the redaction-env workaround is necessary but not sufficient.
|
|
239
|
+
Without ``--verbose``, some sf CLI builds omit fields that callers
|
|
240
|
+
expect in ``--json`` output.
|
|
244
241
|
"""
|
|
245
242
|
from unittest import mock
|
|
246
243
|
captured = {}
|
|
@@ -257,5 +254,162 @@ class RunSfTests(unittest.TestCase):
|
|
|
257
254
|
self.assertIn("--verbose", captured["argv"])
|
|
258
255
|
|
|
259
256
|
|
|
257
|
+
class ShowAccessTokenCapabilityTests(unittest.TestCase):
|
|
258
|
+
"""Preflight for the required ``sf org auth show-access-token`` command."""
|
|
259
|
+
|
|
260
|
+
# Shape of real ``--help`` output (sf CLI 2.150.6): the command id is
|
|
261
|
+
# printed in the USAGE line.
|
|
262
|
+
REAL_HELP = (
|
|
263
|
+
"Show an access token for an org.\n\nUSAGE\n"
|
|
264
|
+
" $ sf org auth show-access-token -o <value> [--json]\n"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def setUp(self):
|
|
268
|
+
# The preflight caches success process-wide; isolate every test.
|
|
269
|
+
sf_cli._show_access_token_capability_ok = False
|
|
270
|
+
self.addCleanup(
|
|
271
|
+
setattr, sf_cli, "_show_access_token_capability_ok", False,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def test_capability_check_passes_when_help_available(self):
|
|
275
|
+
from types import SimpleNamespace
|
|
276
|
+
from unittest import mock
|
|
277
|
+
|
|
278
|
+
cp = SimpleNamespace(returncode=0, stdout=self.REAL_HELP, stderr="")
|
|
279
|
+
with mock.patch.object(sf_cli.subprocess, "run", return_value=cp) as run:
|
|
280
|
+
sf_cli.assert_show_access_token_capability()
|
|
281
|
+
argv = run.call_args.args[0]
|
|
282
|
+
self.assertEqual(argv, ["sf", "org", "auth", "show-access-token", "--help"])
|
|
283
|
+
|
|
284
|
+
def test_capability_check_raises_clear_error_when_missing(self):
|
|
285
|
+
import subprocess as _subprocess
|
|
286
|
+
from unittest import mock
|
|
287
|
+
|
|
288
|
+
err = _subprocess.CalledProcessError(
|
|
289
|
+
returncode=1,
|
|
290
|
+
cmd=["sf", "org", "auth", "show-access-token", "--help"],
|
|
291
|
+
stderr="show-access-token is not a sf command",
|
|
292
|
+
)
|
|
293
|
+
with mock.patch.object(sf_cli.subprocess, "run", side_effect=err):
|
|
294
|
+
with self.assertRaises(sf_cli.SfCliError) as ctx:
|
|
295
|
+
sf_cli.assert_show_access_token_capability()
|
|
296
|
+
self.assertIn("missing required command", str(ctx.exception))
|
|
297
|
+
self.assertIn("sf org auth show-access-token", str(ctx.exception))
|
|
298
|
+
|
|
299
|
+
def test_capability_check_raises_when_sf_not_on_path(self):
|
|
300
|
+
from unittest import mock
|
|
301
|
+
|
|
302
|
+
with mock.patch.object(
|
|
303
|
+
sf_cli.subprocess, "run", side_effect=FileNotFoundError("sf"),
|
|
304
|
+
):
|
|
305
|
+
with self.assertRaises(sf_cli.SfCliError) as ctx:
|
|
306
|
+
sf_cli.assert_show_access_token_capability()
|
|
307
|
+
self.assertIn("not found on PATH", str(ctx.exception))
|
|
308
|
+
|
|
309
|
+
def test_successful_preflight_is_cached_for_process(self):
|
|
310
|
+
from types import SimpleNamespace
|
|
311
|
+
from unittest import mock
|
|
312
|
+
|
|
313
|
+
cp = SimpleNamespace(returncode=0, stdout=self.REAL_HELP, stderr="")
|
|
314
|
+
with mock.patch.object(sf_cli.subprocess, "run", return_value=cp) as run:
|
|
315
|
+
sf_cli.assert_show_access_token_capability()
|
|
316
|
+
sf_cli.assert_show_access_token_capability()
|
|
317
|
+
self.assertEqual(run.call_count, 1)
|
|
318
|
+
|
|
319
|
+
def test_failed_preflight_is_not_cached(self):
|
|
320
|
+
import subprocess as _subprocess
|
|
321
|
+
from types import SimpleNamespace
|
|
322
|
+
from unittest import mock
|
|
323
|
+
|
|
324
|
+
err = _subprocess.CalledProcessError(
|
|
325
|
+
returncode=1,
|
|
326
|
+
cmd=["sf", "org", "auth", "show-access-token", "--help"],
|
|
327
|
+
stderr="show-access-token is not a sf command",
|
|
328
|
+
)
|
|
329
|
+
ok = SimpleNamespace(returncode=0, stdout=self.REAL_HELP, stderr="")
|
|
330
|
+
with mock.patch.object(
|
|
331
|
+
sf_cli.subprocess, "run", side_effect=[err, ok],
|
|
332
|
+
) as run:
|
|
333
|
+
with self.assertRaises(sf_cli.SfCliError):
|
|
334
|
+
sf_cli.assert_show_access_token_capability()
|
|
335
|
+
# Retried (not cached) and now succeeds.
|
|
336
|
+
sf_cli.assert_show_access_token_capability()
|
|
337
|
+
# Success is cached — no third spawn.
|
|
338
|
+
sf_cli.assert_show_access_token_capability()
|
|
339
|
+
self.assertEqual(run.call_count, 2)
|
|
340
|
+
|
|
341
|
+
def test_zero_exit_without_help_signature_fails_and_is_not_cached(self):
|
|
342
|
+
"""A CLI that answers an unknown command with exit 0 (e.g. printing
|
|
343
|
+
top-level help) must NOT be treated as capable."""
|
|
344
|
+
from types import SimpleNamespace
|
|
345
|
+
from unittest import mock
|
|
346
|
+
|
|
347
|
+
bogus = SimpleNamespace(
|
|
348
|
+
returncode=0,
|
|
349
|
+
stdout="\x1b[1mUSAGE\x1b[22m\n $ sf [COMMAND]\n\nTOPICS\n org\n",
|
|
350
|
+
stderr="Authorization: Bearer SECRET_TOKEN_X",
|
|
351
|
+
)
|
|
352
|
+
ok = SimpleNamespace(returncode=0, stdout=self.REAL_HELP, stderr="")
|
|
353
|
+
with mock.patch.object(
|
|
354
|
+
sf_cli.subprocess, "run", side_effect=[bogus, ok],
|
|
355
|
+
) as run:
|
|
356
|
+
with self.assertRaises(sf_cli.SfCliError) as ctx:
|
|
357
|
+
sf_cli.assert_show_access_token_capability()
|
|
358
|
+
msg = str(ctx.exception)
|
|
359
|
+
self.assertIn("missing required command", msg)
|
|
360
|
+
self.assertNotIn("SECRET_TOKEN_X", msg)
|
|
361
|
+
self.assertFalse(sf_cli._show_access_token_capability_ok)
|
|
362
|
+
# Next call re-probes and now succeeds.
|
|
363
|
+
sf_cli.assert_show_access_token_capability()
|
|
364
|
+
self.assertEqual(run.call_count, 2)
|
|
365
|
+
self.assertTrue(sf_cli._show_access_token_capability_ok)
|
|
366
|
+
|
|
367
|
+
def test_zero_exit_with_empty_output_fails(self):
|
|
368
|
+
from types import SimpleNamespace
|
|
369
|
+
from unittest import mock
|
|
370
|
+
|
|
371
|
+
cp = SimpleNamespace(returncode=0, stdout="", stderr="")
|
|
372
|
+
with mock.patch.object(sf_cli.subprocess, "run", return_value=cp):
|
|
373
|
+
with self.assertRaises(sf_cli.SfCliError) as ctx:
|
|
374
|
+
sf_cli.assert_show_access_token_capability()
|
|
375
|
+
self.assertIn("lacked the command signature", str(ctx.exception))
|
|
376
|
+
self.assertFalse(sf_cli._show_access_token_capability_ok)
|
|
377
|
+
|
|
378
|
+
def test_zero_exit_unknown_command_notice_echoing_name_fails(self):
|
|
379
|
+
"""Unknown-command notices contain the bare command name; only the
|
|
380
|
+
USAGE form (`$ sf org auth show-access-token`) may pass."""
|
|
381
|
+
from types import SimpleNamespace
|
|
382
|
+
from unittest import mock
|
|
383
|
+
|
|
384
|
+
for out in (
|
|
385
|
+
"Error: Command org:auth:show-access-token not found.",
|
|
386
|
+
"Warning: show-access-token is not a sf command.",
|
|
387
|
+
"Did you mean org auth show-access-token? Run sf help for options.",
|
|
388
|
+
):
|
|
389
|
+
with self.subTest(out=out):
|
|
390
|
+
sf_cli._show_access_token_capability_ok = False
|
|
391
|
+
cp = SimpleNamespace(returncode=0, stdout="", stderr=out)
|
|
392
|
+
with mock.patch.object(sf_cli.subprocess, "run", return_value=cp):
|
|
393
|
+
with self.assertRaises(sf_cli.SfCliError) as ctx:
|
|
394
|
+
sf_cli.assert_show_access_token_capability()
|
|
395
|
+
self.assertIn("missing required command", str(ctx.exception))
|
|
396
|
+
self.assertFalse(sf_cli._show_access_token_capability_ok)
|
|
397
|
+
|
|
398
|
+
def test_ansi_colored_real_help_passes(self):
|
|
399
|
+
"""Real help with ANSI styling still matches the signature."""
|
|
400
|
+
from types import SimpleNamespace
|
|
401
|
+
from unittest import mock
|
|
402
|
+
|
|
403
|
+
cp = SimpleNamespace(
|
|
404
|
+
returncode=0,
|
|
405
|
+
stdout="\x1b[1mUSAGE\x1b[22m\n $ sf org auth "
|
|
406
|
+
"show-access-token -o <value>\n",
|
|
407
|
+
stderr="",
|
|
408
|
+
)
|
|
409
|
+
with mock.patch.object(sf_cli.subprocess, "run", return_value=cp):
|
|
410
|
+
sf_cli.assert_show_access_token_capability()
|
|
411
|
+
self.assertTrue(sf_cli._show_access_token_capability_ok)
|
|
412
|
+
|
|
413
|
+
|
|
260
414
|
if __name__ == "__main__":
|
|
261
415
|
unittest.main()
|