@jskit-ai/connectors-catalog 0.1.1

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 (399) hide show
  1. package/README.md +192 -0
  2. package/docs/ai-upstream-notices.md +51 -0
  3. package/docs/ai.md +191 -0
  4. package/docs/airtable.md +130 -0
  5. package/docs/algolia.md +190 -0
  6. package/docs/amazon-redshift.md +229 -0
  7. package/docs/amplitude.md +210 -0
  8. package/docs/apify.md +162 -0
  9. package/docs/apollo-io.md +200 -0
  10. package/docs/asana.md +126 -0
  11. package/docs/ashby.md +188 -0
  12. package/docs/atlassian.md +224 -0
  13. package/docs/attention.md +162 -0
  14. package/docs/aws-athena.md +150 -0
  15. package/docs/aws-credentials.md +75 -0
  16. package/docs/aws-s3.md +193 -0
  17. package/docs/bigquery.md +146 -0
  18. package/docs/brevo.md +122 -0
  19. package/docs/calendly.md +158 -0
  20. package/docs/canva.md +191 -0
  21. package/docs/chargebee.md +192 -0
  22. package/docs/clay.md +194 -0
  23. package/docs/clickhouse.md +242 -0
  24. package/docs/confidence-exp.md +74 -0
  25. package/docs/confidence-flags.md +75 -0
  26. package/docs/confidence-oauth.md +137 -0
  27. package/docs/contentful.md +123 -0
  28. package/docs/databricks.md +180 -0
  29. package/docs/dbt-semantic-layer.md +175 -0
  30. package/docs/elevenlabs.md +102 -0
  31. package/docs/figma.md +132 -0
  32. package/docs/firebase-cloud-messaging.md +262 -0
  33. package/docs/firecrawl.md +137 -0
  34. package/docs/fireflies.md +95 -0
  35. package/docs/fireworks-ai.md +53 -0
  36. package/docs/gatewayapi.md +113 -0
  37. package/docs/gemini-enterprise.md +224 -0
  38. package/docs/github-api.md +133 -0
  39. package/docs/gitlab-api.md +138 -0
  40. package/docs/gmail.md +133 -0
  41. package/docs/gong.md +205 -0
  42. package/docs/google-ads.md +156 -0
  43. package/docs/google-analytics.md +202 -0
  44. package/docs/google-docs.md +105 -0
  45. package/docs/google-drive.md +108 -0
  46. package/docs/google-maps-platform.md +208 -0
  47. package/docs/google-oauth.md +123 -0
  48. package/docs/google-search-console.md +124 -0
  49. package/docs/google-sheets.md +94 -0
  50. package/docs/google-slides.md +95 -0
  51. package/docs/granola.md +178 -0
  52. package/docs/hex.md +161 -0
  53. package/docs/heygen.md +124 -0
  54. package/docs/hubspot.md +142 -0
  55. package/docs/incident-io.md +118 -0
  56. package/docs/inngest.md +178 -0
  57. package/docs/klipy.md +149 -0
  58. package/docs/lexware.md +144 -0
  59. package/docs/lightspeed.md +193 -0
  60. package/docs/linear.md +137 -0
  61. package/docs/linkedin.md +196 -0
  62. package/docs/logo-dev.md +152 -0
  63. package/docs/mailgun.md +153 -0
  64. package/docs/mapbox.md +174 -0
  65. package/docs/microsoft-excel.md +97 -0
  66. package/docs/microsoft-fabric.md +249 -0
  67. package/docs/microsoft-oauth.md +143 -0
  68. package/docs/microsoft-onedrive.md +76 -0
  69. package/docs/microsoft-onenote.md +75 -0
  70. package/docs/microsoft-outlook.md +90 -0
  71. package/docs/microsoft-powerpoint.md +119 -0
  72. package/docs/microsoft-sharepoint.md +75 -0
  73. package/docs/microsoft-teams.md +72 -0
  74. package/docs/microsoft-word.md +119 -0
  75. package/docs/miro.md +117 -0
  76. package/docs/n8n.md +169 -0
  77. package/docs/notion.md +137 -0
  78. package/docs/oura.md +161 -0
  79. package/docs/paddle.md +168 -0
  80. package/docs/perplexity.md +85 -0
  81. package/docs/pipedrive.md +120 -0
  82. package/docs/polar.md +63 -0
  83. package/docs/posthog.md +227 -0
  84. package/docs/prestashop.md +151 -0
  85. package/docs/replicate.md +60 -0
  86. package/docs/resend.md +153 -0
  87. package/docs/salesforce.md +234 -0
  88. package/docs/sanity.md +122 -0
  89. package/docs/semrush.md +276 -0
  90. package/docs/sentry.md +112 -0
  91. package/docs/sevdesk.md +107 -0
  92. package/docs/shopify.md +206 -0
  93. package/docs/slack.md +257 -0
  94. package/docs/snowflake.md +271 -0
  95. package/docs/storyblok.md +130 -0
  96. package/docs/stripe.md +77 -0
  97. package/docs/supabase.md +51 -0
  98. package/docs/tally.md +105 -0
  99. package/docs/telegram.md +165 -0
  100. package/docs/tiktok.md +201 -0
  101. package/docs/twilio.md +284 -0
  102. package/docs/twitch.md +428 -0
  103. package/docs/wave.md +440 -0
  104. package/docs/wix.md +1249 -0
  105. package/docs/wiz.md +177 -0
  106. package/docs/woocommerce.md +400 -0
  107. package/docs/wordpress-com.md +358 -0
  108. package/docs/wordpress-self-hosted.md +252 -0
  109. package/docs/workday.md +292 -0
  110. package/docs/x-twitter.md +188 -0
  111. package/docs/xero.md +429 -0
  112. package/docs/zoho-books.md +214 -0
  113. package/docs/zoho-crm.md +192 -0
  114. package/package.json +153 -0
  115. package/patterns/ai-connections/PATTERN.md +84 -0
  116. package/patterns/ai-connections/example/ai-model.js +11 -0
  117. package/patterns/ai-connections/example/perplexity-answer.js +29 -0
  118. package/patterns/api-key-connection/PATTERN.md +368 -0
  119. package/patterns/api-key-connection/example/connections.js +18 -0
  120. package/patterns/api-key-connection/example/integrations.json +19 -0
  121. package/patterns/assistant-mcp/PATTERN.md +110 -0
  122. package/patterns/assistant-mcp/example/integrations.json +13 -0
  123. package/patterns/assistant-mcp-oauth/PATTERN.md +235 -0
  124. package/patterns/assistant-mcp-oauth/example/integrations.json +102 -0
  125. package/patterns/aws-storage-queries/PATTERN.md +169 -0
  126. package/patterns/aws-storage-queries/example/formats/data-formats.js +35 -0
  127. package/patterns/aws-storage-queries/example/formats/package-lock.json +49 -0
  128. package/patterns/aws-storage-queries/example/formats/package.json +12 -0
  129. package/patterns/aws-storage-queries/example/formats/verify-formats.mjs +37 -0
  130. package/patterns/aws-storage-queries/example/integrations.json +38 -0
  131. package/patterns/aws-storage-queries/example/s3-transfer.js +34 -0
  132. package/patterns/event-delivery/PATTERN.md +151 -0
  133. package/patterns/event-delivery/example/integrations.json +17 -0
  134. package/patterns/firebase-messaging/PATTERN.md +156 -0
  135. package/patterns/firebase-messaging/example/server/notifications.js +23 -0
  136. package/patterns/google-ads-search/PATTERN.md +92 -0
  137. package/patterns/google-ads-search/example/ads-setup.js +25 -0
  138. package/patterns/oauth-connection/PATTERN.md +724 -0
  139. package/patterns/oauth-connection/example/integrations.json +20 -0
  140. package/patterns/paddle-catalogue/PATTERN.md +78 -0
  141. package/patterns/paddle-catalogue/example/create-products.js +38 -0
  142. package/patterns/public-image/PATTERN.md +84 -0
  143. package/patterns/public-image/example/integrations.json +13 -0
  144. package/patterns/public-image/example/logo-url.js +16 -0
  145. package/patterns/redshift-queries/PATTERN.md +150 -0
  146. package/patterns/redshift-queries/example/integrations.json +33 -0
  147. package/patterns/source-scanning/PATTERN.md +83 -0
  148. package/patterns/source-scanning/example/source-scanner.js +13 -0
  149. package/src/client/firebase-cloud-messaging.js +10 -0
  150. package/src/client/google-analytics.js +72 -0
  151. package/src/client/google-maps-platform.js +39 -0
  152. package/src/client/logo-dev.js +36 -0
  153. package/src/server/ai.js +46 -0
  154. package/src/server/airtable.js +67 -0
  155. package/src/server/algolia.js +63 -0
  156. package/src/server/amazon-redshift.js +96 -0
  157. package/src/server/amplitude.js +31 -0
  158. package/src/server/apify.js +68 -0
  159. package/src/server/apollo-io.js +85 -0
  160. package/src/server/asana.js +64 -0
  161. package/src/server/ashby.js +61 -0
  162. package/src/server/atlassian.js +32 -0
  163. package/src/server/attention.js +93 -0
  164. package/src/server/aws-athena.js +82 -0
  165. package/src/server/aws-s3.js +49 -0
  166. package/src/server/awsTransport.js +75 -0
  167. package/src/server/bigquery.js +97 -0
  168. package/src/server/brevo.js +97 -0
  169. package/src/server/calendly.js +77 -0
  170. package/src/server/canva.js +13 -0
  171. package/src/server/chargebee.js +88 -0
  172. package/src/server/clay.js +78 -0
  173. package/src/server/clickhouse.js +66 -0
  174. package/src/server/confidence.js +29 -0
  175. package/src/server/contentful.js +40 -0
  176. package/src/server/databricks.js +55 -0
  177. package/src/server/dbt-semantic-layer.js +95 -0
  178. package/src/server/elevenlabs.js +81 -0
  179. package/src/server/figma.js +24 -0
  180. package/src/server/firebase-cloud-messaging.js +111 -0
  181. package/src/server/firecrawl.js +70 -0
  182. package/src/server/fireflies.js +31 -0
  183. package/src/server/fireworks-ai.js +16 -0
  184. package/src/server/gatewayapi.js +48 -0
  185. package/src/server/gemini-enterprise.js +47 -0
  186. package/src/server/github-api.js +90 -0
  187. package/src/server/gitlab-api.js +67 -0
  188. package/src/server/gmail.js +60 -0
  189. package/src/server/gong.js +52 -0
  190. package/src/server/google-ads-search.js +134 -0
  191. package/src/server/google-ads.js +80 -0
  192. package/src/server/google-docs.js +36 -0
  193. package/src/server/google-drive.js +71 -0
  194. package/src/server/google-maps-platform.js +90 -0
  195. package/src/server/google-search-console.js +31 -0
  196. package/src/server/google-sheets.js +60 -0
  197. package/src/server/google-slides.js +36 -0
  198. package/src/server/google.js +61 -0
  199. package/src/server/granola.js +115 -0
  200. package/src/server/graphqlOperation.js +15 -0
  201. package/src/server/hex.js +29 -0
  202. package/src/server/heygen.js +54 -0
  203. package/src/server/hubspot.js +85 -0
  204. package/src/server/incident-io.js +68 -0
  205. package/src/server/inngest.js +86 -0
  206. package/src/server/jsonOperation.js +34 -0
  207. package/src/server/klipy.js +34 -0
  208. package/src/server/lexware.js +59 -0
  209. package/src/server/lightspeed.js +82 -0
  210. package/src/server/linear.js +52 -0
  211. package/src/server/linkedin.js +74 -0
  212. package/src/server/mailgun.js +76 -0
  213. package/src/server/mapbox.js +46 -0
  214. package/src/server/mcpRegistration.js +41 -0
  215. package/src/server/mcpTransport.js +60 -0
  216. package/src/server/microsoft-excel.js +67 -0
  217. package/src/server/microsoft-fabric.js +81 -0
  218. package/src/server/microsoft-onedrive.js +58 -0
  219. package/src/server/microsoft-onenote.js +52 -0
  220. package/src/server/microsoft-outlook.js +58 -0
  221. package/src/server/microsoft-powerpoint.js +5 -0
  222. package/src/server/microsoft-sharepoint.js +46 -0
  223. package/src/server/microsoft-teams.js +29 -0
  224. package/src/server/microsoft-word.js +5 -0
  225. package/src/server/microsoft.js +88 -0
  226. package/src/server/miro.js +29 -0
  227. package/src/server/n8n.js +78 -0
  228. package/src/server/notion.js +76 -0
  229. package/src/server/oura.js +48 -0
  230. package/src/server/paddle.js +83 -0
  231. package/src/server/perplexity.js +15 -0
  232. package/src/server/pipedrive.js +95 -0
  233. package/src/server/polar.js +20 -0
  234. package/src/server/posthog.js +44 -0
  235. package/src/server/prestashop.js +42 -0
  236. package/src/server/replicate.js +42 -0
  237. package/src/server/resend.js +93 -0
  238. package/src/server/salesforce.js +84 -0
  239. package/src/server/sanity.js +31 -0
  240. package/src/server/semrush.js +359 -0
  241. package/src/server/sentry.js +30 -0
  242. package/src/server/sevdesk.js +20 -0
  243. package/src/server/shopify.js +99 -0
  244. package/src/server/slack.js +143 -0
  245. package/src/server/snowflake.js +139 -0
  246. package/src/server/storyblok.js +49 -0
  247. package/src/server/stripe.js +13 -0
  248. package/src/server/supabase.js +13 -0
  249. package/src/server/tally.js +78 -0
  250. package/src/server/telegram.js +55 -0
  251. package/src/server/tiktok.js +86 -0
  252. package/src/server/twilio.js +90 -0
  253. package/src/server/twitch.js +409 -0
  254. package/src/server/wave.js +520 -0
  255. package/src/server/wix.js +1048 -0
  256. package/src/server/wiz.js +117 -0
  257. package/src/server/woocommerce.js +412 -0
  258. package/src/server/wordpress-com.js +236 -0
  259. package/src/server/wordpress-self-hosted.js +134 -0
  260. package/src/server/workday.js +136 -0
  261. package/src/server/x-twitter.js +110 -0
  262. package/src/server/xero.js +397 -0
  263. package/src/server/zoho-books.js +90 -0
  264. package/src/server/zoho-crm.js +89 -0
  265. package/src/server/zohoOAuth.js +21 -0
  266. package/src/shared/ai-models.json +8049 -0
  267. package/src/shared/ai.js +63 -0
  268. package/src/shared/algolia.js +29 -0
  269. package/src/shared/amazon-redshift.js +51 -0
  270. package/src/shared/amplitude.js +28 -0
  271. package/src/shared/atlassian.js +43 -0
  272. package/src/shared/aws.js +72 -0
  273. package/src/shared/canva.js +66 -0
  274. package/src/shared/chargebee.js +26 -0
  275. package/src/shared/clickhouse.js +38 -0
  276. package/src/shared/confidence.js +28 -0
  277. package/src/shared/databricks.js +33 -0
  278. package/src/shared/dbt-semantic-layer.js +32 -0
  279. package/src/shared/definitions.js +140 -0
  280. package/src/shared/figma.js +21 -0
  281. package/src/shared/firebase-cloud-messaging.js +45 -0
  282. package/src/shared/gemini-enterprise.js +33 -0
  283. package/src/shared/gong.js +33 -0
  284. package/src/shared/google-ads-search.js +31 -0
  285. package/src/shared/google-ads.js +28 -0
  286. package/src/shared/google-maps-platform.js +28 -0
  287. package/src/shared/google.js +133 -0
  288. package/src/shared/granola.js +25 -0
  289. package/src/shared/hex.js +28 -0
  290. package/src/shared/inngest.js +35 -0
  291. package/src/shared/lightspeed.js +75 -0
  292. package/src/shared/linkedin.js +27 -0
  293. package/src/shared/logo-dev.js +21 -0
  294. package/src/shared/mapbox.js +38 -0
  295. package/src/shared/mcp.js +92 -0
  296. package/src/shared/microsoft-fabric.js +47 -0
  297. package/src/shared/microsoft.js +231 -0
  298. package/src/shared/miro.js +27 -0
  299. package/src/shared/oura.js +23 -0
  300. package/src/shared/posthog.js +33 -0
  301. package/src/shared/prestashop.js +23 -0
  302. package/src/shared/salesforce.js +36 -0
  303. package/src/shared/semrush.js +24 -0
  304. package/src/shared/shopify.js +51 -0
  305. package/src/shared/siteUrl.js +12 -0
  306. package/src/shared/slack.js +88 -0
  307. package/src/shared/snowflake.js +55 -0
  308. package/src/shared/tiktok.js +22 -0
  309. package/src/shared/tokens.js +747 -0
  310. package/src/shared/twilio.js +41 -0
  311. package/src/shared/twitch.js +49 -0
  312. package/src/shared/wave.js +52 -0
  313. package/src/shared/wix.js +40 -0
  314. package/src/shared/wiz.js +41 -0
  315. package/src/shared/wordpress-com.js +30 -0
  316. package/src/shared/wordpress.js +69 -0
  317. package/src/shared/workday.js +50 -0
  318. package/src/shared/x-twitter.js +19 -0
  319. package/src/shared/xero.js +46 -0
  320. package/src/shared/zoho-books.js +39 -0
  321. package/src/shared/zoho-crm.js +43 -0
  322. package/src/shared/zoho-regions.js +12 -0
  323. package/test/ai.test.js +136 -0
  324. package/test/airtable.test.js +125 -0
  325. package/test/algolia.test.js +207 -0
  326. package/test/amazon-redshift.test.js +272 -0
  327. package/test/amplitude.test.js +239 -0
  328. package/test/apify.test.js +93 -0
  329. package/test/apollo-io.test.js +125 -0
  330. package/test/asana.test.js +99 -0
  331. package/test/ashby.test.js +97 -0
  332. package/test/atlassian.test.js +249 -0
  333. package/test/attention.test.js +79 -0
  334. package/test/aws.test.js +405 -0
  335. package/test/brevo.test.js +71 -0
  336. package/test/calendly.test.js +136 -0
  337. package/test/canva.test.js +258 -0
  338. package/test/chargebee.test.js +195 -0
  339. package/test/clickhouse.test.js +278 -0
  340. package/test/confidence.test.js +205 -0
  341. package/test/databricks.test.js +253 -0
  342. package/test/dbt-semantic-layer.test.js +221 -0
  343. package/test/designMcp.test.js +336 -0
  344. package/test/elevenlabs.test.js +52 -0
  345. package/test/firebase-cloud-messaging.test.js +242 -0
  346. package/test/firecrawl.test.js +66 -0
  347. package/test/gemini-enterprise.test.js +218 -0
  348. package/test/github-api.test.js +167 -0
  349. package/test/gitlab-api.test.js +160 -0
  350. package/test/gong.test.js +192 -0
  351. package/test/google-ads.test.js +350 -0
  352. package/test/google-maps-platform.test.js +191 -0
  353. package/test/google.test.js +710 -0
  354. package/test/granola.test.js +240 -0
  355. package/test/hex.test.js +253 -0
  356. package/test/heygen.test.js +121 -0
  357. package/test/hubspot.test.js +103 -0
  358. package/test/inngest.test.js +219 -0
  359. package/test/lightspeed.test.js +250 -0
  360. package/test/linear.test.js +146 -0
  361. package/test/linkedin.test.js +243 -0
  362. package/test/logo-dev.test.js +79 -0
  363. package/test/mailgun.test.js +107 -0
  364. package/test/mapbox.test.js +181 -0
  365. package/test/mcp.test.js +227 -0
  366. package/test/microsoft-fabric.test.js +258 -0
  367. package/test/microsoft.test.js +554 -0
  368. package/test/n8nOAuth.test.js +188 -0
  369. package/test/notionOAuth.test.js +183 -0
  370. package/test/oura.test.js +211 -0
  371. package/test/paddleCatalogue.test.js +51 -0
  372. package/test/paddlePermissions.test.js +101 -0
  373. package/test/perplexityComposition.test.js +39 -0
  374. package/test/pipedriveOAuth.test.js +140 -0
  375. package/test/posthog.test.js +200 -0
  376. package/test/prestashop.test.js +207 -0
  377. package/test/resend.test.js +163 -0
  378. package/test/salesforce.test.js +276 -0
  379. package/test/semrush.test.js +479 -0
  380. package/test/sentry.test.js +35 -0
  381. package/test/shopify.test.js +247 -0
  382. package/test/slack.test.js +320 -0
  383. package/test/snowflake.test.js +391 -0
  384. package/test/tally.test.js +75 -0
  385. package/test/telegram.test.js +58 -0
  386. package/test/tiktok.test.js +243 -0
  387. package/test/tokens.test.js +1079 -0
  388. package/test/twilio.test.js +221 -0
  389. package/test/twitch.test.js +573 -0
  390. package/test/wave.test.js +678 -0
  391. package/test/wix.test.js +1099 -0
  392. package/test/wiz.test.js +185 -0
  393. package/test/wordpress-com.test.js +469 -0
  394. package/test/wordpress.test.js +660 -0
  395. package/test/workday.test.js +314 -0
  396. package/test/x-twitter.test.js +256 -0
  397. package/test/xero.test.js +700 -0
  398. package/test/zoho-books.test.js +253 -0
  399. package/test/zoho-crm.test.js +223 -0
@@ -0,0 +1,276 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+ import { createConnectionService } from "../../connectors-core/src/server/index.js";
7
+ import { createFileConnectionStore, createCredentialProtection } from "../../connectors-core/src/server/fileStorage.js";
8
+ import { validateIntegrationConfiguration } from "../../connectors-core/src/shared/configuration.js";
9
+ import { salesforceProvider as provider } from "../src/server/salesforce.js";
10
+
11
+ const context = { applicationId: "crm-app", subjectId: "user-one" };
12
+ const input = { context, integrationId: "crm" };
13
+ const accountUrl = "https://fixture.my.salesforce.com";
14
+ const callback = "https://app.example.test/connections/salesforce/callback";
15
+ const limits = { DailyApiRequests: { Max: 100000, Remaining: 75000 }, DailyBulkApiRequests: { Max: 15000, Remaining: 15000 } };
16
+ const cursor = "/services/data/v66.0/query/0r82211CelDTLqEAEX-2000";
17
+ const page = { totalSize: 1, done: true, records: [{ attributes: { type: "Account", url: "/services/data/v66.0/sobjects/Account/001000000000001AAA" }, Id: "001000000000001AAA", Name: "Fixture" }] };
18
+
19
+ async function fixture(t, settings = { environment: "production", accountUrl }) {
20
+ const directory = await mkdtemp(path.join(tmpdir(), "salesforce-connector-"));
21
+ t.after(() => rm(directory, { recursive: true, force: true }));
22
+ const origin = new URL(settings.accountUrl).origin;
23
+ const state = { time: Date.now(), grants: 0, requests: [], tokenStatus: 200, status: 200, scope: "api refresh_token", refresh: true,
24
+ tokenOrigin: origin, response: page, secret: "fixture-secret", deny: false, stall: false };
25
+ const protection = createCredentialProtection({ keys: { current: new Uint8Array(32).fill(7) }, activeKeyId: "current" });
26
+ const configuration = { schemaVersion: 1, integrations: { crm: { provider: "salesforce", accountMode: "per-user", settings,
27
+ scopes: ["api", "refresh_token"], authentication: { method: "oauth2", registrationRef: "sf" } } },
28
+ registrations: { sf: { source: "own", clientId: "fixture-client", clientSecretRef: "env:SF_SECRET", callbackUrlRef: "env:SF_CALLBACK" } } };
29
+ const options = { configuration, providers: [provider], store: createFileConnectionStore({ directory, protection }), now: () => state.time,
30
+ authorize: async (owner) => { if (state.deny) throw new Error("Host denied"); return owner; },
31
+ resolveReference: async (ref) => { if (ref === "env:SF_CALLBACK") return callback; if (ref === "env:SF_SECRET") return state.secret; throw new Error("Unknown reference"); },
32
+ fetchImpl: async (address, init) => {
33
+ const url = new URL(String(address)); const headers = new Headers(init.headers);
34
+ state.requests.push({ url, init, headers });
35
+ assert.equal(url.origin, origin);
36
+ const token = url.pathname === "/services/oauth2/token";
37
+ if (state.stall === true || state.stall === (token ? "token" : "api")) return new Promise((resolve, reject) => {
38
+ init.signal.throwIfAborted(); init.signal.addEventListener("abort", () => reject(init.signal.reason), { once: true });
39
+ });
40
+ if (token) {
41
+ assert.equal(init.redirect, "manual"); assert.equal(headers.has("authorization"), false);
42
+ const body = new URLSearchParams(init.body);
43
+ assert.equal(body.get("client_id"), "fixture-client"); assert.equal(body.get("client_secret"), state.secret);
44
+ if (body.get("grant_type") === "refresh_token") assert.equal(body.get("refresh_token"), state.lastRefresh);
45
+ if (state.tokenStatus !== 200) return Response.json({ error: "invalid_grant", error_description: "fixture-secret" }, { status: state.tokenStatus });
46
+ state.grants += 1;
47
+ if (state.badToken) return Response.json(state.badToken);
48
+ if (state.refresh) state.lastRefresh = `refresh-${state.grants}`;
49
+ return Response.json({ access_token: `access-${state.grants}`, token_type: "Bearer", instance_url: state.tokenOrigin,
50
+ scope: state.scope, ...(state.refresh ? { refresh_token: state.lastRefresh } : {}), ...(state.expiry === undefined ? {} : { expires_in: state.expiry }) });
51
+ }
52
+ assert.equal(init.method, "GET"); assert.equal(init.redirect, "error"); assert.equal(init.credentials, "omit");
53
+ assert.equal(headers.get("authorization"), `Bearer access-${state.grants}`); assert.equal(init.body, undefined);
54
+ if (state.status !== 200) return new Response(state.raw || JSON.stringify([{ errorCode: state.errorCode || "FIXTURE_ERROR", message: "fixture-secret" }]), { status: state.status });
55
+ if (state.raw) return new Response(state.raw, { headers: { "Content-Type": "application/json" } });
56
+ return Response.json(url.pathname.endsWith("/limits") ? (state.limits || limits) : state.response);
57
+ }
58
+ };
59
+ const service = createConnectionService(options);
60
+ const start = async (target = service) => {
61
+ const result = await target.beginAuthorization(input); const url = new URL(result.authorizationUrl);
62
+ const returned = new URL(callback); returned.searchParams.set("code", "fixture-code"); returned.searchParams.set("state", url.searchParams.get("state"));
63
+ return { url, callbackUrl: returned.href };
64
+ };
65
+ const connect = async (target = service) => target.completeAuthorization({ ...input, callbackUrl: (await start(target)).callbackUrl });
66
+ return { options, service, state, directory, protection, configuration, start, connect };
67
+ }
68
+
69
+ test("Salesforce shares production/developer/sandbox configuration validation with CLI and rejects credentials as references", async (t) => {
70
+ const f = await fixture(t);
71
+ for (const [environment, url] of [["production", accountUrl], ["production", "https://fixture.develop.my.salesforce.com/"], ["sandbox", "https://fixture--uat.sandbox.my.salesforce.com"]]) {
72
+ for (const accountMode of provider.accountModes) {
73
+ const config = structuredClone(f.configuration); config.integrations.crm.settings = { environment, accountUrl: url }; config.integrations.crm.accountMode = accountMode;
74
+ assert.deepEqual(validateIntegrationConfiguration(config, { providers: [provider] }), config);
75
+ }
76
+ }
77
+ for (const url of ["https://login.salesforce.com", "https://test.salesforce.com", "http://fixture.my.salesforce.com", "https://fixture.my.salesforce.com.evil.test", "https://fixture.my.salesforce.com:443",
78
+ "https://fixture.my.salesforce.com/path", "https://fixture.my.salesforce.com?x=1", "https://user@fixture.my.salesforce.com", "https://FIXTURE.my.salesforce.com", " https://fixture.my.salesforce.com", "https://fixture.sandbox.my.salesforce.com"]) {
79
+ const config = structuredClone(f.configuration); config.integrations.crm.settings.accountUrl = url;
80
+ assert.throws(() => validateIntegrationConfiguration(config, { providers: [provider] }), undefined, url);
81
+ }
82
+ const sandbox = structuredClone(f.configuration); sandbox.integrations.crm.settings.environment = "sandbox";
83
+ assert.throws(() => validateIntegrationConfiguration(sandbox, { providers: [provider] }));
84
+ for (const field of ["clientSecretRef", "callbackUrlRef"]) for (const value of ["literal-secret", "https://paste.invalid", ""]) {
85
+ const config = structuredClone(f.configuration); config.registrations.sf[field] = value;
86
+ assert.throws(() => validateIntegrationConfiguration(config, { providers: [provider] }));
87
+ }
88
+ });
89
+
90
+ test("Salesforce pins My Domain for PKCE consent, secret-post exchange and API verification in both environments", async (t) => {
91
+ for (const settings of [{ environment: "production", accountUrl }, { environment: "sandbox", accountUrl: "https://fixture--uat.sandbox.my.salesforce.com/" }]) {
92
+ const f = await fixture(t, settings); const { url, callbackUrl } = await f.start();
93
+ const origin = new URL(settings.accountUrl).origin;
94
+ assert.equal(url.origin + url.pathname, origin + "/services/oauth2/authorize");
95
+ assert.equal(url.searchParams.get("code_challenge_method"), "S256"); assert.equal(url.searchParams.get("redirect_uri"), callback);
96
+ assert.equal(url.searchParams.get("scope"), "api refresh_token"); assert.equal(url.searchParams.get("response_type"), "code");
97
+ assert.equal(url.searchParams.has("client_secret"), false); assert.equal(url.searchParams.has("openid"), false);
98
+ f.state.tokenOrigin += "/";
99
+ const connection = await f.service.completeAuthorization({ ...input, callbackUrl });
100
+ assert.equal(connection.status, "connected");
101
+ const body = new URLSearchParams(f.state.requests[0].init.body);
102
+ assert.equal(body.get("code"), "fixture-code"); assert.ok(body.get("code_verifier")); assert.equal(body.get("redirect_uri"), callback);
103
+ assert.equal(f.state.requests[1].url.href, origin + "/services/data/v66.0/limits");
104
+ await assert.rejects(f.service.completeAuthorization({ ...input, callbackUrl }), { code: "connector_attempt_invalid" });
105
+ assert.equal(f.state.requests.length, 2);
106
+ }
107
+ });
108
+
109
+ test("Salesforce exposes bounded SOQL reads, strict next-page destinations and object discovery", async (t) => {
110
+ const f = await fixture(t); await f.connect();
111
+ const q = "SELECT Id, Name FROM Account WHERE Name = 'ACME & Sons + 😀' ORDER BY Id LIMIT 10";
112
+ assert.deepEqual(await f.service.invoke({ ...input, operation: "query.read", input: { q } }), page);
113
+ assert.equal(f.state.requests.at(-1).url.searchParams.get("q"), q);
114
+ f.state.response = { ...page, totalSize: 2001, done: false, nextRecordsUrl: cursor };
115
+ assert.equal((await f.service.invoke({ ...input, operation: "query.read", input: { q } })).nextRecordsUrl, cursor);
116
+ f.state.response = { totalSize: 2001, done: true, records: [{ expr0: 3 }] };
117
+ await f.service.invoke({ ...input, operation: "query.next", input: { nextRecordsUrl: cursor } });
118
+ assert.equal(f.state.requests.at(-1).url.href, accountUrl + cursor);
119
+ f.state.response = { sobjects: [{ name: "ns__Widget__c", label: "Widgets", queryable: true }] };
120
+ await f.service.invoke({ ...input, operation: "objects.list" });
121
+ f.state.response = { name: "ns__Widget__c", fields: [{ name: "Name", type: "string" }] };
122
+ await f.service.invoke({ ...input, operation: "objects.describe", input: { object: "ns__Widget__c" } });
123
+ assert.equal(f.state.requests.at(-1).url.pathname, "/services/data/v66.0/sobjects/ns__Widget__c/describe");
124
+ f.state.response = { totalSize: 0, done: true, records: [] };
125
+ assert.deepEqual(await f.service.invoke({ ...input, operation: "query.read", input: { q: "SELECT Id FROM Account LIMIT 1" } }), f.state.response);
126
+ });
127
+
128
+ test("Salesforce rejects unsupported query effects, Unicode size, cursors and arbitrary transport inputs before requests", async (t) => {
129
+ const f = await fixture(t); await f.connect(); const count = f.state.requests.length;
130
+ for (const q of ["", "DELETE FROM Account", "SELECT Id FROM Account FOR UPDATE", "SELECT Id FROM Account FOR VIEW", "SELECT Id FROM Account FOR REFERENCE", "SELECT\nId FROM Account", "SELECT " + "x".repeat(2000), "SELECT '" + "界".repeat(1100) + "' FROM Account"]) {
131
+ await assert.rejects(f.service.invoke({ ...input, operation: "query.read", input: { q } }), { code: "connector_input_invalid" });
132
+ }
133
+ for (const nextRecordsUrl of ["https://evil.invalid" + cursor, "//evil.invalid" + cursor, cursor + "?x=1", cursor.replace("v66.0", "v65.0"), cursor.replace("/query/", "/queryAll/"), cursor + "/../limits", "%2fservices/data", ""]) {
134
+ await assert.rejects(f.service.invoke({ ...input, operation: "query.next", input: { nextRecordsUrl } }), { code: "connector_input_invalid" });
135
+ }
136
+ for (const object of ["Account/../../limits", "Name,Id", "", "_bad"]) await assert.rejects(f.service.invoke({ ...input, operation: "objects.describe", input: { object } }), { code: "connector_input_invalid" });
137
+ await assert.rejects(f.service.invoke({ ...input, operation: "query.read", input: { q: "SELECT Id FROM Account", headers: { Authorization: "x" } } }), { code: "connector_input_invalid" });
138
+ await assert.rejects(f.service.invoke({ ...input, operation: "limits.read", input: { url: "https://evil.invalid" } }), { code: "connector_input_invalid" });
139
+ assert.equal(f.state.requests.length, count);
140
+ });
141
+
142
+ test("Salesforce encrypted file restart preserves ownership and renews an undated grant under one lock", async (t) => {
143
+ const f = await fixture(t); await f.connect();
144
+ for (const name of await readdir(f.directory)) {
145
+ const value = await readFile(path.join(f.directory, name), "utf8");
146
+ for (const secret of ["fixture-secret", "access-1", "refresh-1"]) assert.equal(value.includes(secret), false);
147
+ }
148
+ const restarted = createConnectionService({ ...f.options, store: createFileConnectionStore({ directory: f.directory, protection: f.protection }) });
149
+ await restarted.invoke({ ...input, operation: "limits.read" }); assert.equal(f.state.grants, 1);
150
+ for (const owner of [{ ...context, subjectId: "user-two" }, { ...context, applicationId: "other-app" }]) {
151
+ await assert.rejects(restarted.invoke({ ...input, context: owner, operation: "limits.read" }), { code: "connector_reconnect_required" });
152
+ }
153
+ f.state.time += 300000; f.state.secret = "rotated-secret";
154
+ await Promise.all([restarted.invoke({ ...input, operation: "limits.read" }), f.service.invoke({ ...input, operation: "limits.read" })]);
155
+ assert.equal(f.state.grants, 2);
156
+ const count = f.state.requests.length; await restarted.disconnect(input);
157
+ assert.equal(f.state.requests.length, count); assert.equal((await f.service.status(input)).status, "disconnected");
158
+ });
159
+
160
+ test("Salesforce persists rotation before later failures and retains a previous refresh token when none is returned", async (t) => {
161
+ const f = await fixture(t); f.state.expiry = 60; await f.connect(); f.state.time += 60000;
162
+ f.state.refresh = false; f.state.status = 403; f.state.errorCode = "REQUEST_LIMIT_EXCEEDED";
163
+ await assert.rejects(f.service.invoke({ ...input, operation: "limits.read" }), { code: "connector_rate_limited" });
164
+ assert.equal(f.state.grants, 2); f.state.status = 200; f.state.time += 60000; f.state.refresh = true;
165
+ await createConnectionService(f.options).invoke({ ...input, operation: "limits.read" }); assert.equal(f.state.grants, 3);
166
+ f.state.time += 60000; f.state.scope = "refresh_token";
167
+ await assert.rejects(f.service.invoke({ ...input, operation: "limits.read" }), { code: "connector_scope_missing" });
168
+ assert.deepEqual((await f.service.status(input)).grantedScopes, ["refresh_token"]);
169
+ });
170
+
171
+ test("Salesforce rejects foreign and malformed grants, missing API consent and changed org/client configuration", async (t) => {
172
+ const f = await fixture(t);
173
+ for (const tokenOrigin of ["https://other.my.salesforce.com", accountUrl + "/path", "https://evil.invalid", "https://login.salesforce.com"]) {
174
+ f.state.tokenOrigin = tokenOrigin; await assert.rejects(f.connect(), { code: "connector_response_invalid" });
175
+ assert.equal((await f.service.status(input)).status, "disconnected");
176
+ }
177
+ f.state.tokenOrigin = accountUrl; f.state.badToken = { instance_url: accountUrl, access_token: "bad", token_type: "Bearer" };
178
+ await assert.rejects(f.connect(), { code: "connector_response_invalid" }); delete f.state.badToken;
179
+ f.state.scope = "refresh_token"; await assert.rejects(f.connect(), { code: "connector_scope_missing" });
180
+ f.state.scope = "api refresh_token"; await f.connect(); const pending = await f.start(); const count = f.state.requests.length;
181
+ for (const field of ["domain", "environment", "client"]) {
182
+ const config = structuredClone(f.configuration);
183
+ if (field === "client") config.registrations.sf.clientId = "different-client";
184
+ else config.integrations.crm.settings = field === "domain" ? { environment: "production", accountUrl: "https://other.my.salesforce.com" }
185
+ : { environment: "sandbox", accountUrl: "https://fixture--uat.sandbox.my.salesforce.com" };
186
+ const changed = createConnectionService({ ...f.options, configuration: config });
187
+ assert.equal((await changed.status(input)).status, "reconnect-required");
188
+ await assert.rejects(changed.completeAuthorization({ ...input, callbackUrl: pending.callbackUrl }), { code: "connector_attempt_invalid" });
189
+ await assert.rejects(changed.invoke({ ...input, operation: "limits.read" }), { code: "connector_reconnect_required" });
190
+ }
191
+ assert.equal(f.state.requests.length, count);
192
+ });
193
+
194
+ test("Salesforce malformed pages, cursors, object names and limits cannot become successful responses", async (t) => {
195
+ const f = await fixture(t); await f.connect();
196
+ for (const response of [{}, { ...page, totalSize: -1 }, { ...page, totalSize: 0 }, { ...page, done: false }, { ...page, records: [null] },
197
+ { ...page, totalSize: 3000, records: Array(2001).fill({}) }, { ...page, done: false, nextRecordsUrl: "https://evil.invalid" + cursor }, { ...page, nextRecordsUrl: cursor }]) {
198
+ f.state.response = response;
199
+ await assert.rejects(f.service.invoke({ ...input, operation: "query.read", input: { q: "SELECT Id FROM Account" } }), { code: "connector_response_invalid" });
200
+ }
201
+ f.state.response = { name: "Other", fields: [] };
202
+ await assert.rejects(f.service.invoke({ ...input, operation: "objects.describe", input: { object: "Account" } }), { code: "connector_response_invalid" });
203
+ f.state.limits = { DailyApiRequests: { Max: 100, Remaining: -1 } };
204
+ await assert.rejects(f.service.invoke({ ...input, operation: "limits.read" }), { code: "connector_response_invalid" });
205
+ f.state.raw = "not-json";
206
+ await assert.rejects(f.service.invoke({ ...input, operation: "objects.list" }), { code: "connector_response_invalid" });
207
+ });
208
+
209
+ test("Salesforce maps org limits, expired cursors and provider failures without exposing responses or retrying", async (t) => {
210
+ const f = await fixture(t); await f.connect();
211
+ for (const [status, errorCode, code] of [[403, "REQUEST_LIMIT_EXCEEDED", "connector_rate_limited"], [400, "INVALID_QUERY_LOCATOR", "connector_cursor_expired"],
212
+ [400, "MALFORMED_QUERY", "connector_query_invalid"], [404, "NOT_FOUND", "connector_resource_not_found"], [403, "API_DISABLED_FOR_ORG", "connector_permission_denied"],
213
+ [429, "OTHER", "connector_rate_limited"], [500, "OTHER", "connector_provider_failed"], [302, "OTHER", "connector_provider_failed"], [401, "INVALID_SESSION_ID", "connector_reconnect_required"]]) {
214
+ Object.assign(f.state, { status, errorCode }); const count = f.state.requests.length;
215
+ await assert.rejects(f.service.invoke({ ...input, operation: "query.read", input: { q: "SELECT Id FROM Account" } }), (error) => {
216
+ assert.equal(error.code, code); assert.equal(JSON.stringify(error).includes("fixture-secret"), false); return true;
217
+ });
218
+ assert.equal(f.state.requests.length, count + 1);
219
+ }
220
+ assert.equal((await f.service.status(input)).status, "reconnect-required");
221
+ });
222
+
223
+ test("Salesforce host policy, cancelled/denied consent and managed references cannot silently connect", async (t) => {
224
+ const f = await fixture(t); let pending = await f.start();
225
+ await f.service.cancelAuthorization({ ...input, state: pending.url.searchParams.get("state") });
226
+ await assert.rejects(f.service.completeAuthorization({ ...input, callbackUrl: pending.callbackUrl }), { code: "connector_attempt_invalid" });
227
+ pending = await f.start(); const denied = new URL(pending.callbackUrl); denied.searchParams.delete("code"); denied.searchParams.set("error", "access_denied");
228
+ await assert.rejects(f.service.completeAuthorization({ ...input, callbackUrl: denied.href }), { code: "connector_consent_denied" });
229
+ f.state.deny = true; await assert.rejects(f.service.beginAuthorization(input), /Host denied/);
230
+ assert.equal(f.state.requests.length, 0);
231
+ f.state.deny = false;
232
+ const config = structuredClone(f.configuration); config.registrations.sf = { source: "managed", serviceUrlRef: "env:GATEWAY", serviceCredentialRef: "env:APP", assignmentRef: "online" };
233
+ assert.throws(() => createConnectionService({ ...f.options, configuration: config }), { code: "integration_configuration_invalid" });
234
+ assert.equal(f.state.requests.length, 0);
235
+ });
236
+
237
+ test("Salesforce cancellation and timeout interrupt requests and a revoked refresh becomes reconnect-required", async (t) => {
238
+ const f = await fixture(t); await f.connect(); f.state.stall = "api";
239
+ const timed = createConnectionService({ ...f.options, providers: [{ ...provider, requestTimeoutMs: 30 }] });
240
+ await assert.rejects(timed.invoke({ ...input, operation: "limits.read" }), { code: "connector_provider_timeout" });
241
+ const controller = new AbortController(); const pending = f.service.invoke({ ...input, operation: "limits.read", signal: controller.signal });
242
+ const timer = setTimeout(() => controller.abort(), 30);
243
+ await assert.rejects(pending, { code: "connector_cancelled" }); clearTimeout(timer);
244
+ assert.equal((await f.service.status(input)).status, "connected");
245
+ f.state.stall = false; f.state.time += 300000; f.state.tokenStatus = 400;
246
+ await assert.rejects(f.service.invoke({ ...input, operation: "limits.read" }), { code: "connector_reconnect_required" });
247
+ assert.equal((await f.service.status(input)).status, "reconnect-required");
248
+ });
249
+
250
+ test("Salesforce query policy authorizes an immutable request and denies data access before HTTP", async (t) => {
251
+ const f = await fixture(t); await f.connect();
252
+ const allowed = "SELECT Id FROM Account LIMIT 1";
253
+ const caller = { q: allowed };
254
+ const service = createConnectionService({ ...f.options, authorize: async (owner, request) => {
255
+ if (request.operation === "query.read") {
256
+ assert.equal(request.input.q, allowed);
257
+ caller.q = "SELECT Id FROM Contact";
258
+ request.input.q = "SELECT Id FROM Lead";
259
+ }
260
+ return owner;
261
+ } });
262
+ await service.invoke({ ...input, operation: "query.read", input: caller });
263
+ assert.equal(f.state.requests.at(-1).url.searchParams.get("q"), allowed);
264
+ const count = f.state.requests.length;
265
+ const denied = createConnectionService({ ...f.options, authorize: async () => { throw new Error("Query not allowed"); },
266
+ resolveReference: async () => { assert.fail("No credentials should be resolved before authorization"); } });
267
+ await assert.rejects(denied.invoke({ ...input, operation: "query.read", input: { q: allowed } }), /Query not allowed/);
268
+ assert.equal(f.state.requests.length, count);
269
+ });
270
+
271
+ test("Salesforce packaged configuration example validates with the same CLI schema", async () => {
272
+ const guide = await readFile(new URL("../docs/salesforce.md", import.meta.url), "utf8");
273
+ const json = /```json\n([\s\S]*?)\n```/u.exec(guide)?.[1]; assert.ok(json);
274
+ const configuration = JSON.parse(json);
275
+ assert.deepEqual(validateIntegrationConfiguration(configuration, { providers: [provider] }), configuration);
276
+ });