sfq 0.0.20__tar.gz → 0.0.22__tar.gz
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.
- {sfq-0.0.20 → sfq-0.0.22}/.github/workflows/publish.yml +12 -1
- {sfq-0.0.20 → sfq-0.0.22}/PKG-INFO +1 -1
- {sfq-0.0.20 → sfq-0.0.22}/pyproject.toml +1 -1
- {sfq-0.0.20 → sfq-0.0.22}/src/sfq/__init__.py +4 -4
- sfq-0.0.22/tests/conftest.py +16 -0
- sfq-0.0.22/tests/test_limits_api.py +44 -0
- sfq-0.0.22/tests/test_log_trace_redact.py +79 -0
- {sfq-0.0.20 → sfq-0.0.22}/uv.lock +1 -1
- {sfq-0.0.20 → sfq-0.0.22}/.gitignore +0 -0
- {sfq-0.0.20 → sfq-0.0.22}/.python-version +0 -0
- {sfq-0.0.20 → sfq-0.0.22}/README.md +0 -0
- {sfq-0.0.20 → sfq-0.0.22}/src/sfq/_cometd.py +0 -0
- {sfq-0.0.20 → sfq-0.0.22}/src/sfq/py.typed +0 -0
@@ -32,6 +32,9 @@ jobs:
|
|
32
32
|
- name: Sync dependencies with uv
|
33
33
|
run: uv sync
|
34
34
|
|
35
|
+
- name: Install pytest
|
36
|
+
run: pip install pytest
|
37
|
+
|
35
38
|
- name: Authenticate GitHub CLI
|
36
39
|
run: |
|
37
40
|
echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token
|
@@ -75,12 +78,20 @@ jobs:
|
|
75
78
|
sed -i -E "s/(user_agent: str = \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\")/\1$VERSION\2/" src/sfq/__init__.py
|
76
79
|
sed -i -E "s/(default is \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\")/\1$VERSION\2/" src/sfq/__init__.py
|
77
80
|
|
81
|
+
- name: Run tests
|
82
|
+
run: pytest --verbose --strict-config
|
83
|
+
env:
|
84
|
+
SF_INSTANCE_URL: ${{ secrets.SF_INSTANCE_URL }}
|
85
|
+
SF_CLIENT_ID: ${{ secrets.SF_CLIENT_ID }}
|
86
|
+
SF_CLIENT_SECRET: ${{ secrets.SF_CLIENT_SECRET }}
|
87
|
+
SF_REFRESH_TOKEN: ${{ secrets.SF_REFRESH_TOKEN }}
|
88
|
+
|
78
89
|
- name: Commit version updates
|
79
90
|
run: |
|
80
91
|
git config user.name "github-actions"
|
81
92
|
git config user.email "github-actions@users.noreply.github.com"
|
82
93
|
git add pyproject.toml uv.lock src/sfq/__init__.py
|
83
|
-
git commit -m "
|
94
|
+
git commit -m "CI: bump version to ${{ steps.get_version.outputs.version }}"
|
84
95
|
git push
|
85
96
|
|
86
97
|
- name: Create and push git tag
|
@@ -79,12 +79,12 @@ class SFAuth:
|
|
79
79
|
client_id: str,
|
80
80
|
refresh_token: str, # client_secret & refresh_token will swap positions 2025-AUG-1
|
81
81
|
client_secret: str = "_deprecation_warning", # mandatory after 2025-AUG-1
|
82
|
-
api_version: str = "
|
82
|
+
api_version: str = "v64.0",
|
83
83
|
token_endpoint: str = "/services/oauth2/token",
|
84
84
|
access_token: Optional[str] = None,
|
85
85
|
token_expiration_time: Optional[float] = None,
|
86
86
|
token_lifetime: int = 15 * 60,
|
87
|
-
user_agent: str = "sfq/0.0.
|
87
|
+
user_agent: str = "sfq/0.0.22",
|
88
88
|
sforce_client: str = "_auto",
|
89
89
|
proxy: str = "_auto",
|
90
90
|
) -> None:
|
@@ -95,12 +95,12 @@ class SFAuth:
|
|
95
95
|
:param client_id: The client ID for OAuth.
|
96
96
|
:param refresh_token: The refresh token for OAuth.
|
97
97
|
:param client_secret: The client secret for OAuth (default is "_deprecation_warning").
|
98
|
-
:param api_version: The Salesforce API version (default is "
|
98
|
+
:param api_version: The Salesforce API version (default is "v64.0").
|
99
99
|
:param token_endpoint: The token endpoint (default is "/services/oauth2/token").
|
100
100
|
:param access_token: The access token for the current session (default is None).
|
101
101
|
:param token_expiration_time: The expiration time of the access token (default is None).
|
102
102
|
:param token_lifetime: The lifetime of the access token in seconds (default is 15 minutes).
|
103
|
-
:param user_agent: Custom User-Agent string (default is "sfq/0.0.
|
103
|
+
:param user_agent: Custom User-Agent string (default is "sfq/0.0.22").
|
104
104
|
:param sforce_client: Custom Application Identifier (default is user_agent).
|
105
105
|
:param proxy: The proxy configuration, "_auto" to use environment (default is "_auto").
|
106
106
|
"""
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# tests/conftest.py
|
2
|
+
|
3
|
+
executed_tests = 0
|
4
|
+
|
5
|
+
def pytest_runtest_logreport(report):
|
6
|
+
global executed_tests
|
7
|
+
if report.when == "call" and report.passed:
|
8
|
+
executed_tests += 1
|
9
|
+
elif report.when == "call" and report.failed:
|
10
|
+
executed_tests += 1
|
11
|
+
|
12
|
+
def pytest_sessionfinish(session, exitstatus):
|
13
|
+
if session.testscollected > 0 and executed_tests == 0:
|
14
|
+
print()
|
15
|
+
print("❌ Pytest collected tests, but all were skipped. Failing the run.")
|
16
|
+
session.exitstatus = 1
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
|
7
|
+
# --- Setup local import path ---
|
8
|
+
project_root = Path(__file__).resolve().parents[1]
|
9
|
+
src_path = project_root / "src"
|
10
|
+
sys.path.insert(0, str(src_path))
|
11
|
+
from sfq import SFAuth # noqa: E402
|
12
|
+
|
13
|
+
|
14
|
+
@pytest.fixture(scope="module")
|
15
|
+
def sf_instance():
|
16
|
+
required_env_vars = [
|
17
|
+
"SF_INSTANCE_URL",
|
18
|
+
"SF_CLIENT_ID",
|
19
|
+
"SF_CLIENT_SECRET",
|
20
|
+
"SF_REFRESH_TOKEN",
|
21
|
+
]
|
22
|
+
|
23
|
+
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
24
|
+
if missing_vars:
|
25
|
+
pytest.skip(f"Missing required env vars: {', '.join(missing_vars)}")
|
26
|
+
|
27
|
+
sf = SFAuth(
|
28
|
+
instance_url=os.getenv("SF_INSTANCE_URL"),
|
29
|
+
client_id=os.getenv("SF_CLIENT_ID"),
|
30
|
+
client_secret=os.getenv("SF_CLIENT_SECRET"),
|
31
|
+
refresh_token=os.getenv("SF_REFRESH_TOKEN"),
|
32
|
+
)
|
33
|
+
return sf
|
34
|
+
|
35
|
+
|
36
|
+
def test_limits_api(sf_instance):
|
37
|
+
"""
|
38
|
+
Test the limits API endpoint.
|
39
|
+
"""
|
40
|
+
|
41
|
+
limits = sf_instance.limits()
|
42
|
+
|
43
|
+
assert isinstance(limits, dict)
|
44
|
+
assert "DailyApiRequests" in limits.keys()
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from io import StringIO
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
import pytest
|
8
|
+
|
9
|
+
# --- Setup local import path ---
|
10
|
+
project_root = Path(__file__).resolve().parents[1]
|
11
|
+
src_path = project_root / "src"
|
12
|
+
sys.path.insert(0, str(src_path))
|
13
|
+
from sfq import SFAuth # noqa: E402
|
14
|
+
|
15
|
+
|
16
|
+
@pytest.fixture(scope="module")
|
17
|
+
def sf_instance():
|
18
|
+
required_env_vars = [
|
19
|
+
"SF_INSTANCE_URL",
|
20
|
+
"SF_CLIENT_ID",
|
21
|
+
"SF_CLIENT_SECRET",
|
22
|
+
"SF_REFRESH_TOKEN",
|
23
|
+
]
|
24
|
+
|
25
|
+
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
26
|
+
if missing_vars:
|
27
|
+
pytest.skip(f"Missing required env vars: {', '.join(missing_vars)}")
|
28
|
+
|
29
|
+
sf = SFAuth(
|
30
|
+
instance_url=os.getenv("SF_INSTANCE_URL"),
|
31
|
+
client_id=os.getenv("SF_CLIENT_ID"),
|
32
|
+
client_secret=os.getenv("SF_CLIENT_SECRET"),
|
33
|
+
refresh_token=os.getenv("SF_REFRESH_TOKEN"),
|
34
|
+
)
|
35
|
+
return sf
|
36
|
+
|
37
|
+
|
38
|
+
@pytest.fixture
|
39
|
+
def capture_logs():
|
40
|
+
"""
|
41
|
+
Fixture to capture logs emitted to 'sfq' logger at TRACE level.
|
42
|
+
"""
|
43
|
+
log_stream = StringIO()
|
44
|
+
handler = logging.StreamHandler(log_stream)
|
45
|
+
handler.setLevel(5)
|
46
|
+
|
47
|
+
logger = logging.getLogger("sfq")
|
48
|
+
original_level = logger.level
|
49
|
+
original_handlers = logger.handlers[:]
|
50
|
+
|
51
|
+
logger.setLevel(5)
|
52
|
+
for h in original_handlers:
|
53
|
+
logger.removeHandler(h)
|
54
|
+
logger.addHandler(handler)
|
55
|
+
|
56
|
+
yield logger, log_stream
|
57
|
+
|
58
|
+
# Teardown - restore original handlers and level
|
59
|
+
logger.removeHandler(handler)
|
60
|
+
for h in original_handlers:
|
61
|
+
logger.addHandler(h)
|
62
|
+
logger.setLevel(original_level)
|
63
|
+
|
64
|
+
|
65
|
+
def test_access_token_redacted_in_logs(sf_instance, capture_logs):
|
66
|
+
"""
|
67
|
+
Ensure access tokens are redacted in log output to prevent leakage.
|
68
|
+
"""
|
69
|
+
logger, log_stream = capture_logs
|
70
|
+
|
71
|
+
sf_instance._get_common_headers()
|
72
|
+
|
73
|
+
logger.handlers[0].flush()
|
74
|
+
log_contents = log_stream.getvalue()
|
75
|
+
|
76
|
+
assert "access_token" in log_contents, "Expected access_token key in logs"
|
77
|
+
assert "'access_token': '********'," in log_contents in log_contents, (
|
78
|
+
"Access token was not properly redacted in logs"
|
79
|
+
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|