salespyforce 1.4.0.dev0__tar.gz → 1.4.0.dev1__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.
Files changed (28) hide show
  1. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/PKG-INFO +6 -2
  2. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/README.md +5 -1
  3. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/pyproject.toml +1 -1
  4. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/log_utils.py +10 -9
  5. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/conftest.py +202 -0
  6. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_core_utils.py +195 -0
  7. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_instantiate_object.py +62 -0
  8. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_log_utils.py +79 -0
  9. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/test_sobjects.py +22 -15
  10. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/test_soql.py +8 -8
  11. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/test_sosl.py +9 -9
  12. salespyforce-1.4.0.dev1/src/salespyforce/utils/tests/test_version_utils.py +70 -0
  13. salespyforce-1.4.0.dev0/src/salespyforce/utils/tests/test_instantiate_object.py +0 -49
  14. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/LICENSE +0 -0
  15. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/__init__.py +0 -0
  16. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/api.py +0 -0
  17. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/chatter.py +0 -0
  18. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/core.py +0 -0
  19. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/errors/__init__.py +0 -0
  20. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/errors/exceptions.py +0 -0
  21. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/errors/handlers.py +0 -0
  22. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/knowledge.py +0 -0
  23. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/__init__.py +0 -0
  24. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/core_utils.py +0 -0
  25. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/helper.py +0 -0
  26. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/__init__.py +0 -0
  27. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/tests/resources.py +0 -0
  28. {salespyforce-1.4.0.dev0 → salespyforce-1.4.0.dev1}/src/salespyforce/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: salespyforce
3
- Version: 1.4.0.dev0
3
+ Version: 1.4.0.dev1
4
4
  Summary: A Python toolset for performing Salesforce API calls
5
5
  License: MIT License
6
6
 
@@ -63,7 +63,7 @@ A Python toolset for performing Salesforce API calls
63
63
  <td>Latest Beta/RC Release</td>
64
64
  <td>
65
65
  <a href='https://pypi.org/project/salespyforce/#history'>
66
- <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.3.0rc3-blue">
66
+ <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.dev1-blue">
67
67
  </a>
68
68
  </td>
69
69
  </tr>
@@ -140,6 +140,10 @@ A Python toolset for performing Salesforce API calls
140
140
  </tr>
141
141
  </table>
142
142
 
143
+ ## Overview
144
+ salespyforce is a Python toolkit focused on interacting with Salesforce APIs, with a primary `Salesforce` class
145
+ that centralizes authentication, version selection, and access to helper feature sets (Chatter, Knowledge).
146
+
143
147
  ## Installation
144
148
  The package can be installed via pip using the syntax below.
145
149
 
@@ -14,7 +14,7 @@ A Python toolset for performing Salesforce API calls
14
14
  <td>Latest Beta/RC Release</td>
15
15
  <td>
16
16
  <a href='https://pypi.org/project/salespyforce/#history'>
17
- <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.3.0rc3-blue">
17
+ <img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.dev1-blue">
18
18
  </a>
19
19
  </td>
20
20
  </tr>
@@ -91,6 +91,10 @@ A Python toolset for performing Salesforce API calls
91
91
  </tr>
92
92
  </table>
93
93
 
94
+ ## Overview
95
+ salespyforce is a Python toolkit focused on interacting with Salesforce APIs, with a primary `Salesforce` class
96
+ that centralizes authentication, version selection, and access to helper feature sets (Chatter, Knowledge).
97
+
94
98
  ## Installation
95
99
  The package can be installed via pip using the syntax below.
96
100
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "salespyforce"
3
- version = "1.4.0.dev0"
3
+ version = "1.4.0.dev1"
4
4
  description = "A Python toolset for performing Salesforce API calls"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9,<4.0"
@@ -6,7 +6,7 @@
6
6
  :Example: ``logger = log_utils.initialize_logging(__name__)``
7
7
  :Created By: Jeff Shurtliff
8
8
  :Last Modified: Jeff Shurtliff
9
- :Modified Date: 22 Jan 2023
9
+ :Modified Date: 31 Dec 2025
10
10
  """
11
11
 
12
12
  import os
@@ -59,7 +59,7 @@ class LessThanFilter(logging.Filter):
59
59
  self.max_level = exclusive_maximum
60
60
 
61
61
  def filter(self, record):
62
- """This method returns a Boolean integer value indicating whether or not a message should be logged.
62
+ """This method returns a Boolean integer value indicating whether a message should be logged.
63
63
 
64
64
  .. note:: A non-zero return indicates that the message will be logged.
65
65
  """
@@ -69,6 +69,9 @@ class LessThanFilter(logging.Filter):
69
69
  def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _console_level, _syslog_level):
70
70
  """This function applies default values to the configuration settings if not explicitly defined.
71
71
 
72
+ .. version-changed:: 1.4.0
73
+ The default logging level is now defined.
74
+
72
75
  :param _logger_name: The name of the logger instance
73
76
  :type _logger_name: str, None
74
77
  :param _formatter: The log format to utilize for the logger instance
@@ -79,8 +82,9 @@ def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _
79
82
  :type _log_level: str, None
80
83
  :returns: The values that will be used for the configuration settings
81
84
  """
85
+ _default_log_level = LOGGING_DEFAULTS.get('log_level')
82
86
  _log_levels = {
83
- 'general': _log_level,
87
+ 'general': _log_level or _default_log_level,
84
88
  'file': _file_level,
85
89
  'console': _console_level,
86
90
  'syslog': _syslog_level,
@@ -90,12 +94,9 @@ def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _
90
94
  for _log_type in _log_levels:
91
95
  _log_levels[_log_type] = 'debug'
92
96
  else:
93
- if _log_level:
94
- for _lvl_type, _lvl_value in _log_levels.items():
95
- if _lvl_type != 'general' and _lvl_value is None:
96
- _log_levels[_lvl_type] = _log_level
97
- else:
98
- _log_level = LOGGING_DEFAULTS.get('log_level')
97
+ for _lvl_type, _lvl_value in _log_levels.items():
98
+ if _lvl_value is None:
99
+ _log_levels[_lvl_type] = _log_levels['general']
99
100
  if _formatter and isinstance(_formatter, str):
100
101
  _formatter = logging.Formatter(_formatter)
101
102
  _formatter = LOGGING_DEFAULTS.get('formatter') if not _formatter else _formatter
@@ -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,195 @@
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: 20 Dec 2025
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_get_image_ref_id_parses_query_param():
119
+ """This function tests get_image_ref_id parsing.
120
+
121
+ .. version-added:: 1.4.0
122
+ """
123
+ image_url = (
124
+ "https://example.force.com/servlet/servlet.ImageServer"
125
+ "?oid=00Dxx0000001gPFEAY&refid=abc123&lastMod=123"
126
+ )
127
+ assert core_utils.get_image_ref_id(image_url) == "abc123"
128
+
129
+
130
+ def test_download_image_raises_without_input():
131
+ """This function tests download_image when neither URL nor response is provided.
132
+
133
+ .. version-added:: 1.4.0
134
+ """
135
+ with pytest.raises(RuntimeError):
136
+ core_utils.download_image()
137
+
138
+
139
+ def test_download_image_raises_on_bad_status(monkeypatch, tmp_path):
140
+ """This function tests download_image when the response is unsuccessful.
141
+
142
+ .. version-added:: 1.4.0
143
+ """
144
+ class DummyResponse:
145
+ status_code = 404
146
+ content = b""
147
+
148
+ monkeypatch.setattr(core_utils.requests, "get", lambda *_args, **_kwargs: DummyResponse())
149
+ with pytest.raises(RuntimeError):
150
+ core_utils.download_image(image_url="https://example.com/image", file_path=str(tmp_path))
151
+
152
+
153
+ def test_download_image_writes_response_content(tmp_path):
154
+ """This function tests download_image writing provided response content.
155
+
156
+ .. version-added:: 1.4.0
157
+ """
158
+ file_path = tmp_path / "images"
159
+ os.makedirs(file_path, exist_ok=True)
160
+
161
+ class DummyResponse:
162
+ status_code = 200
163
+ content = b"image-bytes"
164
+
165
+ destination = core_utils.download_image(
166
+ image_url="https://example.com/image",
167
+ file_name="logo.png",
168
+ file_path=str(file_path),
169
+ response=DummyResponse(),
170
+ )
171
+ saved_path = pathlib.Path(destination)
172
+ assert saved_path.name == "logo.png"
173
+ assert saved_path.parent == file_path
174
+ assert saved_path.read_bytes() == DummyResponse.content
175
+
176
+
177
+ def test_download_image_generates_file_name(monkeypatch, tmp_path):
178
+ """This function tests download_image generates a file name when not provided.
179
+
180
+ .. version-added:: 1.4.0
181
+ """
182
+ class DummyResponse:
183
+ status_code = 200
184
+ content = b"bytes"
185
+
186
+ monkeypatch.setattr(core_utils, "get_random_string", lambda *_args, **_kwargs: "image_stub")
187
+ destination = core_utils.download_image(
188
+ image_url="https://example.com/image",
189
+ file_path=str(tmp_path),
190
+ response=DummyResponse(),
191
+ extension="png",
192
+ )
193
+ assert destination.startswith(f"{tmp_path}{os.sep}image_stub")
194
+ assert destination.endswith("png")
195
+ assert pathlib.Path(destination).read_bytes() == DummyResponse.content
@@ -0,0 +1,62 @@
1
+ # -*- coding: utf-8 -*-
2
+ # bandit: skip=B101
3
+ """
4
+ :Module: salespyforce.utils.tests.test_instantiate_object
5
+ :Synopsis: This module is used by pytest to test instantiating the core object
6
+ :Created By: Jeff Shurtliff
7
+ :Last Modified: Jeff Shurtliff
8
+ :Modified Date: 20 Dec 2025
9
+
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
+ """
15
+
16
+
17
+ def test_instantiate_core_object(salesforce_unit):
18
+ """This function tests the ability to instantiate the core object.
19
+
20
+ .. version-added:: 1.1.0
21
+
22
+ .. version-changed:: 1.4.0
23
+ The function now utilizes the ``salesforce_unit`` fixture.
24
+ """
25
+ sfdc_object = salesforce_unit
26
+ assert 'force.com' in sfdc_object.base_url
27
+
28
+
29
+ def test_get_api_versions(salesforce_unit):
30
+ """This function tests the get_api_versions() method in the core object.
31
+
32
+ .. version-added:: 1.1.0
33
+
34
+ .. version-changed:: 1.4.0
35
+ The function now utilizes the ``salesforce_unit`` fixture.
36
+ """
37
+ api_versions = salesforce_unit.get_api_versions()
38
+ assert isinstance(api_versions, list) and 'version' in api_versions[0]
39
+
40
+
41
+ def test_get_rest_resources(salesforce_unit):
42
+ """This function tests the get_rest_resources() method in the core object.
43
+
44
+ .. version-added:: 1.1.0
45
+
46
+ .. version-changed:: 1.4.0
47
+ The function now utilizes the ``salesforce_unit`` fixture.
48
+ """
49
+ rest_resources = salesforce_unit.get_rest_resources()
50
+ assert 'metadata' in rest_resources
51
+
52
+
53
+ def test_get_org_limits(salesforce_unit):
54
+ """This function tests the get_org_limits() method in the core object.
55
+
56
+ .. version-added:: 1.1.0
57
+
58
+ .. version-changed:: 1.4.0
59
+ The function now utilizes the ``salesforce_unit`` fixture.
60
+ """
61
+ org_limits = salesforce_unit.get_org_limits()
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: 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: 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: 29 May 2023
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
- sfdc_object = resources.get_core_object()
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 = sfdc_object.get_sobject('Account')
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 = sfdc_object.get_sobject('Account', describe=True)
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 = sfdc_object.describe_object('Account')
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
- # Instantiate the core object
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 = sfdc_object.create_sobject_record('Account', payload)
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
@@ -1,23 +1,23 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # bandit: skip=B101
2
3
  """
3
- :Module: salespyforce.utils.tests.test_sobjects
4
+ :Module: salespyforce.utils.tests.test_soql
4
5
  :Synopsis: This module is used by pytest to test performing SOQL queries
5
6
  :Created By: Jeff Shurtliff
6
7
  :Last Modified: Jeff Shurtliff
7
- :Modified Date: 29 May 2023
8
+ :Modified Date: 20 Dec 2025
8
9
  """
9
10
 
10
- from . import resources
11
11
 
12
-
13
- def test_soql_query():
12
+ def test_soql_query(salesforce_unit):
14
13
  """This function tests the ability to perform a SOQL query.
15
14
 
16
15
  .. versionadded:: 1.1.0
16
+
17
+ .. version-changed:: 1.4.0
18
+ The function now utilizes the ``salesforce_unit`` fixture.
17
19
  """
18
- sfdc_object = resources.get_core_object()
19
20
  soql_statement = 'SELECT Id FROM Account LIMIT 1'
20
- soql_response = sfdc_object.soql_query(soql_statement)
21
+ soql_response = salesforce_unit.soql_query(soql_statement)
21
22
  assert 'done' in soql_response and soql_response.get('done') is True
22
23
  assert 'totalSize' in soql_response and 'records' in soql_response
23
-
@@ -1,10 +1,11 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # bandit: skip=B101
2
3
  """
3
- :Module: salespyforce.utils.tests.test_sobjects
4
- :Synopsis: This module is used by pytest to test performing SOQL queries
4
+ :Module: salespyforce.utils.tests.test_sosl
5
+ :Synopsis: This module is used by pytest to test performing SOSL queries
5
6
  :Created By: Jeff Shurtliff
6
7
  :Last Modified: Jeff Shurtliff
7
- :Modified Date: 29 May 2023
8
+ :Modified Date: 20 Dec 2025
8
9
  """
9
10
 
10
11
  import requests
@@ -12,18 +13,17 @@ import requests
12
13
  from . import resources
13
14
 
14
15
 
15
- def test_search_string(monkeypatch):
16
+ def test_search_string(monkeypatch, salesforce_unit):
16
17
  """This function tests the ability to search for a string using an SOSL query.
17
18
 
18
19
  .. versionadded:: 1.1.0
19
- """
20
- # Instantiate the core object
21
- sfdc_object = resources.get_core_object()
22
20
 
21
+ .. version-changed:: 1.4.0
22
+ The function now utilizes the ``salesforce_unit`` fixture.
23
+ """
23
24
  # Overwrite the requests.post functionality with the mock_success_post() function
24
25
  monkeypatch.setattr(requests, 'get', resources.mock_sosl_get)
25
26
 
26
27
  # Perform the mock API call
27
- result = sfdc_object.search_string('Account')
28
+ result = salesforce_unit.search_string('Account')
28
29
  assert 'searchRecords' in result
29
-
@@ -0,0 +1,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ # bandit: skip=B101
3
+ """
4
+ :Module: salespyforce.utils.tests.test_version_utils
5
+ :Synopsis: Pytest suite for salespyforce.utils.version helpers
6
+ :Created By: Jeff Shurtliff
7
+ :Last Modified: Jeff Shurtliff
8
+ :Modified Date: 20 Dec 2025
9
+ """
10
+
11
+ import importlib
12
+ import importlib.metadata as importlib_metadata
13
+
14
+ from salespyforce.utils import version as version_utils
15
+
16
+
17
+ def test_get_full_version_returns_metadata(monkeypatch):
18
+ """This function tests get_full_version returns the package metadata."""
19
+ expected_version = "1.2.3"
20
+ monkeypatch.setattr(
21
+ version_utils, "version", lambda package: expected_version
22
+ )
23
+
24
+ assert version_utils.get_full_version() == expected_version
25
+
26
+
27
+ def test_get_full_version_handles_missing_package(monkeypatch, caplog):
28
+ """This function tests get_full_version fallback when package is missing."""
29
+ def raise_not_found(_package):
30
+ raise version_utils.PackageNotFoundError("salespyforce")
31
+
32
+ monkeypatch.setattr(version_utils, "version", raise_not_found)
33
+
34
+ with caplog.at_level("WARNING"):
35
+ version_string = version_utils.get_full_version()
36
+
37
+ assert version_string == "0.0.0"
38
+ assert any("falling back to '0.0.0'" in message for message in caplog.messages)
39
+
40
+
41
+ def test_get_major_minor_version_returns_two_components(monkeypatch):
42
+ """This function tests extracting the major.minor version components."""
43
+ monkeypatch.setattr(
44
+ version_utils, "get_full_version", lambda: "2.3.4"
45
+ )
46
+
47
+ assert version_utils.get_major_minor_version() == "2.3"
48
+
49
+
50
+ def test_get_major_minor_version_returns_full_when_missing_minor(monkeypatch):
51
+ """This function tests get_major_minor_version with a single-part version."""
52
+ monkeypatch.setattr(version_utils, "get_full_version", lambda: "7")
53
+
54
+ assert version_utils.get_major_minor_version() == "7"
55
+
56
+
57
+ def test_dunder_version_matches_full_version(monkeypatch):
58
+ """This function tests __version__ initialization uses get_full_version."""
59
+ real_version_function = importlib_metadata.version
60
+ expected_version = "9.8.7"
61
+ monkeypatch.setattr(
62
+ importlib_metadata, "version", lambda package: expected_version
63
+ )
64
+
65
+ reloaded_module = importlib.reload(version_utils)
66
+ assert reloaded_module.__version__ == expected_version
67
+ assert reloaded_module.get_full_version() == expected_version
68
+
69
+ importlib_metadata.version = real_version_function
70
+ importlib.reload(version_utils)
@@ -1,49 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- :Module: salespyforce.utils.tests.test_instantiate_object
4
- :Synopsis: This module is used by pytest to test instantiating the core object
5
- :Created By: Jeff Shurtliff
6
- :Last Modified: Jeff Shurtliff
7
- :Modified Date: 29 May 2023
8
- """
9
-
10
- from . import resources
11
-
12
-
13
- def test_instantiate_core_object():
14
- """This function tests the ability to instantiate the core object.
15
-
16
- .. versionadded:: 1.1.0
17
- """
18
- sfdc_object = resources.get_core_object()
19
- assert 'force.com' in sfdc_object.base_url
20
-
21
-
22
- def test_get_api_versions():
23
- """This function tests the get_api_versions() method in the core object.
24
-
25
- .. versionadded:: 1.1.0
26
- """
27
- sfdc_object = resources.get_core_object()
28
- api_versions = sfdc_object.get_api_versions()
29
- assert isinstance(api_versions, list) and 'version' in api_versions[0]
30
-
31
-
32
- def test_get_rest_resources():
33
- """This function tests the get_rest_resources() method in the core object.
34
-
35
- .. versionadded:: 1.1.0
36
- """
37
- sfdc_object = resources.get_core_object()
38
- rest_resources = sfdc_object.get_rest_resources()
39
- assert 'metadata' in rest_resources
40
-
41
-
42
- def test_get_org_limits():
43
- """This function tests the get_org_limits() method in the core object.
44
-
45
- .. versionadded:: 1.1.0
46
- """
47
- sfdc_object = resources.get_core_object()
48
- org_limits = sfdc_object.get_org_limits()
49
- assert 'DailyApiRequests' in org_limits