Skip to content

Commit 07be840

Browse files
authored
chore: pytestify requests (#1696)
* fix: black ci errors * chore: pytestify requests * style: black * chore: pytestify request_factory object requests * chore: pytestify ssl_config --------- Co-authored-by: Jordan Woods <13803242+jorwoods@users.noreply.github.com>
1 parent 4f38f0a commit 07be840

File tree

4 files changed

+183
-183
lines changed

4 files changed

+183
-183
lines changed
Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import unittest
21
import tableauserverclient as TSC
32
import tableauserverclient.server.request_factory as TSC_RF
43
from tableauserverclient import DatasourceItem
54

65

7-
class DatasourceRequestTests(unittest.TestCase):
8-
def test_generate_xml(self):
9-
datasource_item: TSC.DatasourceItem = TSC.DatasourceItem("name")
10-
datasource_item.name = "a ds"
11-
datasource_item.description = "described"
12-
datasource_item.use_remote_query_agent = False
13-
datasource_item.ask_data_enablement = DatasourceItem.AskDataEnablement.Enabled
14-
datasource_item.project_id = "testval"
15-
TSC_RF.RequestFactory.Datasource._generate_xml(datasource_item)
6+
def test_generate_xml():
7+
datasource_item: TSC.DatasourceItem = TSC.DatasourceItem("name")
8+
datasource_item.name = "a ds"
9+
datasource_item.description = "described"
10+
datasource_item.use_remote_query_agent = False
11+
datasource_item.ask_data_enablement = DatasourceItem.AskDataEnablement.Enabled
12+
datasource_item.project_id = "testval"
13+
TSC_RF.RequestFactory.Datasource._generate_xml(datasource_item)
Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,58 @@
1-
import unittest
21
import tableauserverclient as TSC
32
import tableauserverclient.server.request_factory as TSC_RF
43
from tableauserverclient.helpers.strings import redact_xml
54
import pytest
65
import sys
76

87

9-
class WorkbookRequestTests(unittest.TestCase):
10-
def test_embedded_extract_req(self):
11-
include_all = True
12-
embedded_datasources = None
13-
xml_result = TSC_RF.RequestFactory.Workbook.embedded_extract_req(include_all, embedded_datasources)
14-
15-
def test_generate_xml(self):
16-
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
17-
TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item)
18-
19-
def test_generate_xml_invalid_connection(self):
20-
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
21-
conn = TSC.ConnectionItem()
22-
with self.assertRaises(ValueError):
23-
request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
24-
25-
def test_generate_xml_invalid_connection_credentials(self):
26-
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
27-
conn = TSC.ConnectionItem()
28-
conn.server_address = "address"
29-
creds = TSC.ConnectionCredentials("username", "password")
30-
creds.name = None
31-
conn.connection_credentials = creds
32-
with self.assertRaises(ValueError):
33-
request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
34-
35-
def test_generate_xml_valid_connection_credentials(self):
36-
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
37-
conn = TSC.ConnectionItem()
38-
conn.server_address = "address"
39-
creds = TSC.ConnectionCredentials("username", "DELETEME")
40-
conn.connection_credentials = creds
8+
def test_embedded_extract_req() -> None:
9+
include_all = True
10+
embedded_datasources = None
11+
xml_result = TSC_RF.RequestFactory.Workbook.embedded_extract_req(include_all, embedded_datasources)
12+
13+
14+
def test_generate_xml() -> None:
15+
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
16+
TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item)
17+
18+
19+
def test_generate_xml_invalid_connection() -> None:
20+
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
21+
conn = TSC.ConnectionItem()
22+
with pytest.raises(ValueError):
4123
request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
42-
assert request.find(b"DELETEME") > 0
43-
44-
def test_redact_passwords_in_xml(self):
45-
if sys.version_info < (3, 7):
46-
pytest.skip("Redaction is only implemented for 3.7+.")
47-
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
48-
conn = TSC.ConnectionItem()
49-
conn.server_address = "address"
50-
creds = TSC.ConnectionCredentials("username", "DELETEME")
51-
conn.connection_credentials = creds
24+
25+
26+
def test_generate_xml_invalid_connection_credentials() -> None:
27+
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
28+
conn = TSC.ConnectionItem()
29+
conn.server_address = "address"
30+
creds = TSC.ConnectionCredentials("username", "password")
31+
creds.name = None
32+
conn.connection_credentials = creds
33+
with pytest.raises(ValueError):
5234
request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
53-
redacted = redact_xml(request)
54-
assert request.find(b"DELETEME") > 0, request
55-
assert redacted.find(b"DELETEME") == -1, redacted
35+
36+
37+
def test_generate_xml_valid_connection_credentials() -> None:
38+
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
39+
conn = TSC.ConnectionItem()
40+
conn.server_address = "address"
41+
creds = TSC.ConnectionCredentials("username", "DELETEME")
42+
conn.connection_credentials = creds
43+
request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
44+
assert request.find(b"DELETEME") > 0
45+
46+
47+
def test_redact_passwords_in_xml() -> None:
48+
if sys.version_info < (3, 7):
49+
pytest.skip("Redaction is only implemented for 3.7+.")
50+
workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
51+
conn = TSC.ConnectionItem()
52+
conn.server_address = "address"
53+
creds = TSC.ConnectionCredentials("username", "DELETEME")
54+
conn.connection_credentials = creds
55+
request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
56+
redacted = redact_xml(request)
57+
assert request.find(b"DELETEME") > 0, request
58+
assert redacted.find(b"DELETEME") == -1, redacted

test/test_requests.py

Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,69 @@
1-
import re
2-
import unittest
1+
from urllib.parse import parse_qs
32

3+
import pytest
44
import requests
55
import requests_mock
66

77
import tableauserverclient as TSC
88
from tableauserverclient.server.endpoint.exceptions import InternalServerError, NonXMLResponseError
99

1010

11-
class RequestTests(unittest.TestCase):
12-
def setUp(self):
13-
self.server = TSC.Server("http://test", False)
14-
15-
# Fake sign in
16-
self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
17-
self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
18-
19-
self.baseurl = self.server.workbooks.baseurl
20-
21-
def test_make_get_request(self):
22-
with requests_mock.mock() as m:
23-
m.get(requests_mock.ANY)
24-
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
25-
opts = TSC.RequestOptions(pagesize=13, pagenumber=15)
26-
resp = self.server.workbooks.get_request(url, request_object=opts)
27-
28-
self.assertTrue(re.search("pagesize=13", resp.request.query))
29-
self.assertTrue(re.search("pagenumber=15", resp.request.query))
30-
31-
def test_make_post_request(self):
32-
with requests_mock.mock() as m:
33-
m.post(requests_mock.ANY)
34-
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
35-
resp = self.server.workbooks._make_request(
36-
requests.post,
37-
url,
38-
content=b"1337",
39-
auth_token="j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM",
40-
content_type="multipart/mixed",
41-
)
42-
self.assertEqual(resp.request.headers["x-tableau-auth"], "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM")
43-
self.assertEqual(resp.request.headers["content-type"], "multipart/mixed")
44-
self.assertTrue(re.search("Tableau Server Client", resp.request.headers["user-agent"]))
45-
self.assertEqual(resp.request.body, b"1337")
46-
47-
# Test that 500 server errors are handled properly
48-
def test_internal_server_error(self):
49-
self.server.version = "3.2"
50-
server_response = "500: Internal Server Error"
51-
with requests_mock.mock() as m:
52-
m.register_uri("GET", self.server.server_info.baseurl, status_code=500, text=server_response)
53-
self.assertRaisesRegex(InternalServerError, server_response, self.server.server_info.get)
54-
55-
# Test that non-xml server errors are handled properly
56-
def test_non_xml_error(self):
57-
self.server.version = "3.2"
58-
server_response = "this is not xml"
59-
with requests_mock.mock() as m:
60-
m.register_uri("GET", self.server.server_info.baseurl, status_code=499, text=server_response)
61-
self.assertRaisesRegex(NonXMLResponseError, server_response, self.server.server_info.get)
11+
@pytest.fixture(scope="function")
12+
def server():
13+
"""Fixture to create a TSC.Server instance for testing."""
14+
server = TSC.Server("http://test", False)
15+
16+
# Fake signin
17+
server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
18+
server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
19+
20+
return server
21+
22+
23+
def test_make_get_request(server: TSC.Server) -> None:
24+
with requests_mock.mock() as m:
25+
m.get(requests_mock.ANY)
26+
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
27+
opts = TSC.RequestOptions(pagesize=13, pagenumber=15)
28+
resp = server.workbooks.get_request(url, request_object=opts)
29+
30+
query = parse_qs(resp.request.query)
31+
assert query.get("pagesize") == ["13"]
32+
assert query.get("pagenumber") == ["15"]
33+
34+
35+
def test_make_post_request(server: TSC.Server) -> None:
36+
with requests_mock.mock() as m:
37+
m.post(requests_mock.ANY)
38+
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
39+
resp = server.workbooks._make_request(
40+
requests.post,
41+
url,
42+
content=b"1337",
43+
auth_token="j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM",
44+
content_type="multipart/mixed",
45+
)
46+
assert resp.request.headers["x-tableau-auth"] == "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
47+
assert resp.request.headers["content-type"] == "multipart/mixed"
48+
assert "Tableau Server Client" in resp.request.headers["user-agent"]
49+
assert resp.request.body == b"1337"
50+
51+
52+
# Test that 500 server errors are handled properly
53+
def test_internal_server_error(server: TSC.Server) -> None:
54+
server.version = "3.2"
55+
server_response = "500: Internal Server Error"
56+
with requests_mock.mock() as m:
57+
m.register_uri("GET", server.server_info.baseurl, status_code=500, text=server_response)
58+
with pytest.raises(InternalServerError, match=server_response):
59+
server.server_info.get()
60+
61+
62+
# Test that non-xml server errors are handled properly
63+
def test_non_xml_error(server: TSC.Server) -> None:
64+
server.version = "3.2"
65+
server_response = "this is not xml"
66+
with requests_mock.mock() as m:
67+
m.register_uri("GET", server.server_info.baseurl, status_code=499, text=server_response)
68+
with pytest.raises(NonXMLResponseError, match=server_response):
69+
server.server_info.get()

test/test_ssl_config.py

Lines changed: 65 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,68 @@
1-
import unittest
2-
import ssl
3-
from unittest.mock import patch, MagicMock
4-
from tableauserverclient import Server
5-
from tableauserverclient.server.endpoint import Endpoint
61
import logging
2+
from unittest.mock import MagicMock
73

4+
import pytest
85

9-
class TestSSLConfig(unittest.TestCase):
10-
@patch("requests.session")
11-
@patch("tableauserverclient.server.endpoint.Endpoint.set_parameters")
12-
def setUp(self, mock_set_parameters, mock_session):
13-
"""Set up test fixtures with mocked session and request validation"""
14-
# Mock the session
15-
self.mock_session = MagicMock()
16-
mock_session.return_value = self.mock_session
17-
18-
# Mock request preparation
19-
self.mock_request = MagicMock()
20-
self.mock_session.prepare_request.return_value = self.mock_request
21-
22-
# Create server instance with mocked components
23-
self.server = Server("http://test")
24-
25-
def test_default_ssl_config(self):
26-
"""Test that by default, no custom SSL context is used"""
27-
self.assertIsNone(self.server._ssl_context)
28-
self.assertNotIn("verify", self.server.http_options)
29-
30-
@patch("ssl.create_default_context")
31-
def test_weak_dh_config(self, mock_create_context):
32-
"""Test that weak DH keys can be allowed when configured"""
33-
# Setup mock SSL context
34-
mock_context = MagicMock()
35-
mock_create_context.return_value = mock_context
36-
37-
# Configure SSL with weak DH
38-
self.server.configure_ssl(allow_weak_dh=True)
39-
40-
# Verify SSL context was created and configured correctly
41-
mock_create_context.assert_called_once()
42-
mock_context.set_dh_parameters.assert_called_once_with(min_key_bits=512)
43-
44-
# Verify context was added to http options
45-
self.assertEqual(self.server.http_options["verify"], mock_context)
46-
47-
@patch("ssl.create_default_context")
48-
def test_disable_weak_dh_config(self, mock_create_context):
49-
"""Test that SSL config can be reset to defaults"""
50-
# Setup mock SSL context
51-
mock_context = MagicMock()
52-
mock_create_context.return_value = mock_context
53-
54-
# First enable weak DH
55-
self.server.configure_ssl(allow_weak_dh=True)
56-
self.assertIsNotNone(self.server._ssl_context)
57-
self.assertIn("verify", self.server.http_options)
58-
59-
# Then disable it
60-
self.server.configure_ssl(allow_weak_dh=False)
61-
self.assertIsNone(self.server._ssl_context)
62-
self.assertNotIn("verify", self.server.http_options)
63-
64-
@patch("ssl.create_default_context")
65-
def test_warning_on_weak_dh(self, mock_create_context):
66-
"""Test that a warning is logged when enabling weak DH keys"""
67-
logging.getLogger().setLevel(logging.WARNING)
68-
with self.assertLogs(level="WARNING") as log:
69-
self.server.configure_ssl(allow_weak_dh=True)
70-
self.assertTrue(
71-
any("WARNING: Allowing weak Diffie-Hellman keys" in record for record in log.output),
72-
"Expected warning about weak DH keys was not logged",
73-
)
74-
75-
76-
if __name__ == "__main__":
77-
unittest.main()
6+
import tableauserverclient as TSC
7+
8+
9+
@pytest.fixture(scope="function")
10+
def server():
11+
"""Fixture to create a TSC.Server instance for testing."""
12+
server = TSC.Server("http://test", False)
13+
14+
# Fake signin
15+
server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
16+
server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
17+
18+
return server
19+
20+
21+
def test_default_ssl_config(server):
22+
"""Test that by default, no custom SSL context is used"""
23+
assert server._ssl_context is None
24+
assert "verify" not in server.http_options
25+
26+
27+
def test_weak_dh_config(server, monkeypatch):
28+
"""Test that weak DH keys can be allowed when configured"""
29+
mock_context = MagicMock()
30+
mock_create_context = MagicMock(return_value=mock_context)
31+
monkeypatch.setattr("ssl.create_default_context", mock_create_context)
32+
33+
server.configure_ssl(allow_weak_dh=True)
34+
35+
mock_create_context.assert_called_once()
36+
mock_context.set_dh_parameters.assert_called_once_with(min_key_bits=512)
37+
assert server.http_options["verify"] == mock_context
38+
39+
40+
def test_disable_weak_dh_config(server, monkeypatch):
41+
"""Test that SSL config can be reset to defaults"""
42+
mock_context = MagicMock()
43+
mock_create_context = MagicMock(return_value=mock_context)
44+
monkeypatch.setattr("ssl.create_default_context", mock_create_context)
45+
46+
# First enable weak DH
47+
server.configure_ssl(allow_weak_dh=True)
48+
assert server._ssl_context is not None
49+
assert "verify" in server.http_options
50+
51+
# Then disable it
52+
server.configure_ssl(allow_weak_dh=False)
53+
assert server._ssl_context is None
54+
assert "verify" not in server.http_options
55+
56+
57+
def test_warning_on_weak_dh(server, monkeypatch, caplog):
58+
"""Test that a warning is logged when enabling weak DH keys"""
59+
mock_context = MagicMock()
60+
mock_create_context = MagicMock(return_value=mock_context)
61+
monkeypatch.setattr("ssl.create_default_context", mock_create_context)
62+
63+
with caplog.at_level(logging.WARNING):
64+
server.configure_ssl(allow_weak_dh=True)
65+
66+
assert any(
67+
"Allowing weak Diffie-Hellman keys" in record.getMessage() for record in caplog.records
68+
), "Expected warning about weak DH keys was not logged"

0 commit comments

Comments
 (0)