Skip to content

Commit b5220fe

Browse files
committed
Add Docker disovery and definition integration tests
1 parent b443875 commit b5220fe

1 file changed

Lines changed: 304 additions & 0 deletions

File tree

tests/test_database_base.py

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,183 @@ def test_list_connections_shows_db(self, request, cli_runner):
5151
assert connection in result.stdout
5252
assert self.config.display_name in result.stdout
5353

54+
def test_docker_container_detection(self, request):
55+
"""Test that docker discovery detects the database container.
56+
57+
This ensures that the docker auto-discovery feature can find
58+
containers for this database type in the connection picker.
59+
"""
60+
# Skip for file-based databases (they don't use Docker containers)
61+
from sqlit.db.providers import is_file_based
62+
63+
if is_file_based(self.config.db_type):
64+
pytest.skip(f"{self.config.display_name} is file-based, no Docker container")
65+
66+
from sqlit.services.docker_detector import (
67+
IMAGE_PATTERNS,
68+
DockerStatus,
69+
detect_database_containers,
70+
)
71+
72+
# Skip if this database type has no Docker image patterns defined
73+
has_pattern = any(
74+
db_type == self.config.db_type
75+
for db_type in IMAGE_PATTERNS.values()
76+
)
77+
if not has_pattern:
78+
pytest.skip(f"{self.config.display_name} has no Docker image patterns")
79+
80+
status, containers = detect_database_containers()
81+
82+
if status != DockerStatus.AVAILABLE:
83+
pytest.skip("Docker is not available")
84+
85+
# Find a container matching this database type
86+
matching_containers = [
87+
c for c in containers if c.db_type == self.config.db_type
88+
]
89+
90+
assert len(matching_containers) > 0, (
91+
f"No Docker container detected for {self.config.display_name}. "
92+
f"Found containers: {[(c.container_name, c.db_type) for c in containers]}"
93+
)
94+
95+
# Verify the container has a port detected
96+
container = matching_containers[0]
97+
assert container.port is not None, (
98+
f"Container {container.container_name} has no port detected"
99+
)
100+
101+
def test_docker_container_no_password_prompt_when_not_needed(self, request):
102+
"""Test that docker discovery doesn't trigger password prompts for no-auth databases.
103+
104+
Some databases (CockroachDB, Turso) can run without authentication in
105+
local/insecure mode. When docker discovery detects these containers,
106+
it should return password="" (empty string) rather than password=None.
107+
108+
- password=None means "not set" -> UI will prompt for password
109+
- password="" means "explicitly empty" -> UI will NOT prompt
110+
111+
This test ensures users aren't asked for passwords for databases
112+
that don't need them.
113+
"""
114+
# Skip for file-based databases (they don't use Docker containers)
115+
from sqlit.db.providers import is_file_based
116+
117+
if is_file_based(self.config.db_type):
118+
pytest.skip(f"{self.config.display_name} is file-based, no Docker container")
119+
120+
from sqlit.services.docker_detector import (
121+
DockerStatus,
122+
container_to_connection_config,
123+
detect_database_containers,
124+
)
125+
126+
status, containers = detect_database_containers()
127+
128+
if status != DockerStatus.AVAILABLE:
129+
pytest.skip("Docker is not available")
130+
131+
# Find a container matching this database type
132+
matching_containers = [
133+
c for c in containers if c.db_type == self.config.db_type
134+
]
135+
136+
if not matching_containers:
137+
pytest.skip(f"No Docker container found for {self.config.display_name}")
138+
139+
container = matching_containers[0]
140+
config = container_to_connection_config(container)
141+
142+
# Databases that don't require auth should have password="" not None
143+
# This prevents the UI from showing "Password Required" dialog
144+
from sqlit.db.providers import requires_auth
145+
146+
if not requires_auth(self.config.db_type):
147+
assert config.password is not None, (
148+
f"{self.config.display_name} doesn't require authentication, but "
149+
f"password is None. This will cause the UI to prompt for a password. "
150+
f"Set password='' (empty string) in docker_detector.py for databases "
151+
f"that don't need auth."
152+
)
153+
154+
def test_docker_container_connection(self, request):
155+
"""Test that docker-discovered credentials actually work.
156+
157+
This tests the full docker discovery flow:
158+
1. Detect the container
159+
2. Convert to ConnectionConfig
160+
3. Connect using discovered credentials
161+
4. Run a simple query
162+
163+
This catches issues like:
164+
- Wrong host (localhost vs 127.0.0.1 for MySQL/MariaDB)
165+
- Missing or incorrect credentials
166+
- Wrong port mappings
167+
"""
168+
# Skip for file-based databases (they don't use Docker containers)
169+
from sqlit.db.providers import is_file_based
170+
171+
if is_file_based(self.config.db_type):
172+
pytest.skip(f"{self.config.display_name} is file-based, no Docker container")
173+
174+
from sqlit.db.adapters import get_adapter
175+
from sqlit.services.docker_detector import (
176+
DockerStatus,
177+
container_to_connection_config,
178+
detect_database_containers,
179+
)
180+
181+
status, containers = detect_database_containers()
182+
183+
if status != DockerStatus.AVAILABLE:
184+
pytest.skip("Docker is not available")
185+
186+
# Find a container matching this database type
187+
matching_containers = [
188+
c for c in containers if c.db_type == self.config.db_type
189+
]
190+
191+
if not matching_containers:
192+
pytest.skip(f"No Docker container found for {self.config.display_name}")
193+
194+
container = matching_containers[0]
195+
if not container.connectable:
196+
pytest.skip(f"Container {container.container_name} is not connectable")
197+
198+
# Convert to ConnectionConfig (this is what the UI does)
199+
config = container_to_connection_config(container)
200+
201+
# Get the adapter and try to connect
202+
adapter = get_adapter(config.db_type)
203+
204+
try:
205+
conn = adapter.connect(config)
206+
except Exception as e:
207+
pytest.fail(
208+
f"Failed to connect using docker-discovered credentials:\n"
209+
f" Container: {container.container_name}\n"
210+
f" Host: {config.server}\n"
211+
f" Port: {config.port}\n"
212+
f" Username: {config.username}\n"
213+
f" Password: {'***' if config.password else 'None'}\n"
214+
f" Database: {config.database}\n"
215+
f" Error: {e}"
216+
)
217+
218+
# Run a simple query to verify connection works
219+
try:
220+
adapter.execute_test_query(conn)
221+
except Exception as e:
222+
pytest.fail(
223+
f"Connected but failed to execute query: {e}"
224+
)
225+
finally:
226+
try:
227+
conn.close()
228+
except Exception:
229+
pass
230+
54231
def test_query_select(self, request, cli_runner):
55232
"""Test executing SELECT query."""
56233
connection = request.getfixturevalue(self.config.connection_fixture)
@@ -520,6 +697,133 @@ def test_get_sequences(self, request):
520697
f"Found sequences: {[seq.name for seq in sequences]}"
521698
)
522699

700+
def test_get_trigger_definition(self, request):
701+
"""Test that adapter correctly retrieves trigger definitions.
702+
703+
This tests the code path used when a user clicks on a trigger
704+
in the TUI tree view to see its details.
705+
"""
706+
from sqlit.config import load_connections
707+
from sqlit.db.adapters import get_adapter
708+
from sqlit.services.session import ConnectionSession
709+
710+
connection_name = request.getfixturevalue(self.config.connection_fixture)
711+
connections = load_connections()
712+
config = next((c for c in connections if c.name == connection_name), None)
713+
assert config is not None, f"Connection {connection_name} not found"
714+
715+
with ConnectionSession.create(config, get_adapter) as session:
716+
if not session.adapter.supports_triggers:
717+
pytest.skip(f"{self.config.display_name} does not support triggers")
718+
719+
# First get triggers to find one to look up
720+
triggers = session.adapter.get_triggers(
721+
session.connection,
722+
database=config.database if session.adapter.supports_multiple_databases else None,
723+
)
724+
725+
test_trigger = next(
726+
(trg for trg in triggers if "test_users_audit" in trg.name.lower()),
727+
None,
728+
)
729+
if test_trigger is None:
730+
pytest.skip("Test trigger not found")
731+
732+
# Now get the definition (simulates user clicking on trigger in TUI)
733+
info = session.adapter.get_trigger_definition(
734+
session.connection,
735+
test_trigger.name,
736+
test_trigger.table_name,
737+
database=config.database if session.adapter.supports_multiple_databases else None,
738+
)
739+
740+
assert isinstance(info, dict), "get_trigger_definition should return a dict"
741+
assert "name" in info, "Trigger info should contain 'name'"
742+
743+
def test_get_sequence_definition(self, request):
744+
"""Test that adapter correctly retrieves sequence definitions.
745+
746+
This tests the code path used when a user clicks on a sequence
747+
in the TUI tree view to see its details.
748+
"""
749+
from sqlit.config import load_connections
750+
from sqlit.db.adapters import get_adapter
751+
from sqlit.services.session import ConnectionSession
752+
753+
connection_name = request.getfixturevalue(self.config.connection_fixture)
754+
connections = load_connections()
755+
config = next((c for c in connections if c.name == connection_name), None)
756+
assert config is not None, f"Connection {connection_name} not found"
757+
758+
with ConnectionSession.create(config, get_adapter) as session:
759+
if not session.adapter.supports_sequences:
760+
pytest.skip(f"{self.config.display_name} does not support sequences")
761+
762+
# First get sequences to find one to look up
763+
sequences = session.adapter.get_sequences(
764+
session.connection,
765+
database=config.database if session.adapter.supports_multiple_databases else None,
766+
)
767+
768+
test_sequence = next(
769+
(seq for seq in sequences if "test_sequence" in seq.name.lower()),
770+
None,
771+
)
772+
if test_sequence is None:
773+
pytest.skip("Test sequence not found")
774+
775+
# Now get the definition (simulates user clicking on sequence in TUI)
776+
info = session.adapter.get_sequence_definition(
777+
session.connection,
778+
test_sequence.name,
779+
database=config.database if session.adapter.supports_multiple_databases else None,
780+
)
781+
782+
assert isinstance(info, dict), "get_sequence_definition should return a dict"
783+
assert "name" in info, "Sequence info should contain 'name'"
784+
785+
def test_get_index_definition(self, request):
786+
"""Test that adapter correctly retrieves index definitions.
787+
788+
This tests the code path used when a user clicks on an index
789+
in the TUI tree view to see its details.
790+
"""
791+
from sqlit.config import load_connections
792+
from sqlit.db.adapters import get_adapter
793+
from sqlit.services.session import ConnectionSession
794+
795+
connection_name = request.getfixturevalue(self.config.connection_fixture)
796+
connections = load_connections()
797+
config = next((c for c in connections if c.name == connection_name), None)
798+
assert config is not None, f"Connection {connection_name} not found"
799+
800+
with ConnectionSession.create(config, get_adapter) as session:
801+
if not session.adapter.supports_indexes:
802+
pytest.skip(f"{self.config.display_name} does not support indexes")
803+
804+
# First get indexes to find one to look up
805+
indexes = session.adapter.get_indexes(
806+
session.connection,
807+
database=config.database if session.adapter.supports_multiple_databases else None,
808+
)
809+
810+
test_index = next(
811+
(idx for idx in indexes if "test_users_email" in idx.name.lower()),
812+
None,
813+
)
814+
if test_index is None:
815+
pytest.skip("Test index not found")
816+
817+
# Now get the definition (simulates user clicking on index in TUI)
818+
info = session.adapter.get_index_definition(
819+
session.connection,
820+
test_index.name,
821+
test_index.table_name,
822+
database=config.database if session.adapter.supports_multiple_databases else None,
823+
)
824+
825+
assert isinstance(info, dict), "get_index_definition should return a dict"
826+
assert "name" in info, "Index info should contain 'name'"
523827

524828
class BaseDatabaseTestsWithLimit(BaseDatabaseTests):
525829
"""Base tests for databases that support LIMIT syntax."""

0 commit comments

Comments
 (0)