-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_db.py
More file actions
194 lines (148 loc) · 4.99 KB
/
test_db.py
File metadata and controls
194 lines (148 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import pytest
import sqlite3
import os
import db
class MockCursor:
def execute(self, *args, **kwargs):
pass
class MockConnection:
def cursor(self):
return MockCursor()
def commit(self):
raise sqlite3.DatabaseError("commit failed")
def close(self):
pass
@pytest.fixture(autouse=True)
def setup_db(tmp_path, monkeypatch):
test_db = tmp_path / 'currency_rates.db'
monkeypatch.setattr(db, 'DB_NAME', str(test_db))
db.init_db()
yield
if os.path.exists(str(test_db)):
os.remove(str(test_db))
def test_init_db_creates_table():
conn = sqlite3.connect(db.DB_NAME)
cursor = conn.cursor()
cursor.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='rates'"
)
assert cursor.fetchone()[0] == 'rates'
conn.close()
def test_save_rate_new_record():
db.save_rate(1, 'USD', 70.55)
conn = sqlite3.connect(db.DB_NAME)
cursor = conn.cursor()
cursor.execute("SELECT rate FROM rates WHERE target_currency='USD'")
assert cursor.fetchone()[0] == 70.55
conn.close()
def test_save_rate_update_existing():
db.save_rate(1, 'USD', 70.55)
db.save_rate(1, 'USD', 75.11)
conn = sqlite3.connect(db.DB_NAME)
cursor = conn.cursor()
cursor.execute("SELECT rate FROM rates WHERE target_currency='USD'")
assert cursor.fetchone()[0] == 75.11
conn.close()
def test_save_rate_date_format():
db.save_rate(1, 'USD', 70.55)
conn = sqlite3.connect(db.DB_NAME)
cursor = conn.cursor()
cursor.execute("SELECT updated_at FROM rates WHERE target_currency='USD'")
updated_at = cursor.fetchone()[0]
import re
assert re.match(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}", updated_at)
conn.close()
def test_save_rate_multiple_currencies():
db.save_rate(1, 'USD', 70)
db.save_rate(2, 'EUR', 80)
db.save_rate(3, 'GBP', 90)
conn = sqlite3.connect(db.DB_NAME)
cursor = conn.cursor()
cursor.execute("SELECT count(*) FROM rates")
assert cursor.fetchone()[0] == 3
conn.close()
@pytest.mark.parametrize("currency,rate", [
("TEST", 0),
("NEG", -1.23),
("FLOAT", 9999999.99),
("LONG", 1e20),
])
def test_save_rate_edge_cases(currency, rate):
db.save_rate(1, currency, rate)
conn = sqlite3.connect(db.DB_NAME)
cursor = conn.cursor()
cursor.execute(
"SELECT rate FROM rates WHERE target_currency=?", (currency,)
)
assert cursor.fetchone()[0] == rate
conn.close()
def test_save_rate_database_connection_error(monkeypatch):
def bad_connect(*args, **kwargs):
raise sqlite3.OperationalError("connection failed")
monkeypatch.setattr(sqlite3, 'connect', bad_connect)
with pytest.raises(sqlite3.OperationalError):
db.save_rate(1, 'USD', 65.00)
def test_save_rate_commit_error(monkeypatch):
monkeypatch.setattr(
"sqlite3.connect", lambda *args, **kwargs: MockConnection()
)
with pytest.raises(sqlite3.DatabaseError):
db.save_rate(1, 'USD', 65.00)
@pytest.mark.parametrize("id,cur,rate", [
("1", "USD", 70.5),
(None, "EUR", 80),
(3, 123, 50),
(4, "USD", "125"),
])
def test_save_rate_parameter_types(id, cur, rate):
try:
db.save_rate(id, cur, rate)
except Exception:
pass
@pytest.mark.parametrize(
"currency", ["USD'; DROP TABLE rates; --", "EUR OR 1=1"]
)
def test_save_rate_sql_injection_protection(currency):
db.save_rate(1, currency, 75)
conn = sqlite3.connect(db.DB_NAME)
cursor = conn.cursor()
cursor.execute(
"SELECT rate FROM rates WHERE target_currency=?", (currency,)
)
val = cursor.fetchone()
assert val is not None
conn.close()
def test_get_saved_rate_success():
db.save_rate(1, 'USD', 90)
assert db.get_saved_rate('USD') == 90
def test_get_saved_rate_default_currency():
db.save_rate(1, 'USD', 70)
assert db.get_saved_rate('USD') == 70
def test_get_saved_rate_nonexistent_currency():
assert db.get_saved_rate('ABC') is None
def test_get_saved_rate_empty_database():
assert db.get_saved_rate('USD') is None
def test_get_saved_rate_multiple_currencies():
db.save_rate(1, 'USD', 70)
db.save_rate(2, 'EUR', 80)
db.save_rate(3, 'GBP', 90)
assert db.get_saved_rate('USD') == 70
assert db.get_saved_rate('EUR') == 80
assert db.get_saved_rate('GBP') == 90
def test_get_saved_rate_case_sensitivity():
db.save_rate(1, 'usd', 70)
assert db.get_saved_rate('usd') == 70
assert db.get_saved_rate('USD') is None
@pytest.mark.parametrize(
"currency", ["USD'; DROP TABLE rates; --", "EUR OR 1=1"]
)
def test_get_saved_rate_sql_injection_protection(currency):
db.save_rate(1, currency, 77)
rate = db.get_saved_rate(currency)
assert rate == 77
def test_get_saved_rate_database_connection_error(monkeypatch):
def bad_connect(*args, **kwargs):
raise sqlite3.OperationalError("fail connect")
monkeypatch.setattr(sqlite3, 'connect', bad_connect)
with pytest.raises(sqlite3.OperationalError):
db.get_saved_rate('USD')