salespyforce 1.4.0.dev0__py3-none-any.whl → 1.4.0.dev2__py3-none-any.whl
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.
- salespyforce/api.py +44 -1
- salespyforce/core.py +94 -13
- salespyforce/knowledge.py +21 -5
- salespyforce/utils/core_utils.py +44 -2
- salespyforce/utils/log_utils.py +17 -16
- salespyforce/utils/tests/conftest.py +202 -0
- salespyforce/utils/tests/test_core_utils.py +234 -0
- salespyforce/utils/tests/test_instantiate_object.py +31 -18
- salespyforce/utils/tests/test_log_utils.py +79 -0
- salespyforce/utils/tests/test_sobjects.py +22 -15
- salespyforce/utils/tests/test_soql.py +8 -8
- salespyforce/utils/tests/test_sosl.py +9 -9
- salespyforce/utils/tests/test_version_utils.py +70 -0
- {salespyforce-1.4.0.dev0.dist-info → salespyforce-1.4.0.dev2.dist-info}/METADATA +6 -10
- salespyforce-1.4.0.dev2.dist-info/RECORD +27 -0
- salespyforce-1.4.0.dev0.dist-info/RECORD +0 -23
- {salespyforce-1.4.0.dev0.dist-info → salespyforce-1.4.0.dev2.dist-info}/LICENSE +0 -0
- {salespyforce-1.4.0.dev0.dist-info → salespyforce-1.4.0.dev2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
:Module: salespyforce.utils.tests.conftest
|
|
4
|
+
:Synopsis: Configuration for performing unit testing with pytest
|
|
5
|
+
:Usage: Leveraged by pytest in test modules
|
|
6
|
+
:Example: ``soql_response = salesforce_unit.soql_query(soql_statement)``
|
|
7
|
+
:Created By: Jeff Shurtliff
|
|
8
|
+
:Last Modified: Jeff Shurtliff
|
|
9
|
+
:Modified Date: 13 Dec 2025
|
|
10
|
+
|
|
11
|
+
Pytest fixtures for ``salespyforce.utils.tests``.
|
|
12
|
+
|
|
13
|
+
This module centralizes helpers used across the test suite to avoid
|
|
14
|
+
repeated setup in individual test files. It introduces two key fixtures:
|
|
15
|
+
|
|
16
|
+
* ``salesforce_integration`` — Instantiates the real ``Salesforce``
|
|
17
|
+
client when a helper file is available. Tests using this fixture are
|
|
18
|
+
marked as ``integration`` and are skipped unless ``--integration`` is
|
|
19
|
+
provided.
|
|
20
|
+
* ``salesforce_unit`` — Provides a lightweight stub that mimics the
|
|
21
|
+
public API used by existing tests without performing any network I/O.
|
|
22
|
+
|
|
23
|
+
The goal is to make it easy to switch between fast, isolated unit tests
|
|
24
|
+
and opt-in integration runs against a real Salesforce org. Additional
|
|
25
|
+
fixtures can be added here to share common mocking or configuration.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from types import SimpleNamespace
|
|
33
|
+
from typing import Iterator
|
|
34
|
+
|
|
35
|
+
import pytest
|
|
36
|
+
|
|
37
|
+
from salespyforce.core import Salesforce
|
|
38
|
+
|
|
39
|
+
# Define constants
|
|
40
|
+
HELPER_FILE_NAME = "helper_dm_conn.yml"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# -----------------------------
|
|
44
|
+
# Pytest configuration hooks
|
|
45
|
+
# -----------------------------
|
|
46
|
+
|
|
47
|
+
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
48
|
+
"""This function registers custom CLI options.
|
|
49
|
+
|
|
50
|
+
.. version-added:: 1.4.0
|
|
51
|
+
|
|
52
|
+
``--integration`` enables tests that require access to a real
|
|
53
|
+
Salesforce org. Keeping this opt-in protects routine runs from
|
|
54
|
+
network or credential dependencies.
|
|
55
|
+
"""
|
|
56
|
+
parser.addoption(
|
|
57
|
+
"--integration",
|
|
58
|
+
action="store_true",
|
|
59
|
+
default=False,
|
|
60
|
+
help="run tests that require a Salesforce helper file",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def pytest_configure(config: pytest.Config) -> None:
|
|
65
|
+
"""This function declares custom markers so pytest will not warn during collection.
|
|
66
|
+
|
|
67
|
+
.. version-added:: 1.4.0
|
|
68
|
+
"""
|
|
69
|
+
config.addinivalue_line(
|
|
70
|
+
"markers",
|
|
71
|
+
"integration: marks tests that require a real Salesforce org",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def pytest_collection_modifyitems(
|
|
76
|
+
config: pytest.Config, items: list[pytest.Item]
|
|
77
|
+
) -> None:
|
|
78
|
+
"""This function skips integration tests when ``--integration`` is not provided.
|
|
79
|
+
|
|
80
|
+
.. version-added:: 1.4.0
|
|
81
|
+
"""
|
|
82
|
+
if config.getoption("--integration"):
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
skip_integration = pytest.mark.skip(
|
|
86
|
+
reason="requires --integration to run against a Salesforce org"
|
|
87
|
+
)
|
|
88
|
+
for item in items:
|
|
89
|
+
if "integration" in item.keywords:
|
|
90
|
+
item.add_marker(skip_integration)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# -----------------------------
|
|
94
|
+
# Helper utilities
|
|
95
|
+
# -----------------------------
|
|
96
|
+
|
|
97
|
+
def _find_helper_file() -> Path | None:
|
|
98
|
+
"""This function locates a helper file in common locations used by this project.
|
|
99
|
+
|
|
100
|
+
.. version-added:: 1.4.0
|
|
101
|
+
"""
|
|
102
|
+
helper_locations = [
|
|
103
|
+
Path(os.environ.get("HOME", "")) / "secrets" / HELPER_FILE_NAME,
|
|
104
|
+
Path("local") / HELPER_FILE_NAME,
|
|
105
|
+
]
|
|
106
|
+
for helper_path in helper_locations:
|
|
107
|
+
if helper_path.is_file():
|
|
108
|
+
return helper_path.resolve()
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# -----------------------------
|
|
113
|
+
# Fixtures
|
|
114
|
+
# -----------------------------
|
|
115
|
+
|
|
116
|
+
@pytest.fixture(scope="session")
|
|
117
|
+
def integration_helper_file() -> Path:
|
|
118
|
+
"""This fixture returns the helper file path or skips the test if none is available.
|
|
119
|
+
|
|
120
|
+
.. version-added:: 1.4.0
|
|
121
|
+
|
|
122
|
+
Keeping this lookup in a session-scoped fixture ensures we only
|
|
123
|
+
perform filesystem checks once per test run and provides a single
|
|
124
|
+
source of truth for integration tests.
|
|
125
|
+
"""
|
|
126
|
+
helper_path = _find_helper_file()
|
|
127
|
+
if helper_path is None:
|
|
128
|
+
pytest.skip("No Salesforce helper file found for integration tests")
|
|
129
|
+
return helper_path
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.fixture(scope="session")
|
|
133
|
+
def salesforce_integration(integration_helper_file: Path) -> Iterator[Salesforce]:
|
|
134
|
+
"""This fixture instantiates the real Salesforce client for integration tests.
|
|
135
|
+
|
|
136
|
+
.. version-added:: 1.4.0
|
|
137
|
+
|
|
138
|
+
The fixture is session-scoped to avoid repeated authentication and
|
|
139
|
+
to reuse connections across tests. It is intended only for tests
|
|
140
|
+
marked with ``@pytest.mark.integration``.
|
|
141
|
+
"""
|
|
142
|
+
client = Salesforce(helper=str(integration_helper_file))
|
|
143
|
+
yield client
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@pytest.fixture()
|
|
147
|
+
def salesforce_unit(monkeypatch: pytest.MonkeyPatch) -> SimpleNamespace:
|
|
148
|
+
"""This fixture provides a lightweight stub that mimics the ``Salesforce`` API.
|
|
149
|
+
|
|
150
|
+
.. version-added:: 1.4.0
|
|
151
|
+
|
|
152
|
+
This fixture avoids network calls by supplying deterministic return
|
|
153
|
+
values for the subset of methods exercised by the current tests.
|
|
154
|
+
It can be extended as coverage grows to keep unit tests fast and
|
|
155
|
+
self-contained.
|
|
156
|
+
"""
|
|
157
|
+
# Minimal data used across tests
|
|
158
|
+
sample_urls = {
|
|
159
|
+
"base_url": "https://example.force.com",
|
|
160
|
+
"rest_resources": {"metadata": "available"},
|
|
161
|
+
"org_limits": {"DailyApiRequests": {"Remaining": 15000}},
|
|
162
|
+
"sobjects": {"sobjects": []},
|
|
163
|
+
"account": {
|
|
164
|
+
"objectDescribe": {},
|
|
165
|
+
"activateable": False,
|
|
166
|
+
},
|
|
167
|
+
"soql_result": {
|
|
168
|
+
"done": True,
|
|
169
|
+
"totalSize": 1,
|
|
170
|
+
"records": [{"Id": "001XX000003NGqqYAG"}],
|
|
171
|
+
},
|
|
172
|
+
"sosl_result": {
|
|
173
|
+
"searchRecords": [
|
|
174
|
+
{"attributes": {"type": "Account"}, "Id": "001XX000003NGqqYAG"}
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
def _create_response(**overrides):
|
|
180
|
+
"""This function creates an API response payload mimicking the Salesforce REST API responses.
|
|
181
|
+
|
|
182
|
+
.. version-added:: 1.4.0
|
|
183
|
+
"""
|
|
184
|
+
response = {"id": "001D000000IqhSLIAZ", "success": True, "errors": []}
|
|
185
|
+
response.update(overrides)
|
|
186
|
+
return response
|
|
187
|
+
|
|
188
|
+
stub = SimpleNamespace()
|
|
189
|
+
stub.base_url = sample_urls["base_url"]
|
|
190
|
+
stub.get_api_versions = lambda: [{"version": "v65.0"}]
|
|
191
|
+
stub.get_rest_resources = lambda: sample_urls["rest_resources"]
|
|
192
|
+
stub.get_org_limits = lambda: sample_urls["org_limits"]
|
|
193
|
+
stub.get_all_sobjects = lambda: sample_urls["sobjects"]
|
|
194
|
+
stub.get_sobject = lambda *_args, **_kwargs: sample_urls["account"]
|
|
195
|
+
stub.describe_object = lambda *_args, **_kwargs: sample_urls["account"]
|
|
196
|
+
stub.create_sobject_record = (
|
|
197
|
+
lambda *_args, **_kwargs: _create_response()
|
|
198
|
+
)
|
|
199
|
+
stub.soql_query = lambda *_args, **_kwargs: sample_urls["soql_result"]
|
|
200
|
+
stub.search_string = lambda *_args, **_kwargs: sample_urls["sosl_result"]
|
|
201
|
+
|
|
202
|
+
return stub
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# bandit: skip=B101
|
|
3
|
+
"""
|
|
4
|
+
:Module: salespyforce.utils.tests.test_core_utils
|
|
5
|
+
:Synopsis: This module is used by pytest to test core utility functions
|
|
6
|
+
:Created By: Jeff Shurtliff
|
|
7
|
+
:Last Modified: Jeff Shurtliff
|
|
8
|
+
:Modified Date: 30 Jan 2026
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import pathlib
|
|
13
|
+
import warnings
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from salespyforce import errors
|
|
18
|
+
from salespyforce.utils import core_utils
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_url_encode_and_decode_round_trip():
|
|
22
|
+
"""This function tests URL encoding and decoding helper functions.
|
|
23
|
+
|
|
24
|
+
.. version-added:: 1.4.0
|
|
25
|
+
"""
|
|
26
|
+
raw_string = "My sample string with spaces & symbols!"
|
|
27
|
+
encoded = core_utils.url_encode(raw_string)
|
|
28
|
+
assert "%26" in encoded and "+" in encoded
|
|
29
|
+
decoded = core_utils.url_decode(encoded)
|
|
30
|
+
assert decoded == raw_string
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_display_warning_emits_userwarning():
|
|
34
|
+
"""This function tests that display_warning emits a UserWarning.
|
|
35
|
+
|
|
36
|
+
.. version-added:: 1.4.0
|
|
37
|
+
"""
|
|
38
|
+
warn_msg = "testing warning"
|
|
39
|
+
with pytest.warns(UserWarning, match=warn_msg):
|
|
40
|
+
core_utils.display_warning(warn_msg)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_get_file_type_detects_json_extension(tmp_path):
|
|
44
|
+
"""This function tests get_file_type with a JSON extension.
|
|
45
|
+
|
|
46
|
+
.. version-added:: 1.4.0
|
|
47
|
+
"""
|
|
48
|
+
json_path = tmp_path / "config.json"
|
|
49
|
+
json_path.write_text('{"key": "value"}')
|
|
50
|
+
assert core_utils.get_file_type(str(json_path)) == "json"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_get_file_type_detects_yaml_extension(tmp_path):
|
|
54
|
+
"""This function tests get_file_type with a YAML extension.
|
|
55
|
+
|
|
56
|
+
.. version-added:: 1.4.0
|
|
57
|
+
"""
|
|
58
|
+
yaml_path = tmp_path / "config.yaml"
|
|
59
|
+
yaml_path.write_text("key: value")
|
|
60
|
+
assert core_utils.get_file_type(str(yaml_path)) == "yaml"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_get_file_type_reads_unknown_extension_with_warning(tmp_path):
|
|
64
|
+
"""This function tests get_file_type fallback detection on unknown extensions.
|
|
65
|
+
|
|
66
|
+
.. version-added:: 1.4.0
|
|
67
|
+
"""
|
|
68
|
+
txt_path = tmp_path / "config.txt"
|
|
69
|
+
txt_path.write_text("# comment line\n{json: true}")
|
|
70
|
+
with warnings.catch_warnings(record=True) as captured_warnings:
|
|
71
|
+
file_type = core_utils.get_file_type(str(txt_path))
|
|
72
|
+
assert file_type == "json"
|
|
73
|
+
assert any(
|
|
74
|
+
warning.category is UserWarning for warning in captured_warnings
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_get_file_type_raises_for_unknown_content(tmp_path):
|
|
79
|
+
"""This function tests get_file_type when content is unrecognized.
|
|
80
|
+
|
|
81
|
+
.. version-added:: 1.4.0
|
|
82
|
+
"""
|
|
83
|
+
bad_path = tmp_path / "config.data"
|
|
84
|
+
bad_path.write_text("plain text content")
|
|
85
|
+
with pytest.warns(UserWarning):
|
|
86
|
+
with pytest.raises(errors.exceptions.UnknownFileTypeError):
|
|
87
|
+
core_utils.get_file_type(str(bad_path))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_get_file_type_raises_for_missing_file():
|
|
91
|
+
"""This function tests get_file_type when a file is missing.
|
|
92
|
+
|
|
93
|
+
.. version-added:: 1.4.0
|
|
94
|
+
"""
|
|
95
|
+
missing_path = "does/not/exist.json"
|
|
96
|
+
with pytest.raises(FileNotFoundError):
|
|
97
|
+
core_utils.get_file_type(missing_path)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_get_random_string_returns_expected_length(monkeypatch):
|
|
101
|
+
"""This function tests get_random_string length and prefix handling.
|
|
102
|
+
|
|
103
|
+
.. version-added:: 1.4.0
|
|
104
|
+
"""
|
|
105
|
+
alphabet = "abc123"
|
|
106
|
+
monkeypatch.setattr(core_utils, "random", core_utils.random)
|
|
107
|
+
monkeypatch.setattr(
|
|
108
|
+
core_utils,
|
|
109
|
+
"string",
|
|
110
|
+
type("DummyString", (), {"ascii_letters": alphabet, "digits": alphabet}),
|
|
111
|
+
)
|
|
112
|
+
result = core_utils.get_random_string(length=5, prefix_string="pre_")
|
|
113
|
+
assert result.startswith("pre_")
|
|
114
|
+
assert len(result) == 5 + len("pre_")
|
|
115
|
+
assert all(char in alphabet for char in result.replace("pre_", ""))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_converts_15_char_id_to_18_char():
|
|
119
|
+
"""This function tests the conversion of a 15-character Salesforce ID into the 18-character equivalent.
|
|
120
|
+
|
|
121
|
+
.. version-added:: 1.4.0
|
|
122
|
+
"""
|
|
123
|
+
id_15 = "ka4PO0000002hby"
|
|
124
|
+
id_18 = core_utils.get_18_char_id(id_15)
|
|
125
|
+
|
|
126
|
+
assert len(id_18) == 18
|
|
127
|
+
assert id_18.startswith(id_15)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_returns_18_char_id_unchanged():
|
|
131
|
+
"""This function tests that an 18-character Salesforce ID is returned unchanged during conversion attempt.
|
|
132
|
+
|
|
133
|
+
.. version-added:: 1.4.0
|
|
134
|
+
"""
|
|
135
|
+
id_18 = "ka4PO0000002hbyYAA"
|
|
136
|
+
assert core_utils.get_18_char_id(id_18) == id_18
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_invalid_id_length_raises_error():
|
|
140
|
+
"""This function tests to ensure passing an invalid Salesforce ID length raises an exception.
|
|
141
|
+
|
|
142
|
+
.. version-added:: 1.4.0
|
|
143
|
+
"""
|
|
144
|
+
with pytest.raises(ValueError):
|
|
145
|
+
core_utils.get_18_char_id("short")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_non_string_id_input_raises_error():
|
|
149
|
+
"""This function tests to ensure passing a non-string to the get_18_char_id function raises an exception.
|
|
150
|
+
|
|
151
|
+
.. version-added:: 1.4.0
|
|
152
|
+
"""
|
|
153
|
+
with pytest.raises(ValueError):
|
|
154
|
+
core_utils.get_18_char_id(12345)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_get_image_ref_id_parses_query_param():
|
|
158
|
+
"""This function tests get_image_ref_id parsing.
|
|
159
|
+
|
|
160
|
+
.. version-added:: 1.4.0
|
|
161
|
+
"""
|
|
162
|
+
image_url = (
|
|
163
|
+
"https://example.force.com/servlet/servlet.ImageServer"
|
|
164
|
+
"?oid=00Dxx0000001gPFEAY&refid=abc123&lastMod=123"
|
|
165
|
+
)
|
|
166
|
+
assert core_utils.get_image_ref_id(image_url) == "abc123"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_download_image_raises_without_input():
|
|
170
|
+
"""This function tests download_image when neither URL nor response is provided.
|
|
171
|
+
|
|
172
|
+
.. version-added:: 1.4.0
|
|
173
|
+
"""
|
|
174
|
+
with pytest.raises(RuntimeError):
|
|
175
|
+
core_utils.download_image()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_download_image_raises_on_bad_status(monkeypatch, tmp_path):
|
|
179
|
+
"""This function tests download_image when the response is unsuccessful.
|
|
180
|
+
|
|
181
|
+
.. version-added:: 1.4.0
|
|
182
|
+
"""
|
|
183
|
+
class DummyResponse:
|
|
184
|
+
status_code = 404
|
|
185
|
+
content = b""
|
|
186
|
+
|
|
187
|
+
monkeypatch.setattr(core_utils.requests, "get", lambda *_args, **_kwargs: DummyResponse())
|
|
188
|
+
with pytest.raises(RuntimeError):
|
|
189
|
+
core_utils.download_image(image_url="https://example.com/image", file_path=str(tmp_path))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_download_image_writes_response_content(tmp_path):
|
|
193
|
+
"""This function tests download_image writing provided response content.
|
|
194
|
+
|
|
195
|
+
.. version-added:: 1.4.0
|
|
196
|
+
"""
|
|
197
|
+
file_path = tmp_path / "images"
|
|
198
|
+
os.makedirs(file_path, exist_ok=True)
|
|
199
|
+
|
|
200
|
+
class DummyResponse:
|
|
201
|
+
status_code = 200
|
|
202
|
+
content = b"image-bytes"
|
|
203
|
+
|
|
204
|
+
destination = core_utils.download_image(
|
|
205
|
+
image_url="https://example.com/image",
|
|
206
|
+
file_name="logo.png",
|
|
207
|
+
file_path=str(file_path),
|
|
208
|
+
response=DummyResponse(),
|
|
209
|
+
)
|
|
210
|
+
saved_path = pathlib.Path(destination)
|
|
211
|
+
assert saved_path.name == "logo.png"
|
|
212
|
+
assert saved_path.parent == file_path
|
|
213
|
+
assert saved_path.read_bytes() == DummyResponse.content
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_download_image_generates_file_name(monkeypatch, tmp_path):
|
|
217
|
+
"""This function tests download_image generates a file name when not provided.
|
|
218
|
+
|
|
219
|
+
.. version-added:: 1.4.0
|
|
220
|
+
"""
|
|
221
|
+
class DummyResponse:
|
|
222
|
+
status_code = 200
|
|
223
|
+
content = b"bytes"
|
|
224
|
+
|
|
225
|
+
monkeypatch.setattr(core_utils, "get_random_string", lambda *_args, **_kwargs: "image_stub")
|
|
226
|
+
destination = core_utils.download_image(
|
|
227
|
+
image_url="https://example.com/image",
|
|
228
|
+
file_path=str(tmp_path),
|
|
229
|
+
response=DummyResponse(),
|
|
230
|
+
extension="png",
|
|
231
|
+
)
|
|
232
|
+
assert destination.startswith(f"{tmp_path}{os.sep}image_stub")
|
|
233
|
+
assert destination.endswith("png")
|
|
234
|
+
assert pathlib.Path(destination).read_bytes() == DummyResponse.content
|
|
@@ -1,49 +1,62 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# bandit: skip=B101
|
|
2
3
|
"""
|
|
3
4
|
:Module: salespyforce.utils.tests.test_instantiate_object
|
|
4
5
|
:Synopsis: This module is used by pytest to test instantiating the core object
|
|
5
6
|
:Created By: Jeff Shurtliff
|
|
6
7
|
:Last Modified: Jeff Shurtliff
|
|
7
|
-
:Modified Date:
|
|
8
|
-
"""
|
|
8
|
+
:Modified Date: 20 Dec 2025
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
These tests rely on the ``salesforce_unit`` fixture defined in
|
|
11
|
+
``conftest.py`` to keep them fast and deterministic. When you want to
|
|
12
|
+
verify behavior against a real org, add ``@pytest.mark.integration``
|
|
13
|
+
and switch the fixture parameter to ``salesforce_integration``.
|
|
14
|
+
"""
|
|
11
15
|
|
|
12
16
|
|
|
13
|
-
def test_instantiate_core_object():
|
|
17
|
+
def test_instantiate_core_object(salesforce_unit):
|
|
14
18
|
"""This function tests the ability to instantiate the core object.
|
|
15
19
|
|
|
16
|
-
..
|
|
20
|
+
.. version-added:: 1.1.0
|
|
21
|
+
|
|
22
|
+
.. version-changed:: 1.4.0
|
|
23
|
+
The function now utilizes the ``salesforce_unit`` fixture.
|
|
17
24
|
"""
|
|
18
|
-
sfdc_object =
|
|
25
|
+
sfdc_object = salesforce_unit
|
|
19
26
|
assert 'force.com' in sfdc_object.base_url
|
|
20
27
|
|
|
21
28
|
|
|
22
|
-
def test_get_api_versions():
|
|
29
|
+
def test_get_api_versions(salesforce_unit):
|
|
23
30
|
"""This function tests the get_api_versions() method in the core object.
|
|
24
31
|
|
|
25
|
-
..
|
|
32
|
+
.. version-added:: 1.1.0
|
|
33
|
+
|
|
34
|
+
.. version-changed:: 1.4.0
|
|
35
|
+
The function now utilizes the ``salesforce_unit`` fixture.
|
|
26
36
|
"""
|
|
27
|
-
|
|
28
|
-
api_versions = sfdc_object.get_api_versions()
|
|
37
|
+
api_versions = salesforce_unit.get_api_versions()
|
|
29
38
|
assert isinstance(api_versions, list) and 'version' in api_versions[0]
|
|
30
39
|
|
|
31
40
|
|
|
32
|
-
def test_get_rest_resources():
|
|
41
|
+
def test_get_rest_resources(salesforce_unit):
|
|
33
42
|
"""This function tests the get_rest_resources() method in the core object.
|
|
34
43
|
|
|
35
|
-
..
|
|
44
|
+
.. version-added:: 1.1.0
|
|
45
|
+
|
|
46
|
+
.. version-changed:: 1.4.0
|
|
47
|
+
The function now utilizes the ``salesforce_unit`` fixture.
|
|
36
48
|
"""
|
|
37
|
-
|
|
38
|
-
rest_resources = sfdc_object.get_rest_resources()
|
|
49
|
+
rest_resources = salesforce_unit.get_rest_resources()
|
|
39
50
|
assert 'metadata' in rest_resources
|
|
40
51
|
|
|
41
52
|
|
|
42
|
-
def test_get_org_limits():
|
|
53
|
+
def test_get_org_limits(salesforce_unit):
|
|
43
54
|
"""This function tests the get_org_limits() method in the core object.
|
|
44
55
|
|
|
45
|
-
..
|
|
56
|
+
.. version-added:: 1.1.0
|
|
57
|
+
|
|
58
|
+
.. version-changed:: 1.4.0
|
|
59
|
+
The function now utilizes the ``salesforce_unit`` fixture.
|
|
46
60
|
"""
|
|
47
|
-
|
|
48
|
-
org_limits = sfdc_object.get_org_limits()
|
|
61
|
+
org_limits = salesforce_unit.get_org_limits()
|
|
49
62
|
assert 'DailyApiRequests' in org_limits
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# bandit: skip=B101
|
|
3
|
+
"""
|
|
4
|
+
:Module: salespyforce.utils.tests.test_log_utils
|
|
5
|
+
:Synopsis: This module is used by pytest to test the logging functionality
|
|
6
|
+
:Created By: Jeff Shurtliff
|
|
7
|
+
:Last Modified: Jeff Shurtliff
|
|
8
|
+
:Modified Date: 31 Dec 2025
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from salespyforce.utils import log_utils
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _cleanup_logger(logger: logging.Logger) -> None:
|
|
20
|
+
"""This function removes and closes handlers for a logger.
|
|
21
|
+
|
|
22
|
+
.. version-added:: 1.4.0
|
|
23
|
+
|
|
24
|
+
:param logger: The logger instance to clean up
|
|
25
|
+
:type logger: class[logging.Logger]
|
|
26
|
+
:returns: None
|
|
27
|
+
"""
|
|
28
|
+
for handler in list(logger.handlers):
|
|
29
|
+
logger.removeHandler(handler)
|
|
30
|
+
handler.close()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_initialize_logging_defaults_to_info_level() -> None:
|
|
34
|
+
"""This function verifies that initialize_logging() defaults logger level to INFO.
|
|
35
|
+
|
|
36
|
+
.. version-added:: 1.4.0
|
|
37
|
+
|
|
38
|
+
:returns: None
|
|
39
|
+
"""
|
|
40
|
+
logger_name = "salespyforce.test.default.info"
|
|
41
|
+
logger = log_utils.initialize_logging(logger_name)
|
|
42
|
+
try:
|
|
43
|
+
assert logger.level == logging.INFO
|
|
44
|
+
finally:
|
|
45
|
+
_cleanup_logger(logger)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_initialize_logging_applies_default_level_to_console_handler(caplog: pytest.LogCaptureFixture) -> None:
|
|
49
|
+
"""This function ensures console handlers inherit the default INFO level.
|
|
50
|
+
|
|
51
|
+
.. version-added:: 1.4.0
|
|
52
|
+
|
|
53
|
+
:param caplog: Pytest fixture capturing log records for assertions
|
|
54
|
+
:type caplog: class[pytest.LogCaptureFixture]
|
|
55
|
+
:returns: None
|
|
56
|
+
"""
|
|
57
|
+
logger_name = "salespyforce.test.console.default"
|
|
58
|
+
logger = log_utils.initialize_logging(logger_name, console_output=True)
|
|
59
|
+
message = "default info message"
|
|
60
|
+
try:
|
|
61
|
+
with caplog.at_level(logging.INFO, logger=logger_name):
|
|
62
|
+
logger.info(message)
|
|
63
|
+
|
|
64
|
+
stdout_handlers = [
|
|
65
|
+
handler
|
|
66
|
+
for handler in logger.handlers
|
|
67
|
+
if isinstance(handler, logging.StreamHandler)
|
|
68
|
+
and getattr(handler, "stream", None) is sys.stdout
|
|
69
|
+
]
|
|
70
|
+
assert stdout_handlers
|
|
71
|
+
for handler in stdout_handlers:
|
|
72
|
+
assert handler.level == logging.INFO
|
|
73
|
+
|
|
74
|
+
assert any(
|
|
75
|
+
record.levelno == logging.INFO and record.message == message
|
|
76
|
+
for record in caplog.records
|
|
77
|
+
)
|
|
78
|
+
finally:
|
|
79
|
+
_cleanup_logger(logger)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# bandit: skip=B101
|
|
2
3
|
"""
|
|
3
4
|
:Module: salespyforce.utils.tests.test_sobjects
|
|
4
5
|
:Synopsis: This module is used by pytest to test basic sObject-related methods
|
|
5
6
|
:Created By: Jeff Shurtliff
|
|
6
7
|
:Last Modified: Jeff Shurtliff
|
|
7
|
-
:Modified Date:
|
|
8
|
+
:Modified Date: 20 Dec 2025
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
import requests
|
|
@@ -12,41 +13,47 @@ import requests
|
|
|
12
13
|
from . import resources
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def test_get_all_sobjects():
|
|
16
|
+
def test_get_all_sobjects(salesforce_unit):
|
|
16
17
|
"""This function tests the get_all_sobjects() method in the core object.
|
|
17
18
|
|
|
18
19
|
.. versionadded:: 1.1.0
|
|
20
|
+
|
|
21
|
+
.. version-changed:: 1.4.0
|
|
22
|
+
The function now utilizes the ``salesforce_unit`` fixture.
|
|
19
23
|
"""
|
|
20
|
-
|
|
21
|
-
all_sobjects = sfdc_object.get_all_sobjects()
|
|
24
|
+
all_sobjects = salesforce_unit.get_all_sobjects()
|
|
22
25
|
assert 'sobjects' in all_sobjects
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
def test_get_and_describe_sobject():
|
|
28
|
+
def test_get_and_describe_sobject(salesforce_unit):
|
|
26
29
|
"""This function tests the get_sobject() and describe_object() methods in the core object.
|
|
27
30
|
|
|
28
31
|
.. versionadded:: 1.1.0
|
|
29
|
-
"""
|
|
30
|
-
# Instantiate the core object
|
|
31
|
-
sfdc_object = resources.get_core_object()
|
|
32
32
|
|
|
33
|
+
.. version-changed:: 1.4.0
|
|
34
|
+
The function now utilizes the ``salesforce_unit`` fixture.
|
|
35
|
+
"""
|
|
33
36
|
# Test the default query (non-describe)
|
|
34
|
-
account_sobject =
|
|
37
|
+
account_sobject = salesforce_unit.get_sobject('Account')
|
|
35
38
|
assert 'objectDescribe' in account_sobject
|
|
36
39
|
|
|
37
40
|
# Test with describe enabled
|
|
38
|
-
account_sobject_describe =
|
|
41
|
+
account_sobject_describe = salesforce_unit.get_sobject('Account', describe=True)
|
|
39
42
|
assert 'activateable' in account_sobject_describe
|
|
40
43
|
|
|
41
44
|
# Test the describe_object() method
|
|
42
|
-
account_describe =
|
|
45
|
+
account_describe = salesforce_unit.describe_object('Account')
|
|
43
46
|
assert 'activateable' in account_describe
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def test_create_record(monkeypatch):
|
|
47
|
-
|
|
48
|
-
sfdc_object = resources.get_core_object()
|
|
49
|
+
def test_create_record(monkeypatch, salesforce_unit):
|
|
50
|
+
"""This function tests creating a Salesforce object record.
|
|
49
51
|
|
|
52
|
+
.. versionadded:: 1.1.0
|
|
53
|
+
|
|
54
|
+
.. version-changed:: 1.4.0
|
|
55
|
+
The function now utilizes the ``salesforce_unit`` fixture.
|
|
56
|
+
"""
|
|
50
57
|
# Overwrite the requests.post functionality with the mock_success_post() function
|
|
51
58
|
monkeypatch.setattr(requests, 'post', resources.mock_success_post)
|
|
52
59
|
|
|
@@ -54,5 +61,5 @@ def test_create_record(monkeypatch):
|
|
|
54
61
|
payload = {
|
|
55
62
|
"Name": "Express Logistics and Transport"
|
|
56
63
|
}
|
|
57
|
-
response =
|
|
64
|
+
response = salesforce_unit.create_sobject_record('Account', payload)
|
|
58
65
|
assert 'success' in response and response.get('success') is True and 'id' in response
|