Skip to content

Commit 218b138

Browse files
committed
testing out run command
1 parent 0eea072 commit 218b138

2 files changed

Lines changed: 157 additions & 0 deletions

File tree

.github/workflows/sf_cli_integration.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ jobs:
4141
- name: Install data-code-extension plugin
4242
run: sf plugins install @salesforce/plugin-data-code-extension --force
4343

44+
- name: Set up Java 17 (required for PySpark during run)
45+
uses: actions/setup-java@v4
46+
with:
47+
distribution: temurin
48+
java-version: '17'
49+
50+
# ── Mock Salesforce server + fake org auth ────────────────────────────────
51+
52+
- name: Start mock Salesforce server
53+
run: python scripts/mock_sf_server.py &
54+
env:
55+
MOCK_SF_PORT: '8888'
56+
57+
- name: Register fake org 'dev1' with SF CLI
58+
run: |
59+
sleep 1
60+
echo "fake_access_token_00D000000000001AAA" | \
61+
sf org login access-token \
62+
--instance-url http://localhost:8888 \
63+
--alias dev1 \
64+
--no-prompt
65+
4466
# ── Script: init ──────────────────────────────────────────────────────────
4567

4668
- name: '[script] init — sf data-code-extension script init --package-dir testScript'
@@ -106,6 +128,17 @@ jobs:
106128
exit 1
107129
}
108130
131+
# ── Script: run ───────────────────────────────────────────────────────────
132+
133+
- name: '[script] run — sf data-code-extension script run --entrypoint testScript/payload/entrypoint.py -o dev1'
134+
run: |
135+
sf data-code-extension script run \
136+
--entrypoint testScript/payload/entrypoint.py \
137+
-o dev1 || {
138+
echo "::error::sf data-code-extension script run FAILED. Check mock server output above for which endpoint failed. The --entrypoint flag or SF CLI org auth contract may have changed."
139+
exit 1
140+
}
141+
109142
# ── Function: init ────────────────────────────────────────────────────────
110143

111144
- name: '[function] init — sf data-code-extension function init --package-dir testFunction'
@@ -173,3 +206,14 @@ jobs:
173206
echo "::error::deployment.zip not found after sf data-code-extension function zip."
174207
exit 1
175208
}
209+
210+
# ── Function: run ─────────────────────────────────────────────────────────
211+
212+
- name: '[function] run — sf data-code-extension function run --entrypoint testFunction/payload/entrypoint.py -o dev1'
213+
run: |
214+
sf data-code-extension function run \
215+
--entrypoint testFunction/payload/entrypoint.py \
216+
-o dev1 || {
217+
echo "::error::sf data-code-extension function run FAILED. Check mock server output above; the --entrypoint flag or SF CLI org auth contract may have changed."
218+
exit 1
219+
}

scripts/mock_sf_server.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""Minimal mock Salesforce server for CI integration tests.
2+
3+
Intercepts the HTTP calls made during ``sf data-code-extension script|function run``
4+
so that neither a real Salesforce org nor real Data Cloud data is required.
5+
6+
Endpoints handled
7+
-----------------
8+
GET /services/oauth2/userinfo
9+
Called by ``sf org login access-token`` to resolve a username from a token,
10+
and by ``connection.refreshAuth()`` in the SF CLI plugin.
11+
12+
POST /services/oauth2/token
13+
Called by ``connection.refreshAuth()`` when it tries to exchange a token.
14+
15+
POST /services/data/v66.0/ssot/query-sql
16+
Called by SFCLIDataCloudReader when the script entrypoint reads a DLO/DMO.
17+
Returns fake rows with the columns expected by the default script template
18+
(Account_std__dll: description__c, sfdcorganizationid__c, kq_id__c).
19+
20+
GET /* (catch-all)
21+
Returns {"status": "ok"} for any other SF API path the CLI may probe.
22+
23+
Usage
24+
-----
25+
python scripts/mock_sf_server.py # listens on port 8888
26+
MOCK_SF_PORT=9000 python scripts/mock_sf_server.py
27+
python scripts/mock_sf_server.py 9000
28+
"""
29+
30+
from __future__ import annotations
31+
32+
from http.server import BaseHTTPRequestHandler, HTTPServer
33+
import json
34+
import os
35+
import sys
36+
37+
PORT = (
38+
int(sys.argv[1])
39+
if len(sys.argv) > 1
40+
else int(os.environ.get("MOCK_SF_PORT", "8888"))
41+
)
42+
43+
_USERINFO = {
44+
"sub": "https://test.salesforce.com/id/00D000000000001AAA/005000000000001AAA",
45+
"user_id": "005000000000001AAA",
46+
"organization_id": "00D000000000001AAA",
47+
"preferred_username": "dev1@example.com",
48+
"name": "Dev User",
49+
"email": "dev1@example.com",
50+
"username": "dev1@example.com",
51+
"active": True,
52+
}
53+
54+
_TOKEN_RESPONSE = {
55+
"access_token": "fake_access_token_00D000000000001AAA",
56+
"instance_url": f"http://localhost:{PORT}",
57+
"token_type": "Bearer",
58+
"scope": "api",
59+
}
60+
61+
# Fake rows for Account_std__dll (used by the default script template).
62+
# Columns: description__c, sfdcorganizationid__c, kq_id__c
63+
_QUERY_RESPONSE = {
64+
"metadata": [
65+
{"name": "description__c"},
66+
{"name": "sfdcorganizationid__c"},
67+
{"name": "kq_id__c"},
68+
],
69+
"data": [
70+
["hello world", "org123", "kq001"],
71+
["another row", "org123", "kq002"],
72+
],
73+
}
74+
75+
76+
class MockSFHandler(BaseHTTPRequestHandler):
77+
def _send_json(self, payload: object, status: int = 200) -> None:
78+
body = json.dumps(payload).encode()
79+
self.send_response(status)
80+
self.send_header("Content-Type", "application/json")
81+
self.send_header("Content-Length", str(len(body)))
82+
self.end_headers()
83+
self.wfile.write(body)
84+
85+
def log_message(self, fmt: str, *args: object) -> None: # type: ignore[override]
86+
print(f"[MOCK SF] {self.command} {self.path}{fmt % args}", flush=True)
87+
88+
def do_GET(self) -> None:
89+
path = self.path.split("?")[0]
90+
if path == "/services/oauth2/userinfo":
91+
self._send_json(_USERINFO)
92+
else:
93+
self._send_json({"status": "ok"})
94+
95+
def do_POST(self) -> None:
96+
path = self.path.split("?")[0]
97+
length = int(self.headers.get("Content-Length", 0))
98+
body = self.rfile.read(length) if length else b""
99+
print(f"[MOCK SF] POST body: {body[:200]!r}", flush=True)
100+
101+
if path == "/services/oauth2/token":
102+
self._send_json(_TOKEN_RESPONSE)
103+
elif path.endswith("/ssot/query-sql"):
104+
self._send_json(_QUERY_RESPONSE)
105+
else:
106+
self._send_json({"status": "ok"})
107+
108+
109+
if __name__ == "__main__":
110+
server = HTTPServer(("localhost", PORT), MockSFHandler)
111+
server.allow_reuse_address = True
112+
print(f"[MOCK SF] Listening on http://localhost:{PORT}", flush=True)
113+
server.serve_forever()

0 commit comments

Comments
 (0)