Skip to content

Commit 4a04456

Browse files
authored
Merge pull request #280 from PySimpleSQL/pytest
First unit tests
2 parents 2d6938f + 0c143e4 commit 4a04456

File tree

6 files changed

+367
-15
lines changed

6 files changed

+367
-15
lines changed

pysimplesql/pysimplesql.py

+25-15
Original file line numberDiff line numberDiff line change
@@ -3884,6 +3884,29 @@ def __init__(self, title: str, config: dict = None):
38843884
:param config: Dictionary of configuration options as listed above
38853885
:returns: None
38863886
"""
3887+
default_config = {
3888+
# oscillators for the bar divider and colors
3889+
"bar": {"value_start": 0, "value_range": 100, "period": 3, "offset": 0},
3890+
"red": {"value_start": 0, "value_range": 255, "period": 2, "offset": 0},
3891+
"green": {"value_start": 0, "value_range": 255, "period": 3, "offset": 120},
3892+
"blue": {"value_start": 0, "value_range": 255, "period": 4, "offset": 240},
3893+
# phrases to display and the number of seconds to elapse between phrases
3894+
"phrases": lang.animate_phrases,
3895+
"phrase_delay": 5,
3896+
}
3897+
if config is None:
3898+
config = {}
3899+
3900+
if type(config) is not dict:
3901+
raise ValueError("config must be a dictionary")
3902+
3903+
if set(config.keys()) - set(default_config.keys()):
3904+
raise NotImplementedError(
3905+
f"config may only contain keys: {default_config.keys()}"
3906+
)
3907+
3908+
self.config = {**default_config, **config}
3909+
38873910
self.title = title
38883911
self.win: sg.Window = None
38893912
self.layout = [
@@ -3902,20 +3925,6 @@ def __init__(self, title: str, config: dict = None):
39023925
self.phrase_index = 0
39033926
self.completed = asyncio.Event()
39043927

3905-
default_config = {
3906-
# oscillators for the bar divider and colors
3907-
"bar": {"value_start": 0, "value_range": 100, "period": 3, "offset": 0},
3908-
"red": {"value_start": 0, "value_range": 255, "period": 2, "offset": 0},
3909-
"green": {"value_start": 0, "value_range": 255, "period": 3, "offset": 120},
3910-
"blue": {"value_start": 0, "value_range": 255, "period": 4, "offset": 240},
3911-
# phrases to display and the number of seconds to elapse between phrases
3912-
"phrases": lang.animate_phrases,
3913-
"phrase_delay": 5,
3914-
}
3915-
if config is None:
3916-
config = {}
3917-
self.config = {**default_config, **config}
3918-
39193928
def run(self, fn: callable, *args, **kwargs):
39203929
"""
39213930
Runs the function in a separate co-routine, while animating the progress bar in
@@ -3952,6 +3961,7 @@ async def run_process(self, fn: callable, *args, **kwargs):
39523961
return result
39533962
except Exception as e: # noqa: BLE001
39543963
print(f"\nAn error occurred in the process: {e}")
3964+
raise e # Pass the exception along to the caller
39553965
finally:
39563966
self.completed.set()
39573967

@@ -7443,7 +7453,7 @@ def __init__(
74437453
self, host, user, password, database, sql_script=None, sql_commands=None
74447454
):
74457455
super().__init__(
7446-
name="Sqlserver", requires=["pyodbc"], table_quote='"', placeholder="?"
7456+
name="Sqlserver", requires=["pyodbc"], table_quote="[]", placeholder="?"
74477457
)
74487458

74497459
self.import_required_modules()

ruff.toml

+1
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@ ignore = [
6565
"I",
6666
]
6767
"doc_examples/*" = ["F821"]
68+
"tests/*" = ["BLE001", "F405", "PT011", "PT012", "PT015", "PT017", "SIM114"]
6869
"pysimplesql/language_pack.py" = ["E501"]
6970
"pysimplesql/theme_pack.py" = ["E501"]

tests/progressanimate_test.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from time import sleep
2+
3+
import pytest
4+
5+
import pysimplesql as ss
6+
7+
8+
# Simulated process
9+
def process(raise_error=False):
10+
if raise_error:
11+
raise ValueError("Oops! This process had an error!")
12+
sleep(5)
13+
14+
15+
def test_successful_process():
16+
try:
17+
sa = ss.ProgressAnimate("Test ProgressAnimate")
18+
sa.run(process, False)
19+
except Exception as e:
20+
assert False, f"An exception was raised: {e}"
21+
22+
23+
def test_exception_during_process():
24+
with pytest.raises(Exception):
25+
sa = ss.ProgressAnimate("Test ProgressAnimate")
26+
v = sa.run(process, True)
27+
print(v, type(v))
28+
29+
30+
def test_config():
31+
# What if config was set with an int?
32+
with pytest.raises(ValueError):
33+
ss.ProgressAnimate("Test", config=1)
34+
# What if config was set with a string
35+
with pytest.raises(ValueError):
36+
ss.ProgressAnimate("Test", config="My Config")
37+
# What if config was set with a list?
38+
with pytest.raises(ValueError):
39+
ss.ProgressAnimate("Test", config=["red"])
40+
# What if config was set with a bool?
41+
with pytest.raises(ValueError):
42+
ss.ProgressAnimate("Test", config=True)
43+
# What if the user does supply a dict, but it doesn't have the right keys?
44+
with pytest.raises(NotImplementedError):
45+
# Purposely fail by
46+
config = {
47+
"sound_effect": "beep",
48+
}
49+
ss.ProgressAnimate("Test", config=config)

tests/sqldriver_test.py

+260
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# ruff: skip-file
2+
3+
import contextlib
4+
5+
import docker.errors
6+
import pytest
7+
8+
import pysimplesql as ss
9+
from pysimplesql.docker_utils import * # noqa F403
10+
11+
12+
# --------------------------------------------------------------------------------------
13+
# Create session-level fixtures for the docker containers to provide database servers
14+
# --------------------------------------------------------------------------------------
15+
@pytest.fixture(scope="session", autouse=True)
16+
def mysql_container():
17+
docker_image = "pysimplesql/examples:mysql"
18+
docker_image_pull(docker_image)
19+
docker_container = docker_container_start(
20+
image=docker_image,
21+
container_name="pysimplesql-examples-mysql",
22+
ports={"3306/tcp": ("127.0.0.1", 3306)},
23+
)
24+
yield docker_container
25+
with contextlib.suppress(docker.errors.APIError):
26+
docker_container.stop()
27+
28+
29+
@pytest.fixture(scope="session", autouse=True)
30+
def postgres_container():
31+
docker_image = "pysimplesql/examples:postgres"
32+
docker_image_pull(docker_image)
33+
docker_container = docker_container_start(
34+
image=docker_image,
35+
container_name="pysimplesql-examples-postgres",
36+
ports={"5432/tcp": ("127.0.0.1", 5432)},
37+
)
38+
yield docker_container
39+
with contextlib.suppress(docker.errors.APIError):
40+
docker_container.stop()
41+
42+
43+
@pytest.fixture(scope="session", autouse=True)
44+
def sqlserver_container():
45+
docker_image = "pysimplesql/examples:sqlserver"
46+
docker_image_pull(docker_image)
47+
docker_container = docker_container_start(
48+
image=docker_image,
49+
container_name="pysimplesql-examples-sqlserver",
50+
ports={"1433/tcp": ("127.0.0.1", 1433)},
51+
)
52+
yield docker_container
53+
with contextlib.suppress(docker.errors.APIError):
54+
docker_container.stop()
55+
56+
57+
# Credentials to use each with the docker servers
58+
mysql_docker = {
59+
"user": "pysimplesql_user",
60+
"password": "pysimplesql",
61+
"host": "127.0.0.1",
62+
"database": "pysimplesql_examples",
63+
}
64+
65+
postgres_docker = {
66+
"host": "localhost",
67+
"user": "pysimplesql_user",
68+
"password": "pysimplesql",
69+
"database": "pysimplesql_examples",
70+
}
71+
72+
sqlserver_docker = {
73+
"host": "127.0.0.1",
74+
"user": "pysimplesql_user",
75+
"password": "Pysimplesql!",
76+
"database": "pysimplesql_examples",
77+
}
78+
79+
80+
# --------------------------------------------------------------------------------------
81+
# Use a fixture to create a driver instance for each test
82+
# --------------------------------------------------------------------------------------
83+
@pytest.fixture(
84+
params=[
85+
ss.Driver.sqlite,
86+
ss.Driver.flatfile,
87+
ss.Driver.mysql,
88+
ss.Driver.postgres,
89+
ss.Driver.sqlserver,
90+
ss.Driver.msaccess,
91+
]
92+
)
93+
def driver(request):
94+
driver_class = request.param
95+
96+
# Use an in-memory database for sqlite tests
97+
if driver_class == ss.Driver.sqlite:
98+
return driver_class(db_path=":memory:")
99+
if driver_class == ss.Driver.flatfile:
100+
return driver_class(file_path="test.csv")
101+
if driver_class == ss.Driver.mysql:
102+
return driver_class(**mysql_docker)
103+
if driver_class == ss.Driver.postgres:
104+
return driver_class(**postgres_docker)
105+
if driver_class == ss.Driver.sqlserver:
106+
return driver_class(**sqlserver_docker)
107+
if driver_class == ss.Driver.msaccess:
108+
return driver_class(database_file="test.accdb")
109+
raise NotImplementedError("Driver class not supported in tests.")
110+
111+
112+
# --------------------------------------------------------------------------------------
113+
# General tests that apply to all SQLDriver implementations
114+
# --------------------------------------------------------------------------------------
115+
# Note: driver-specific implementations will be provided after this section
116+
117+
118+
# Test creating a connection
119+
@pytest.mark.parametrize(
120+
"driver",
121+
[
122+
ss.Driver.sqlite,
123+
ss.Driver.flatfile,
124+
ss.Driver.mysql,
125+
ss.Driver.postgres,
126+
ss.Driver.sqlserver,
127+
ss.Driver.msaccess,
128+
],
129+
indirect=True,
130+
)
131+
def test_connect(driver):
132+
driver_class = driver.__class__
133+
134+
# Note: We don't actually need to look at the driver classes in this case
135+
# as the action for all is exactly the same. I just wanted a good example
136+
# of how we can separate logic out for individual drivers if needed.
137+
if driver_class == ss.Driver.sqlite:
138+
assert driver.con is not None
139+
elif driver_class == ss.Driver.flatfile:
140+
assert driver.con is not None # uses sqlite, so should have con
141+
elif driver_class == ss.Driver.mysql:
142+
assert driver.con is not None
143+
elif driver_class == ss.Driver.postgres:
144+
assert driver.con is not None
145+
elif driver_class == ss.Driver.sqlserver:
146+
assert driver.con is not None
147+
elif driver_class == ss.Driver.msaccess:
148+
assert driver.con is not None
149+
else:
150+
raise NotImplementedError("Driver class not supported in tests.")
151+
152+
153+
# Test closing a connection
154+
@pytest.mark.parametrize(
155+
"driver",
156+
[
157+
ss.Driver.sqlite,
158+
ss.Driver.flatfile,
159+
ss.Driver.mysql,
160+
ss.Driver.postgres,
161+
ss.Driver.sqlserver,
162+
ss.Driver.msaccess,
163+
],
164+
indirect=True,
165+
)
166+
def test_close(driver):
167+
# Close the driver
168+
driver.close()
169+
170+
# Now see if we can execute a simple query. If we can, it did not close properly
171+
query = "SELECT 1"
172+
173+
with pytest.raises(Exception):
174+
driver.execute(query)
175+
176+
177+
# Test creating tables
178+
@pytest.mark.parametrize(
179+
"driver",
180+
[
181+
ss.Driver.sqlite,
182+
ss.Driver.flatfile,
183+
ss.Driver.mysql,
184+
ss.Driver.postgres,
185+
ss.Driver.sqlserver,
186+
# ss.Driver.msaccess, #MSAccess not quite working yet...
187+
],
188+
indirect=True,
189+
)
190+
def test_create_table(driver: ss.SQLDriver):
191+
driver_class = driver.__class__
192+
# Create
193+
table = "TestAaBb123"
194+
table_quoted = driver.quote_table(table)
195+
196+
# Drop the table so we start clean
197+
query = f"DROP TABLE IF EXISTS {table_quoted};"
198+
driver.execute(query)
199+
driver.commit()
200+
201+
reference_tables = driver.get_tables()
202+
print(driver_class, "reference_tables", reference_tables)
203+
assert table not in reference_tables
204+
205+
# Now create the table
206+
query = f"CREATE TABLE {table_quoted} (id INTEGER);"
207+
driver.execute(query)
208+
driver.commit()
209+
210+
# Get tables again
211+
tables = driver.get_tables()
212+
print(driver_class, "tables", tables)
213+
assert table in tables
214+
215+
216+
"""
217+
@pytest.mark.parametrize("sql_driver_instance", [
218+
ss.Driver.sqlite,
219+
ss.Driver.flatfile,
220+
ss.Driver.mysql,
221+
ss.Driver.postgres,
222+
ss.Driver.sqlserver,
223+
ss.Driver.msaccess
224+
], indirect=True)
225+
def test_get_tables(sql_driver_instance):
226+
227+
228+
@pytest.mark.parametrize("sql_driver_instance", [
229+
ss.Driver.sqlite,
230+
ss.Driver.flatfile,
231+
ss.Driver.mysql,
232+
ss.Driver.postgres,
233+
ss.Driver.sqlserver,
234+
ss.Driver.msaccess
235+
], indirect=True)
236+
def test_column_info(sql_driver_instance):
237+
238+
239+
@pytest.mark.parametrize("sql_driver_instance", [
240+
ss.Driver.sqlite,
241+
ss.Driver.flatfile,
242+
ss.Driver.mysql,
243+
ss.Driver.postgres,
244+
ss.Driver.sqlserver,
245+
ss.Driver.msaccess
246+
], indirect=True)
247+
def test_execute_query(sql_driver_instance):
248+
249+
250+
@pytest.mark.parametrize("sql_driver_instance", [
251+
ss.Driver.sqlite,
252+
ss.Driver.flatfile,
253+
ss.Driver.mysql,
254+
ss.Driver.postgres,
255+
ss.Driver.sqlserver,
256+
ss.Driver.msaccess
257+
], indirect=True)
258+
def test_relationships(sql_driver_instance):
259+
260+
"""

tests/test.accdb

660 KB
Binary file not shown.

0 commit comments

Comments
 (0)