Skip to content

Commit 39cb0a1

Browse files
committed
Tests for SQL
1 parent 5c3ffbc commit 39cb0a1

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed

modules/generic/tests/test_sql.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import pytest
2+
from testcontainers.core.exceptions import ContainerStartException
3+
from testcontainers.generic.sql import SqlContainer
4+
5+
6+
class SimpleSqlContainer(SqlContainer):
7+
"""Simple concrete implementation for testing."""
8+
9+
def __init__(self, image: str = "postgres:13"):
10+
super().__init__(image)
11+
self.username = "testuser"
12+
self.password = "testpass"
13+
self.dbname = "testdb"
14+
self.port = 5432
15+
16+
def get_connection_url(self) -> str:
17+
return self._create_connection_url(
18+
dialect="postgresql", username=self.username, password=self.password, port=self.port, dbname=self.dbname
19+
)
20+
21+
def _configure(self) -> None:
22+
self.with_env("POSTGRES_USER", self.username)
23+
self.with_env("POSTGRES_PASSWORD", self.password)
24+
self.with_env("POSTGRES_DB", self.dbname)
25+
self.with_exposed_ports(self.port)
26+
27+
28+
class TestSqlContainer:
29+
def test_abstract_methods_raise_not_implemented(self):
30+
container = SqlContainer("test:latest")
31+
32+
with pytest.raises(NotImplementedError):
33+
container.get_connection_url()
34+
35+
with pytest.raises(NotImplementedError):
36+
container._configure()
37+
38+
def test_transfer_seed_default_behavior(self):
39+
container = SqlContainer("test:latest")
40+
# Should not raise an exception
41+
container._transfer_seed()
42+
43+
def test_connection_url_creation_basic(self):
44+
container = SimpleSqlContainer()
45+
container._container = type("MockContainer", (), {})() # Simple mock
46+
container.get_container_host_ip = lambda: "localhost"
47+
container.get_exposed_port = lambda port: port
48+
49+
url = container._create_connection_url(dialect="postgresql", username="user", password="pass", port=5432)
50+
51+
assert url == "postgresql://user:pass@localhost:5432"
52+
53+
def test_connection_url_with_database_name(self):
54+
container = SimpleSqlContainer()
55+
container._container = type("MockContainer", (), {})()
56+
container.get_container_host_ip = lambda: "localhost"
57+
container.get_exposed_port = lambda port: port
58+
59+
url = container._create_connection_url(
60+
dialect="postgresql", username="user", password="pass", port=5432, dbname="mydb"
61+
)
62+
63+
assert url == "postgresql://user:pass@localhost:5432/mydb"
64+
65+
def test_connection_url_with_special_characters(self):
66+
container = SimpleSqlContainer()
67+
container._container = type("MockContainer", (), {})()
68+
container.get_container_host_ip = lambda: "localhost"
69+
container.get_exposed_port = lambda port: port
70+
71+
url = container._create_connection_url(
72+
dialect="postgresql", username="user@domain", password="p@ss/word", port=5432
73+
)
74+
75+
# Check that special characters are URL encoded
76+
assert "user%40domain" in url
77+
assert "p%40ss%2Fword" in url
78+
79+
def test_connection_url_with_query_params(self):
80+
container = SimpleSqlContainer()
81+
container._container = type("MockContainer", (), {})()
82+
container.get_container_host_ip = lambda: "localhost"
83+
container.get_exposed_port = lambda port: port
84+
85+
url = container._create_connection_url(
86+
dialect="postgresql",
87+
username="user",
88+
password="pass",
89+
port=5432,
90+
query_params={"ssl": "require", "timeout": "30"},
91+
)
92+
93+
assert "?" in url
94+
assert "ssl=require" in url
95+
assert "timeout=30" in url
96+
97+
def test_connection_url_validation_errors(self):
98+
container = SimpleSqlContainer()
99+
container._container = type("MockContainer", (), {})()
100+
101+
# Test missing dialect
102+
with pytest.raises(ValueError, match="Database dialect is required"):
103+
container._create_connection_url("", "user", "pass", port=5432)
104+
105+
# Test missing username
106+
with pytest.raises(ValueError, match="Database username is required"):
107+
container._create_connection_url("postgresql", "", "pass", port=5432)
108+
109+
# Test missing port
110+
with pytest.raises(ValueError, match="Database port is required"):
111+
container._create_connection_url("postgresql", "user", "pass", port=None)
112+
113+
def test_connection_url_container_not_started(self):
114+
container = SimpleSqlContainer()
115+
container._container = None
116+
117+
with pytest.raises(ContainerStartException, match="Container has not been started"):
118+
container._create_connection_url("postgresql", "user", "pass", port=5432)
119+
120+
def test_container_configuration(self):
121+
container = SimpleSqlContainer("postgres:13")
122+
123+
# Test that configuration sets up environment
124+
container._configure()
125+
126+
assert container.env["POSTGRES_USER"] == "testuser"
127+
assert container.env["POSTGRES_PASSWORD"] == "testpass"
128+
assert container.env["POSTGRES_DB"] == "testdb"
129+
130+
def test_concrete_container_connection_url(self):
131+
container = SimpleSqlContainer()
132+
container._container = type("MockContainer", (), {})()
133+
container.get_container_host_ip = lambda: "localhost"
134+
container.get_exposed_port = lambda port: 5432
135+
136+
url = container.get_connection_url()
137+
138+
assert url.startswith("postgresql://")
139+
assert "testuser" in url
140+
assert "testpass" in url
141+
assert "testdb" in url
142+
assert "localhost:5432" in url
143+
144+
def test_container_inheritance(self):
145+
container = SimpleSqlContainer()
146+
147+
assert isinstance(container, SqlContainer)
148+
assert hasattr(container, "get_connection_url")
149+
assert hasattr(container, "_configure")
150+
assert hasattr(container, "_transfer_seed")
151+
assert hasattr(container, "start")
152+
153+
def test_additional_transient_errors_list(self):
154+
from testcontainers.generic.sql import ADDITIONAL_TRANSIENT_ERRORS
155+
156+
assert isinstance(ADDITIONAL_TRANSIENT_ERRORS, list)
157+
# List may be empty if SQLAlchemy not available, or contain DBAPIError if it is
158+
159+
def test_empty_password_handling(self):
160+
container = SimpleSqlContainer()
161+
container._container = type("MockContainer", (), {})()
162+
container.get_container_host_ip = lambda: "localhost"
163+
container.get_exposed_port = lambda port: port
164+
165+
url = container._create_connection_url(dialect="postgresql", username="user", password="", port=5432)
166+
167+
assert url == "postgresql://user:@localhost:5432"
168+
169+
def test_unicode_characters_in_credentials(self):
170+
container = SimpleSqlContainer()
171+
container._container = type("MockContainer", (), {})()
172+
container.get_container_host_ip = lambda: "localhost"
173+
container.get_exposed_port = lambda port: port
174+
175+
url = container._create_connection_url(
176+
dialect="postgresql", username="usér", password="päss", port=5432, dbname="tëstdb"
177+
)
178+
179+
assert "us%C3%A9r" in url
180+
assert "p%C3%A4ss" in url
181+
assert "t%C3%ABstdb" in url
182+
183+
def test_start_postgres_container_integration(self):
184+
"""Integration test that actually starts a PostgreSQL container."""
185+
container = SimpleSqlContainer()
186+
187+
# This will start the container and test the connection
188+
container.start()
189+
190+
# Verify the container is running
191+
assert container._container is not None
192+
193+
# Test that we can get a connection URL
194+
url = container.get_connection_url()
195+
assert url.startswith("postgresql://")
196+
assert "testuser" in url
197+
assert "testdb" in url
198+
199+
# Verify environment variables are set
200+
assert container.env["POSTGRES_USER"] == "testuser"
201+
assert container.env["POSTGRES_PASSWORD"] == "testpass"
202+
assert container.env["POSTGRES_DB"] == "testdb"
203+
204+
# check logs
205+
logs = container.get_logs()
206+
assert "database system is ready to accept connections" in logs[0].decode("utf-8").lower()
207+
208+
def test_sql_postgres_container_integration(self):
209+
"""Integration test for SqlContainer with PostgreSQL."""
210+
container = SimpleSqlContainer()
211+
212+
# This will start the container and test the connection
213+
container.start()
214+
215+
# Verify the container is running
216+
assert container._container is not None
217+
218+
# Test that we can get a connection URL
219+
url = container.get_connection_url()
220+
221+
# check sql operations
222+
import sqlalchemy
223+
224+
engine = sqlalchemy.create_engine(url)
225+
with engine.connect() as conn:
226+
# Create a test table
227+
conn.execute(
228+
sqlalchemy.text("CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(50));")
229+
)
230+
# Insert a test record
231+
conn.execute(sqlalchemy.text("INSERT INTO test_table (name) VALUES ('test_name');"))
232+
# Query the test record
233+
result = conn.execute(sqlalchemy.text("SELECT name FROM test_table WHERE name='test_name';"))
234+
fetched = result.fetchone()
235+
assert fetched is not None
236+
assert fetched[0] == "test_name"

0 commit comments

Comments
 (0)