sfq 0.0.37__tar.gz → 0.0.38__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.37 → sfq-0.0.38}/.github/workflows/publish.yml +1 -2
- {sfq-0.0.37 → sfq-0.0.38}/PKG-INFO +1 -1
- {sfq-0.0.37 → sfq-0.0.38}/pyproject.toml +1 -1
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/__init__.py +5 -4
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/http_client.py +1 -1
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/utils.py +66 -11
- sfq-0.0.38/tests/test_records_to_html.py +153 -0
- {sfq-0.0.37 → sfq-0.0.38}/uv.lock +1 -1
- sfq-0.0.37/tests/test_records_to_html.py +0 -45
- {sfq-0.0.37 → sfq-0.0.38}/.gitignore +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/.python-version +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/README.md +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/_cometd.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/auth.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/crud.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/debug_cleanup.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/exceptions.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/py.typed +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/query.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/src/sfq/soap.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_complex_nested.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_complex_nested_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_empty_list.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_empty_list_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_int_float_bool.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_int_float_bool_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_list_value.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_list_value_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_multiple_dicts.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_multiple_dicts_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_nested_dict.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_nested_dict_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_none_value.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_none_value_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_other_types.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_other_types_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_sample_report.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_sample_report_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_single_flat_dict.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_single_flat_dict_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_typecastable_keys_bool.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_typecastable_keys_bool_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_typecastable_keys_float.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_typecastable_keys_float_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_typecastable_keys_int.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/html/test_typecastable_keys_int_styled.html +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_auth.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_cdelete.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_compatibility.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_cquery.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_create.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_crud.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_crud_e2e.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_cupdate.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_debug_cleanup_e2e.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_debug_cleanup_unit.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_http_client.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_limits_api.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_log_trace_redact.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_open_frontdoor.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_query.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_query_client.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_query_e2e.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_query_integration.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_soap.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_soap_batch_operation.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_static_resources.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_utils.py +0 -0
- {sfq-0.0.37 → sfq-0.0.38}/tests/test_utils_html_table.py +0 -0
@@ -76,7 +76,6 @@ jobs:
|
|
76
76
|
|
77
77
|
echo "Updating src/sfq/__init__.py user_agent to $VERSION"
|
78
78
|
sed -i -E "s/(user_agent: str = \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\")/\1$VERSION\2/" src/sfq/__init__.py
|
79
|
-
sed -i -E "s/(user_agent: str = \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\")/\1$VERSION\2/" src/sfq/http_client.py
|
80
79
|
sed -i -E "s/(default is \"sfq\/)[0-9]+\.[0-9]+\.[0-9]+(\")/\1$VERSION\2/" src/sfq/__init__.py
|
81
80
|
|
82
81
|
echo "Updating src/sfq/__init__.py __version__ to $VERSION"
|
@@ -100,7 +99,7 @@ jobs:
|
|
100
99
|
run: |
|
101
100
|
git config user.name "github-actions[bot]"
|
102
101
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
103
|
-
git add pyproject.toml uv.lock src/sfq/__init__.py
|
102
|
+
git add pyproject.toml uv.lock src/sfq/__init__.py src/sfq/http_client.py
|
104
103
|
git commit -m "CI: bump version to ${{ steps.get_version.outputs.version }}"
|
105
104
|
git push
|
106
105
|
|
@@ -43,7 +43,7 @@ __all__ = [
|
|
43
43
|
"__version__",
|
44
44
|
]
|
45
45
|
|
46
|
-
__version__ = "0.0.
|
46
|
+
__version__ = "0.0.38"
|
47
47
|
"""
|
48
48
|
### `__version__`
|
49
49
|
|
@@ -67,7 +67,7 @@ class SFAuth:
|
|
67
67
|
access_token: Optional[str] = None,
|
68
68
|
token_expiration_time: Optional[float] = None,
|
69
69
|
token_lifetime: int = 15 * 60,
|
70
|
-
user_agent: str = "sfq/0.0.
|
70
|
+
user_agent: str = "sfq/0.0.38",
|
71
71
|
sforce_client: str = "_auto",
|
72
72
|
proxy: str = "_auto",
|
73
73
|
) -> None:
|
@@ -132,7 +132,7 @@ class SFAuth:
|
|
132
132
|
self._debug_cleanup = DebugCleanup(sf_auth=self)
|
133
133
|
|
134
134
|
# Store version information
|
135
|
-
self.__version__ = "0.0.
|
135
|
+
self.__version__ = "0.0.38"
|
136
136
|
"""
|
137
137
|
### `__version__`
|
138
138
|
|
@@ -565,6 +565,7 @@ class SFAuth:
|
|
565
565
|
def records_to_html_table(
|
566
566
|
self,
|
567
567
|
items: List[Dict[str, Any]],
|
568
|
+
headers: Dict[str, str] = None,
|
568
569
|
styled: bool = False,
|
569
570
|
) -> str:
|
570
571
|
"""
|
@@ -576,4 +577,4 @@ class SFAuth:
|
|
576
577
|
"""
|
577
578
|
if "records" in items:
|
578
579
|
items = items["records"]
|
579
|
-
return records_to_html_table(items, styled=styled)
|
580
|
+
return records_to_html_table(items, headers=headers, styled=styled)
|
@@ -197,9 +197,7 @@ def extract_org_and_user_ids(token_id_url: str) -> Tuple[str, str]:
|
|
197
197
|
raise ValueError(f"Invalid token ID URL format: {token_id_url}")
|
198
198
|
|
199
199
|
|
200
|
-
def dicts_to_html_table(
|
201
|
-
items: List[Dict[str, Any]], styled: bool = False
|
202
|
-
) -> str:
|
200
|
+
def dicts_to_html_table(items: List[Dict[str, Any]], styled: bool = False) -> str:
|
203
201
|
"""
|
204
202
|
Convert a list of dictionaries to a compact HTML table.
|
205
203
|
|
@@ -309,13 +307,70 @@ def dicts_to_html_table(
|
|
309
307
|
|
310
308
|
return "".join(html)
|
311
309
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
for
|
310
|
+
|
311
|
+
def flatten_dict(d, parent_key="", sep="."):
|
312
|
+
"""Recursively flatten a dictionary with dot notation."""
|
313
|
+
items = {}
|
314
|
+
for k, v in d.items():
|
315
|
+
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
316
|
+
if isinstance(v, dict):
|
317
|
+
items.update(flatten_dict(v, new_key, sep=sep))
|
318
|
+
else:
|
319
|
+
items[new_key] = v
|
320
|
+
return items
|
321
|
+
|
322
|
+
|
323
|
+
def remove_attributes(obj):
|
324
|
+
"""Recursively remove 'attributes' key from dicts/lists."""
|
325
|
+
if isinstance(obj, dict):
|
326
|
+
return {k: remove_attributes(v) for k, v in obj.items() if k != "attributes"}
|
327
|
+
elif isinstance(obj, list):
|
328
|
+
return [remove_attributes(item) for item in obj]
|
329
|
+
else:
|
330
|
+
return obj
|
331
|
+
|
332
|
+
|
333
|
+
def records_to_html_table(
|
334
|
+
records: List[Dict[str, Any]], headers: Dict[str, str] = None, styled: bool = False
|
335
|
+
) -> str:
|
336
|
+
if not isinstance(records, list):
|
337
|
+
raise ValueError("records must be a list of dictionaries")
|
338
|
+
|
339
|
+
cleaned = remove_attributes(records)
|
340
|
+
|
341
|
+
flat_rows = []
|
342
|
+
for record in cleaned:
|
317
343
|
if not isinstance(record, dict):
|
318
344
|
raise ValueError(f"Record is not a dictionary: {record!r}")
|
319
|
-
|
320
|
-
|
321
|
-
|
345
|
+
flat_rows.append(flatten_dict(record))
|
346
|
+
|
347
|
+
# Preserve column order across all rows
|
348
|
+
seen = set()
|
349
|
+
ordered_columns = []
|
350
|
+
for row in flat_rows:
|
351
|
+
for key in row.keys():
|
352
|
+
if key not in seen:
|
353
|
+
ordered_columns.append(key)
|
354
|
+
seen.add(key)
|
355
|
+
|
356
|
+
# headers optionally remaps flattened field names to user-friendly display names
|
357
|
+
if headers is None:
|
358
|
+
headers = {}
|
359
|
+
for col in ordered_columns:
|
360
|
+
headers[col] = col
|
361
|
+
else:
|
362
|
+
for col in ordered_columns:
|
363
|
+
headers[col] = headers.get(col, col)
|
364
|
+
|
365
|
+
# Normalize rows so all have the same keys, using remapped column names
|
366
|
+
normalized_data = []
|
367
|
+
for row in flat_rows:
|
368
|
+
normalized_row = {
|
369
|
+
headers.get(col, col): (
|
370
|
+
"" if row.get(col, None) is None else row.get(col, "")
|
371
|
+
)
|
372
|
+
for col in ordered_columns
|
373
|
+
}
|
374
|
+
normalized_data.append(normalized_row)
|
375
|
+
|
376
|
+
return dicts_to_html_table(normalized_data, styled=styled)
|
@@ -0,0 +1,153 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from sfq import SFAuth
|
6
|
+
|
7
|
+
|
8
|
+
@pytest.fixture(scope="module")
|
9
|
+
def sf_instance():
|
10
|
+
required_env_vars = [
|
11
|
+
"SF_INSTANCE_URL",
|
12
|
+
"SF_CLIENT_ID",
|
13
|
+
"SF_CLIENT_SECRET",
|
14
|
+
"SF_REFRESH_TOKEN",
|
15
|
+
]
|
16
|
+
|
17
|
+
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
18
|
+
if missing_vars:
|
19
|
+
pytest.fail(f"Missing required env vars: {', '.join(missing_vars)}")
|
20
|
+
|
21
|
+
sf = SFAuth(
|
22
|
+
instance_url=os.getenv("SF_INSTANCE_URL"),
|
23
|
+
client_id=os.getenv("SF_CLIENT_ID"),
|
24
|
+
client_secret=os.getenv("SF_CLIENT_SECRET").strip(),
|
25
|
+
refresh_token=os.getenv("SF_REFRESH_TOKEN"),
|
26
|
+
)
|
27
|
+
return sf
|
28
|
+
|
29
|
+
|
30
|
+
def test_html_table_conversion(sf_instance):
|
31
|
+
"""
|
32
|
+
Test the HTML table conversion utility.
|
33
|
+
"""
|
34
|
+
records = sf_instance.query("SELECT Id,NamespacePrefix FROM Organization LIMIT 1")
|
35
|
+
|
36
|
+
plain_html_table = sf_instance.records_to_html_table(records, styled=False)
|
37
|
+
styled_html_table = sf_instance.records_to_html_table(records, styled=True)
|
38
|
+
|
39
|
+
assert isinstance(plain_html_table, str)
|
40
|
+
assert "<table>" in plain_html_table
|
41
|
+
assert "<tr>" in plain_html_table
|
42
|
+
assert "<td>" in plain_html_table
|
43
|
+
org_id = sf_instance.org_id
|
44
|
+
assert f"<table><thead><tr><th>Id</th><th>NamespacePrefix</th></tr></thead><tbody><tr><td>{org_id}</td><td></td></tr></tbody></table>" == plain_html_table
|
45
|
+
assert f'<table style="border-collapse:collapse;font-size:12px;line-height:1.2;margin:0;padding:0;width:auto;"><thead><tr><th style="border:1px solid #ccc;padding:2px 6px;background:#f8f8f8;font-weight:bold;">Id</th><th style="border:1px solid #ccc;padding:2px 6px;background:#f8f8f8;font-weight:bold;">NamespacePrefix</th></tr></thead><tbody><tr><td style="border:1px solid #ccc;padding:2px 6px;vertical-align:top;">{org_id}</td><td style="border:1px solid #ccc;padding:2px 6px;vertical-align:top;"></td></tr></tbody></table>' == styled_html_table
|
46
|
+
|
47
|
+
def test_html_table_with_nested_fields(sf_instance):
|
48
|
+
results = sf_instance.query("SELECT Id,Name,CreatedBy.Name,CreatedBy.Profile.Name FROM User WHERE CreatedBy.Name <> null LIMIT 1")
|
49
|
+
html = sf_instance.records_to_html_table(results)
|
50
|
+
assert isinstance(html, str)
|
51
|
+
assert "<table>" in html
|
52
|
+
assert "<tr>" in html
|
53
|
+
assert "<td>" in html
|
54
|
+
assert "CreatedBy.Name" in html
|
55
|
+
assert "CreatedBy.Profile.Name" in html
|
56
|
+
assert "Id" in html
|
57
|
+
|
58
|
+
def test_html_table_with_mapped_headers(sf_instance):
|
59
|
+
header_map = {
|
60
|
+
"Id": "Id",
|
61
|
+
"Name": "Name",
|
62
|
+
"CreatedBy.Name": "Created By",
|
63
|
+
"CreatedBy.Profile.Name": "Created By Profile"
|
64
|
+
}
|
65
|
+
results = sf_instance.query("SELECT Id,Name,CreatedBy.Name,CreatedBy.Profile.Name FROM User WHERE CreatedBy.Name <> null LIMIT 1")
|
66
|
+
html = sf_instance.records_to_html_table(results, headers=header_map)
|
67
|
+
assert isinstance(html, str)
|
68
|
+
assert "<table>" in html
|
69
|
+
assert "<tr>" in html
|
70
|
+
assert "<td>" in html
|
71
|
+
assert "Created By" in html
|
72
|
+
assert "Created By Profile" in html
|
73
|
+
assert "Id" in html
|
74
|
+
assert "CreatedBy.Name" not in html
|
75
|
+
assert "CreatedBy.Profile.Name" not in html
|
76
|
+
|
77
|
+
def test_html_table_with_no_records(sf_instance):
|
78
|
+
html = sf_instance.records_to_html_table([])
|
79
|
+
assert isinstance(html, str)
|
80
|
+
assert "<table>" in html
|
81
|
+
assert "<tr>" not in html
|
82
|
+
assert "<td>" not in html
|
83
|
+
assert "</table>" in html
|
84
|
+
|
85
|
+
def test_html_table_with_single_record(sf_instance):
|
86
|
+
records = sf_instance.query("SELECT Id,NamespacePrefix FROM Organization LIMIT 1")
|
87
|
+
html = sf_instance.records_to_html_table(records)
|
88
|
+
assert isinstance(html, str)
|
89
|
+
assert "<table>" in html
|
90
|
+
assert "<tr>" in html
|
91
|
+
assert "<td>" in html
|
92
|
+
org_id = sf_instance.org_id
|
93
|
+
assert f"<table><thead><tr><th>Id</th><th>NamespacePrefix</th></tr></thead><tbody><tr><td>{org_id}</td><td></td></tr></tbody></table>" == html
|
94
|
+
|
95
|
+
styled_html = sf_instance.records_to_html_table(records, styled=True)
|
96
|
+
assert isinstance(styled_html, str)
|
97
|
+
assert "<table" in styled_html
|
98
|
+
assert 'style="border-collapse:collapse;font-size:12px;line-height:1.2;margin:0;padding:0;width:auto;"' in styled_html
|
99
|
+
assert "<tr" in styled_html
|
100
|
+
assert "<td" in styled_html
|
101
|
+
assert "</td>" in styled_html
|
102
|
+
assert "</tr>" in styled_html
|
103
|
+
assert "</table>" in styled_html
|
104
|
+
|
105
|
+
def test_html_with_nested_fields(sf_instance):
|
106
|
+
results = sf_instance.query("SELECT Id,Name,CreatedBy.Name,CreatedBy.Profile.Name FROM User WHERE CreatedBy.Name <> null LIMIT 1")
|
107
|
+
html = sf_instance.records_to_html_table(results)
|
108
|
+
assert isinstance(html, str)
|
109
|
+
assert "<table>" in html
|
110
|
+
assert "<tr" in html
|
111
|
+
assert "<td" in html
|
112
|
+
assert "</tr>" in html
|
113
|
+
assert "</td>" in html
|
114
|
+
assert "CreatedBy.Name" in html
|
115
|
+
assert "CreatedBy.Profile.Name" in html
|
116
|
+
assert "Id" in html
|
117
|
+
|
118
|
+
|
119
|
+
def test_html_table_with_none_values(sf_instance):
|
120
|
+
records = [
|
121
|
+
{"Id": None, "Name": "Test"},
|
122
|
+
{"Id": "123", "Name": None},
|
123
|
+
]
|
124
|
+
html = sf_instance.records_to_html_table(records)
|
125
|
+
assert "<td></td>" in html # None should be rendered as empty
|
126
|
+
assert "Test" in html
|
127
|
+
assert "123" in html
|
128
|
+
|
129
|
+
def test_html_table_with_empty_dict(sf_instance):
|
130
|
+
records = [{}]
|
131
|
+
html = sf_instance.records_to_html_table(records)
|
132
|
+
assert "<table>" in html
|
133
|
+
assert "<tr>" not in html or "<td>" not in html
|
134
|
+
|
135
|
+
def test_html_table_with_mixed_types(sf_instance):
|
136
|
+
records = [
|
137
|
+
{"Id": 1, "Active": True, "Score": 99.5},
|
138
|
+
{"Id": 2, "Active": False, "Score": None},
|
139
|
+
]
|
140
|
+
html = sf_instance.records_to_html_table(records)
|
141
|
+
assert "1" in html
|
142
|
+
assert "True" in html or "False" in html
|
143
|
+
assert "99.5" in html
|
144
|
+
|
145
|
+
def test_html_table_with_header_remapping_missing_keys(sf_instance):
|
146
|
+
records = [
|
147
|
+
{"Id": "abc", "Name": "Test", "Extra": "Value"}
|
148
|
+
]
|
149
|
+
headers = {"Id": "Identifier", "Name": "Label"} # 'Extra' not mapped
|
150
|
+
html = sf_instance.records_to_html_table(records, headers=headers)
|
151
|
+
assert "Identifier" in html
|
152
|
+
assert "Label" in html
|
153
|
+
assert "Extra" in html # Should fall back to original key
|
@@ -1,45 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
import pytest
|
4
|
-
|
5
|
-
from sfq import SFAuth
|
6
|
-
|
7
|
-
|
8
|
-
@pytest.fixture(scope="module")
|
9
|
-
def sf_instance():
|
10
|
-
required_env_vars = [
|
11
|
-
"SF_INSTANCE_URL",
|
12
|
-
"SF_CLIENT_ID",
|
13
|
-
"SF_CLIENT_SECRET",
|
14
|
-
"SF_REFRESH_TOKEN",
|
15
|
-
]
|
16
|
-
|
17
|
-
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
18
|
-
if missing_vars:
|
19
|
-
pytest.fail(f"Missing required env vars: {', '.join(missing_vars)}")
|
20
|
-
|
21
|
-
sf = SFAuth(
|
22
|
-
instance_url=os.getenv("SF_INSTANCE_URL"),
|
23
|
-
client_id=os.getenv("SF_CLIENT_ID"),
|
24
|
-
client_secret=os.getenv("SF_CLIENT_SECRET").strip(),
|
25
|
-
refresh_token=os.getenv("SF_REFRESH_TOKEN"),
|
26
|
-
)
|
27
|
-
return sf
|
28
|
-
|
29
|
-
|
30
|
-
def test_html_table_conversion(sf_instance):
|
31
|
-
"""
|
32
|
-
Test the HTML table conversion utility.
|
33
|
-
"""
|
34
|
-
records = sf_instance.query("SELECT Id,NamespacePrefix FROM Organization LIMIT 1")
|
35
|
-
|
36
|
-
plain_html_table = sf_instance.records_to_html_table(records, styled=False)
|
37
|
-
styled_html_table = sf_instance.records_to_html_table(records, styled=True)
|
38
|
-
|
39
|
-
assert isinstance(plain_html_table, str)
|
40
|
-
assert "<table>" in plain_html_table
|
41
|
-
assert "<tr>" in plain_html_table
|
42
|
-
assert "<td>" in plain_html_table
|
43
|
-
org_id = sf_instance.org_id
|
44
|
-
assert f"<table><thead><tr><th>Id</th><th>NamespacePrefix</th></tr></thead><tbody><tr><td>{org_id}</td><td></td></tr></tbody></table>" == plain_html_table
|
45
|
-
assert f'<table style="border-collapse:collapse;font-size:12px;line-height:1.2;margin:0;padding:0;width:auto;"><thead><tr><th style="border:1px solid #ccc;padding:2px 6px;background:#f8f8f8;font-weight:bold;">Id</th><th style="border:1px solid #ccc;padding:2px 6px;background:#f8f8f8;font-weight:bold;">NamespacePrefix</th></tr></thead><tbody><tr><td style="border:1px solid #ccc;padding:2px 6px;vertical-align:top;">{org_id}</td><td style="border:1px solid #ccc;padding:2px 6px;vertical-align:top;"></td></tr></tbody></table>' == styled_html_table
|
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
|
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
|
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
|
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
|
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
|