sfq 0.0.28__tar.gz → 0.0.29__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.28 → sfq-0.0.29}/PKG-INFO +1 -1
- {sfq-0.0.28 → sfq-0.0.29}/pyproject.toml +1 -1
- {sfq-0.0.28 → sfq-0.0.29}/src/sfq/__init__.py +1 -1
- sfq-0.0.29/tests/test_cupdate.py +112 -0
- {sfq-0.0.28 → sfq-0.0.29}/uv.lock +1 -1
- {sfq-0.0.28 → sfq-0.0.29}/.github/workflows/publish.yml +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/.gitignore +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/.python-version +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/README.md +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/src/sfq/_cometd.py +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/src/sfq/py.typed +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/tests/test_cdelete.py +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/tests/test_cquery.py +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/tests/test_create.py +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/tests/test_limits_api.py +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/tests/test_log_trace_redact.py +0 -0
- {sfq-0.0.28 → sfq-0.0.29}/tests/test_query.py +0 -0
@@ -99,7 +99,7 @@ class SFAuth:
|
|
99
99
|
access_token: Optional[str] = None,
|
100
100
|
token_expiration_time: Optional[float] = None,
|
101
101
|
token_lifetime: int = 15 * 60,
|
102
|
-
user_agent: str = "sfq/0.0.
|
102
|
+
user_agent: str = "sfq/0.0.29",
|
103
103
|
sforce_client: str = "_auto",
|
104
104
|
proxy: str = "_auto",
|
105
105
|
) -> None:
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import random
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from datetime import datetime
|
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.fail(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
|
+
def test_simple_cupdate(sf_instance):
|
39
|
+
"""Ensure that a simple update returns the expected results."""
|
40
|
+
query = "SELECT Id, CommentBody FROM FeedComment LIMIT 1"
|
41
|
+
response = sf_instance.query(query)
|
42
|
+
assert response and response["records"], "No FeedComment found for test."
|
43
|
+
|
44
|
+
update_dict = dict()
|
45
|
+
feed_comment_id = response["records"][0]["Id"]
|
46
|
+
update_dict[feed_comment_id] = {"CommentBody": f"Evaluated at {datetime.now()}"}
|
47
|
+
|
48
|
+
result = sf_instance._cupdate(update_dict)
|
49
|
+
|
50
|
+
# Validate the result
|
51
|
+
assert result, "CUpdate did not return a result."
|
52
|
+
assert isinstance(result, list), "CUpdate result is not a list."
|
53
|
+
assert len(result) == 1, "CUpdate result should contain one response."
|
54
|
+
assert "compositeResponse" in result[0], "CUpdate response does not contain 'compositeResponse'."
|
55
|
+
assert len(result[0]["compositeResponse"]) == 1, "CUpdate response should contain one composite response."
|
56
|
+
assert result[0]["compositeResponse"][0]["httpStatusCode"] == 204, "CUpdate did not return HTTP status 204."
|
57
|
+
assert result[0]["compositeResponse"][0]["body"] is None, "CUpdate response body should be None."
|
58
|
+
|
59
|
+
# Verify that the update was successful by querying the updated record
|
60
|
+
new_query = f"SELECT Id, CommentBody FROM FeedComment WHERE Id = '{feed_comment_id}'"
|
61
|
+
updated_response = sf_instance.query(new_query)
|
62
|
+
assert updated_response and updated_response["records"], "No updated FeedComment found."
|
63
|
+
assert len(updated_response["records"]) == 1, "Expected one updated FeedComment."
|
64
|
+
assert updated_response["records"][0]["CommentBody"] == update_dict[feed_comment_id]["CommentBody"], "Updated CommentBody does not match."
|
65
|
+
|
66
|
+
def test_cupdate_with_invalid_data(sf_instance):
|
67
|
+
"""Test cupdate with invalid data to ensure it raises an error."""
|
68
|
+
update_dict = {"invalid_id": {"CommentBody": "This should fail"}}
|
69
|
+
|
70
|
+
res = sf_instance._cupdate(update_dict)
|
71
|
+
assert res, "CUpdate did not return a result."
|
72
|
+
assert isinstance(res, list), "CUpdate result is not a list."
|
73
|
+
assert len(res) == 1, "CUpdate result should contain one response."
|
74
|
+
assert "compositeResponse" in res[0], "CUpdate response does not contain 'compositeResponse'."
|
75
|
+
assert len(res[0]["compositeResponse"]) == 1, "CUpdate response should contain one composite response."
|
76
|
+
assert res[0]["compositeResponse"][0]["httpStatusCode"] == 404, "CUpdate did not return HTTP status 404."
|
77
|
+
assert res[0]["compositeResponse"][0]["body"][0]["errorCode"] == "NOT_FOUND", "Expected NOT_FOUND error code."
|
78
|
+
assert res[0]["compositeResponse"][0]["body"][0]["message"] == "The requested resource does not exist", "Expected NOT_FOUND error message."
|
79
|
+
|
80
|
+
def test_cupdate_pagination(sf_instance):
|
81
|
+
"""Test cupdate with pagination to ensure it handles multiple records."""
|
82
|
+
size = 35 # using `/services/data/{self.api_version}/sobjects/{sobject}/{key}` which is 25 per pagination; I want to migrate to Soap for larger 200 batches.
|
83
|
+
query = f"SELECT Id, CommentBody FROM FeedComment LIMIT {size}"
|
84
|
+
initial_query_response = sf_instance.query(query)
|
85
|
+
|
86
|
+
response_count = len(initial_query_response["records"])
|
87
|
+
assert response_count == size, f"Expected {size} records, got {response_count}."
|
88
|
+
ids_updated = {record["Id"] for record in initial_query_response["records"]}
|
89
|
+
|
90
|
+
update_dict = dict()
|
91
|
+
for record in initial_query_response["records"]:
|
92
|
+
feed_comment_id = record["Id"]
|
93
|
+
update_dict[feed_comment_id] = {"CommentBody": f"sfq/{datetime.now().timestamp()} #{random.randint(1000, 9999)}"}
|
94
|
+
|
95
|
+
update_response = sf_instance._cupdate(update_dict)
|
96
|
+
assert update_response, "CUpdate did not return a result."
|
97
|
+
assert isinstance(update_response, list), "CUpdate result is not a list."
|
98
|
+
|
99
|
+
subset_ids = random.sample(list(ids_updated), min(50, len(ids_updated)))
|
100
|
+
subset_id_str = ", ".join([f"'{id_}'" for id_ in subset_ids])
|
101
|
+
subset_query = f"SELECT Id, CommentBody FROM FeedComment WHERE Id IN ({subset_id_str})"
|
102
|
+
updated_response = sf_instance.query(subset_query)
|
103
|
+
|
104
|
+
# Validate the updated records
|
105
|
+
assert updated_response, "Query for updated records did not return a result."
|
106
|
+
assert updated_response and updated_response["records"], "No updated FeedComment found."
|
107
|
+
assert len(updated_response["records"]) == len(subset_ids), "Expected updated records count does not match subset size."
|
108
|
+
|
109
|
+
# validate that all updated records have the expected CommentBody
|
110
|
+
for record in updated_response["records"]:
|
111
|
+
expected_comment_body = update_dict[record["Id"]]["CommentBody"]
|
112
|
+
assert record["CommentBody"] == expected_comment_body, f"Updated CommentBody for {record['Id']} does not match expected value."
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|