@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.
Files changed (174) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
  3. package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
  4. package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
  5. package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
  6. package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
  7. package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
  8. package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
  9. package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
  10. package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
  11. package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
  12. package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
  13. package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
  14. package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
  15. package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
  16. package/skills/agentforce-d360-analyze/SKILL.md +28 -10
  17. package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
  18. package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
  19. package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
  20. package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
  21. package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
  22. package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
  23. package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
  24. package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
  25. package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
  31. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
  32. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
  33. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
  34. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
  35. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
  36. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
  37. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
  38. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
  39. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
  40. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
  41. package/skills/data360-schema-get/SKILL.md +3 -3
  42. package/skills/design-systems-slds-validate/references/report-format.md +1 -1
  43. package/skills/dx-org-manage/SKILL.md +1 -0
  44. package/skills/dx-org-shape-manage/SKILL.md +1 -0
  45. package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
  46. package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
  47. package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
  48. package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
  49. package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
  50. package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
  51. package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
  52. package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
  53. package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
  54. package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
  55. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
  56. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
  57. package/skills/experience-content-media-image-generate/SKILL.md +83 -0
  58. package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
  59. package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
  60. package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
  61. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
  62. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
  63. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
  64. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
  65. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
  66. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
  67. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
  68. package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
  69. package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
  70. package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
  71. package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
  72. package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
  73. package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
  74. package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
  75. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
  76. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
  77. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
  78. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
  79. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
  80. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
  81. package/skills/marketing-message-test/SKILL.md +194 -0
  82. package/skills/marketing-message-test/references/api-contract.md +333 -0
  83. package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
  84. package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
  85. package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
  86. package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
  87. package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
  88. package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
  89. package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
  90. package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
  91. package/skills/platform-apex-logs-debug/SKILL.md +1 -0
  92. package/skills/platform-metadata-deploy/SKILL.md +1 -0
  93. package/skills/platform-models-api-configure/SKILL.md +5 -5
  94. package/skills/platform-soql-query/SKILL.md +1 -0
  95. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
  96. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
  97. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
  98. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
  99. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
  100. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
  101. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
  102. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
  103. package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
  104. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
  105. package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
  106. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
  107. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
  108. package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
  109. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  110. package/skills/tableau-next-asset-create/README.md +65 -0
  111. package/skills/tableau-next-asset-create/SKILL.md +409 -0
  112. package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
  113. package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
  114. package/skills/tableau-next-asset-create/references/alerts.md +111 -0
  115. package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
  116. package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
  117. package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
  118. package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
  119. package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
  120. package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
  121. package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
  122. package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
  123. package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
  124. package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
  125. package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
  126. package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
  127. package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
  128. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
  129. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
  130. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
  131. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
  132. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
  133. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
  134. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
  135. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
  136. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
  137. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
  138. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
  139. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
  140. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
  141. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
  142. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
  143. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
  144. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
  145. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
  146. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
  147. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
  148. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
  149. package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
  150. package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
  151. package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
  152. package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
  153. package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
  154. package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
  155. package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
  156. package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
  157. package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
  158. package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
  159. package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
  160. package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
  161. package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
  162. package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
  163. package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
  164. package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
  165. package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
  166. package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
  167. package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
  168. package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
  169. package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
  170. package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
  171. package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
  172. package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
  173. package/skills/service-de-channel-consent-configure/SKILL.md +0 -230
  174. 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`` — the two-path access-token retrieval
2
- introduced for forcedotcom/cli#3560 (sf CLI v2 redaction, effective
3
- 2026-05-27).
1
+ """Tests for ``main._resolve_creds`` fail-fast token retrieval
2
+ (forcedotcom/cli#3560).
4
3
 
5
- Path 1 (primary): ``sf org auth show-access-token --json --no-prompt``
6
- via the ``show_access_token`` recipe.
7
- Path 2 (fallback): ``sf org display`` payload with
8
- ``SF_TEMP_SHOW_SECRETS=true`` env var.
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 and subprocess env injection have their own coverage in
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 the two-path retrieval."""
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 test_primary_unknown_falls_back_to_display(self):
69
- """Older sf CLI without the dedicated command surfaces SfCliError;
70
- we fall back to the display payload's accessToken."""
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
- url, token = main._resolve_creds("my-org")
78
- self.assertEqual(url, "https://example.salesforce.com")
79
- self.assertEqual(token, "TOKEN_FROM_DISPLAY")
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 test_primary_returns_redacted_token_falls_back(self):
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
- primary_payload=_show_token_payload(access_token=REDACTED_TOKEN),
92
+ primary_exc=AuthRequired("NoOrgAuthenticationError"),
88
93
  ),
89
94
  ):
90
- _, token = main._resolve_creds("my-org")
91
- self.assertEqual(token, "TOKEN_FROM_DISPLAY")
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 test_primary_returns_empty_token_falls_back(self):
94
- """Empty string from the dedicated command also triggers fallback."""
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
- _, token = main._resolve_creds("my-org")
102
- self.assertEqual(token, "TOKEN_FROM_DISPLAY")
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 test_both_paths_redacted_raises_authrequired(self):
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=REDACTED_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("could not retrieve a usable access token", str(ctx.exception))
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 the env-var-only path on sf CLI versions that
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 test_passes_show_secrets_env_to_subprocess(self):
215
- """Tripwire for the W-22582511 sf-CLI-redaction fix.
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
- Without ``SF_TEMP_SHOW_SECRETS=true`` in the subprocess env, sf CLI
218
- v2 returns the literal string ``"[REDACTED] Use 'sf org auth
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
- self.assertIsNotNone(captured["env"])
236
- self.assertEqual(captured["env"].get("SF_TEMP_SHOW_SECRETS"), "true")
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 v2 omits ``accessToken`` from the
242
- ``--json`` output entirely (regardless of SF_TEMP_SHOW_SECRETS),
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()