Skip to content

Commit 62b02b0

Browse files
committed
Fix SF CLI integration mock server
1 parent 9e836c3 commit 62b02b0

2 files changed

Lines changed: 54 additions & 7 deletions

File tree

‎.github/workflows/sf_cli_integration.yml‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ jobs:
1010
env:
1111
SF_AUTOUPDATE_DISABLE: true
1212
NO_COLOR: '1'
13+
# The mock server serves TLS with a self-signed cert written to this path.
14+
# Point both the CLI (Node) and the SDK (Python requests) at it so they
15+
# trust the specific cert rather than disabling verification wholesale.
16+
MOCK_SF_CERT_FILE: ${{ github.workspace }}/mock_sf_cert.pem
17+
NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/mock_sf_cert.pem
18+
REQUESTS_CA_BUNDLE: ${{ github.workspace }}/mock_sf_cert.pem
1319

1420
steps:
1521
# ── Setup ─────────────────────────────────────────────────────────────────
@@ -56,7 +62,14 @@ jobs:
5662
# ── Mock Salesforce server + fake org auth ────────────────────────────────
5763

5864
- name: Start mock Salesforce server
59-
run: python scripts/mock_sf_server.py &
65+
run: |
66+
python scripts/mock_sf_server.py &
67+
# Wait for the TLS cert the server writes at startup so clients can trust it.
68+
for _ in $(seq 1 30); do
69+
[ -f "$MOCK_SF_CERT_FILE" ] && break
70+
sleep 0.2
71+
done
72+
test -f "$MOCK_SF_CERT_FILE" || { echo "::error::mock server never wrote $MOCK_SF_CERT_FILE"; exit 1; }
6073
env:
6174
MOCK_SF_PORT: '8888'
6275

@@ -73,7 +86,7 @@ jobs:
7386
sfdx_dir.mkdir(exist_ok=True)
7487
auth = {
7588
"accessToken": "00D000000000001AAA!fakeTokenForCITesting",
76-
"instanceUrl": "http://localhost:8888",
89+
"instanceUrl": "https://localhost:8888",
7790
"loginUrl": "https://login.salesforce.com",
7891
"orgId": "00D000000000001AAA",
7992
"userId": "005000000000001AAA",

‎scripts/mock_sf_server.py‎

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,50 @@
4040
python scripts/mock_sf_server.py # listens on port 8888
4141
MOCK_SF_PORT=9000 python scripts/mock_sf_server.py
4242
python scripts/mock_sf_server.py 9000
43+
44+
Serves TLS with a throwaway self-signed cert (the deploy path requires an HTTPS
45+
upload URL). Set ``MOCK_SF_CERT_FILE`` to a path the clients can trust via
46+
``NODE_EXTRA_CA_CERTS`` (CLI) and ``REQUESTS_CA_BUNDLE`` (SDK).
4347
"""
4448

4549
from __future__ import annotations
4650

47-
from http.server import BaseHTTPRequestHandler, HTTPServer
4851
import json
4952
import os
53+
import ssl
54+
import subprocess
5055
import sys
56+
import tempfile
57+
from http.server import BaseHTTPRequestHandler, HTTPServer
5158

5259
PORT = (
5360
int(sys.argv[1])
5461
if len(sys.argv) > 1
5562
else int(os.environ.get("MOCK_SF_PORT", "8888"))
5663
)
5764

65+
66+
def _self_signed_cert(dirpath: str) -> tuple[str, str]:
67+
"""Generate a throwaway self-signed cert for localhost via openssl.
68+
69+
The plugin's deploy path requires an HTTPS upload URL, so the server must
70+
speak TLS. Set ``MOCK_SF_CERT_FILE`` to write the cert to a known path so
71+
clients can trust it (``REQUESTS_CA_BUNDLE`` / ``NODE_EXTRA_CA_CERTS``).
72+
"""
73+
cert_path = os.environ.get("MOCK_SF_CERT_FILE") or os.path.join(dirpath, "cert.pem")
74+
key_path = os.path.join(dirpath, "key.pem")
75+
subprocess.run(
76+
[
77+
"openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes",
78+
"-keyout", key_path, "-out", cert_path, "-days", "1",
79+
"-subj", "/CN=localhost",
80+
"-addext", "subjectAltName=DNS:localhost,IP:127.0.0.1",
81+
],
82+
check=True,
83+
capture_output=True,
84+
)
85+
return cert_path, key_path
86+
5887
_USERINFO = {
5988
"sub": "https://test.salesforce.com/id/00D000000000001AAA/005000000000001AAA",
6089
"user_id": "005000000000001AAA",
@@ -68,7 +97,7 @@
6897

6998
_TOKEN_RESPONSE = {
7099
"access_token": "00D000000000001AAA!fakeAccessTokenForCITesting",
71-
"instance_url": f"http://localhost:{PORT}",
100+
"instance_url": f"https://localhost:{PORT}",
72101
"token_type": "Bearer",
73102
"scope": "api",
74103
}
@@ -135,7 +164,7 @@ def do_POST(self) -> None:
135164
elif path == _DATA_CUSTOM_CODE_PATH:
136165
# create_deployment() — return a presigned upload URL
137166
self._send_json(
138-
{"fileUploadUrl": f"http://localhost:{PORT}/upload/fake-deployment.zip"}
167+
{"fileUploadUrl": f"https://localhost:{PORT}/upload/fake-deployment.zip"}
139168
)
140169
elif path == _DATA_TRANSFORMS_PATH:
141170
# create_data_transform() — script packages only
@@ -152,5 +181,10 @@ def do_PUT(self) -> None:
152181
if __name__ == "__main__":
153182
server = HTTPServer(("localhost", PORT), MockSFHandler)
154183
server.allow_reuse_address = True
155-
print(f"[MOCK SF] Listening on http://localhost:{PORT}", flush=True)
156-
server.serve_forever()
184+
with tempfile.TemporaryDirectory() as certdir:
185+
cert_path, key_path = _self_signed_cert(certdir)
186+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
187+
ctx.load_cert_chain(cert_path, key_path)
188+
server.socket = ctx.wrap_socket(server.socket, server_side=True)
189+
print(f"[MOCK SF] Listening on https://localhost:{PORT}", flush=True)
190+
server.serve_forever()

0 commit comments

Comments
 (0)