33from __future__ import annotations
44
55import subprocess
6+ from pathlib import Path
67
78import pytest
89
9- from sqlbuild .integrations .ingestr ._helpers .runner import run_ingestr_command
10+ from sqlbuild .integrations .ingestr ._helpers .runner import ensure_duckdb_driver , run_ingestr_command
1011from sqlbuild .integrations .ingestr .exceptions import IngestrIntegrationError
1112from sqlbuild .observability import (
1213 EventDispatcher ,
1314 LifecycleEvent ,
1415 dispatcher_scope ,
1516 invocation_scope ,
1617)
17- from tests .unit .src .sqlbuild .integrations .ingestr ._test_types import IngestrRunnerTestCase
18+ from tests .unit .src .sqlbuild .integrations .ingestr ._test_types import (
19+ IngestrDriverFailureTestCase ,
20+ IngestrDriverTestCase ,
21+ IngestrRunnerTestCase ,
22+ )
23+
24+ _RUNNER : str = "sqlbuild.integrations.ingestr._helpers.runner"
25+ _INSTALL : tuple [str , ...] = ("dbc" , "install" , "--quiet" , "duckdb=1.5.2" )
1826
1927
2028@pytest .mark .parametrize (
@@ -39,6 +47,9 @@ def test_given_signalled_ingestr_when_run_then_safe_terminal_excludes_child_stre
3947 "sqlbuild.integrations.ingestr._helpers.runner.shutil.which" ,
4048 lambda executable : executable ,
4149 )
50+ monkeypatch .setattr (
51+ "sqlbuild.integrations.ingestr._helpers.runner.ensure_duckdb_driver" , lambda : None
52+ )
4253 monkeypatch .setattr (
4354 "sqlbuild.integrations.ingestr._helpers.runner._run_ingestr_subprocess" ,
4455 lambda ** kwargs : subprocess .CompletedProcess (
@@ -92,6 +103,9 @@ def test_given_unobserved_ingestr_interruption_when_run_then_only_start_is_publi
92103 "sqlbuild.integrations.ingestr._helpers.runner.shutil.which" ,
93104 lambda executable : executable ,
94105 )
106+ monkeypatch .setattr (
107+ "sqlbuild.integrations.ingestr._helpers.runner.ensure_duckdb_driver" , lambda : None
108+ )
95109 monkeypatch .setattr (
96110 "sqlbuild.integrations.ingestr._helpers.runner._run_ingestr_subprocess" ,
97111 lambda ** kwargs : (_ for _ in ()).throw (KeyboardInterrupt ),
@@ -105,3 +119,88 @@ def test_given_unobserved_ingestr_interruption_when_run_then_only_start_is_publi
105119 _ = run_ingestr_command (("ingestr" , "run" ))
106120
107121 assert tuple (event .event_type for event in events ) == test_case .expected_event_types
122+
123+
124+ @pytest .mark .parametrize (
125+ "test_case" ,
126+ (
127+ IngestrDriverTestCase (
128+ description = "missing driver installs the duckdb package version" ,
129+ driver_files = (),
130+ environment = {},
131+ expected_installs = (_INSTALL ,),
132+ ),
133+ IngestrDriverTestCase (
134+ description = "driver of another version is replaced" ,
135+ driver_files = (("duckdb.toml" , "version = '2.0.0'\n " ),),
136+ environment = {},
137+ expected_installs = (_INSTALL ,),
138+ ),
139+ IngestrDriverTestCase (
140+ description = "matching driver is kept" ,
141+ driver_files = (("duckdb.toml" , "version = '1.5.2'\n " ),),
142+ environment = {},
143+ expected_installs = (),
144+ ),
145+ IngestrDriverTestCase (
146+ description = "user driver path is left alone" ,
147+ driver_files = (),
148+ environment = {"ADBC_DRIVER_PATH" : "/custom/adbc" },
149+ expected_installs = (),
150+ ),
151+ ),
152+ ids = lambda case : case .description ,
153+ )
154+ def test_given_duckdb_driver_state_when_ensuring_then_installs_only_a_mismatch (
155+ test_case : IngestrDriverTestCase ,
156+ monkeypatch : pytest .MonkeyPatch ,
157+ tmp_path : Path ,
158+ ) -> None :
159+ driver_path : Path = tmp_path / "etc" / "adbc" / "drivers"
160+ driver_path .mkdir (parents = True )
161+ _ = [(driver_path / name ).write_text (text ) for name , text in test_case .driver_files ]
162+ calls : list [tuple [tuple [str , ...], dict [str , str ]]] = []
163+ monkeypatch .setattr (f"{ _RUNNER } .sys.prefix" , str (tmp_path ))
164+ monkeypatch .setattr (f"{ _RUNNER } .environ" , dict (test_case .environment ))
165+ monkeypatch .setattr (f"{ _RUNNER } .metadata.version" , lambda name : "1.5.2" )
166+ monkeypatch .setattr (f"{ _RUNNER } ._dbc_executable" , lambda : "dbc" )
167+ monkeypatch .setattr (
168+ f"{ _RUNNER } .subprocess.run" ,
169+ lambda args , ** kwargs : (
170+ calls .append ((args , kwargs ["env" ])) or subprocess .CompletedProcess (args , 0 , "" , "" )
171+ ),
172+ )
173+
174+ ensure_duckdb_driver ()
175+
176+ assert tuple (args for args , _ in calls ) == test_case .expected_installs
177+ assert all (env ["ADBC_DRIVER_PATH" ] == str (driver_path ) for _ , env in calls )
178+
179+
180+ @pytest .mark .parametrize (
181+ "test_case" ,
182+ (
183+ IngestrDriverFailureTestCase (
184+ description = "failed install names the duckdb version" ,
185+ stderr = "registry unreachable" ,
186+ expected_message = "duckdb 1.5.2: registry unreachable" ,
187+ ),
188+ ),
189+ ids = lambda case : case .description ,
190+ )
191+ def test_given_failing_driver_install_when_ensuring_then_names_the_duckdb_version (
192+ test_case : IngestrDriverFailureTestCase ,
193+ monkeypatch : pytest .MonkeyPatch ,
194+ tmp_path : Path ,
195+ ) -> None :
196+ monkeypatch .setattr (f"{ _RUNNER } .sys.prefix" , str (tmp_path ))
197+ monkeypatch .setattr (f"{ _RUNNER } .environ" , {})
198+ monkeypatch .setattr (f"{ _RUNNER } .metadata.version" , lambda name : "1.5.2" )
199+ monkeypatch .setattr (f"{ _RUNNER } ._dbc_executable" , lambda : "dbc" )
200+ monkeypatch .setattr (
201+ f"{ _RUNNER } .subprocess.run" ,
202+ lambda args , ** kwargs : subprocess .CompletedProcess (args , 1 , "" , test_case .stderr ),
203+ )
204+
205+ with pytest .raises (IngestrIntegrationError , match = test_case .expected_message ):
206+ ensure_duckdb_driver ()
0 commit comments