Skip to content

Commit a605d5c

Browse files
committed
Add integration tests
1 parent df7e1f9 commit a605d5c

1 file changed

Lines changed: 196 additions & 0 deletions

File tree

tests/integration/test_magics.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import os
15+
import pytest
16+
import certifi
17+
from unittest import mock
18+
19+
from google.cloud.dataproc_spark_connect import DataprocSparkSession
20+
21+
22+
_SERVICE_ACCOUNT_KEY_FILE_ = "service_account_key.json"
23+
24+
25+
@pytest.fixture(params=[None, "3.0"])
26+
def image_version(request):
27+
return request.param
28+
29+
30+
@pytest.fixture
31+
def test_project():
32+
return os.getenv("GOOGLE_CLOUD_PROJECT")
33+
34+
35+
@pytest.fixture
36+
def test_region():
37+
return os.getenv("GOOGLE_CLOUD_REGION")
38+
39+
40+
def is_ci_environment():
41+
"""Detect if running in CI environment."""
42+
return os.getenv("CI") == "true" or os.getenv("GITHUB_ACTIONS") == "true"
43+
44+
45+
@pytest.fixture
46+
def auth_type(request):
47+
"""Auto-detect authentication type based on environment.
48+
49+
CI environment (CI=true or GITHUB_ACTIONS=true): Uses SERVICE_ACCOUNT
50+
Local environment: Uses END_USER_CREDENTIALS
51+
Test parametrization can still override this default.
52+
"""
53+
# Allow test parametrization to override
54+
if hasattr(request, "param"):
55+
return request.param
56+
57+
# Auto-detect based on environment
58+
if is_ci_environment():
59+
return "SERVICE_ACCOUNT"
60+
else:
61+
return "END_USER_CREDENTIALS"
62+
63+
64+
@pytest.fixture
65+
def test_subnet():
66+
return os.getenv("DATAPROC_SPARK_CONNECT_SUBNET")
67+
68+
69+
@pytest.fixture
70+
def test_subnetwork_uri(test_subnet):
71+
# Make DATAPROC_SPARK_CONNECT_SUBNET the full URI to align with how user would specify it in the project
72+
return test_subnet
73+
74+
75+
@pytest.fixture
76+
def os_environment(auth_type, image_version, test_project, test_region):
77+
original_environment = dict(os.environ)
78+
if os.path.isfile(_SERVICE_ACCOUNT_KEY_FILE_):
79+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = (
80+
_SERVICE_ACCOUNT_KEY_FILE_
81+
)
82+
os.environ["DATAPROC_SPARK_CONNECT_AUTH_TYPE"] = auth_type
83+
if auth_type == "END_USER_CREDENTIALS":
84+
os.environ.pop("DATAPROC_SPARK_CONNECT_SERVICE_ACCOUNT", None)
85+
# Add SSL certificate fix
86+
os.environ["SSL_CERT_FILE"] = certifi.where()
87+
os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
88+
yield os.environ
89+
os.environ.clear()
90+
os.environ.update(original_environment)
91+
92+
@pytest.fixture
93+
def connect_session(test_project, test_region, os_environment):
94+
session = (
95+
DataprocSparkSession.builder.projectId(test_project)
96+
.location(test_region)
97+
.getOrCreate()
98+
)
99+
yield session
100+
# Clean up the session after each test to prevent resource conflicts
101+
try:
102+
session.stop()
103+
except Exception:
104+
# Ignore cleanup errors to avoid masking the actual test failure
105+
pass
106+
107+
# Tests for magics.py
108+
@pytest.fixture
109+
def ipython_shell(connect_session):
110+
"""Provides an IPython shell with a DataprocSparkSession in user_ns."""
111+
pytest.importorskip("IPython", reason="IPython not available")
112+
try:
113+
from IPython.terminal.interactiveshell import TerminalInteractiveShell
114+
from google.cloud.dataproc_spark_connect import magics
115+
116+
shell = TerminalInteractiveShell.instance()
117+
shell.user_ns = {"spark": connect_session}
118+
119+
# Load magics
120+
magics.load_ipython_extension(shell)
121+
122+
yield shell
123+
finally:
124+
from IPython.terminal.interactiveshell import TerminalInteractiveShell
125+
126+
TerminalInteractiveShell.clear_instance()
127+
128+
129+
def test_dp_spark_pip_magic_loads(ipython_shell):
130+
"""Test that %dp_spark_pip magic is registered."""
131+
assert "dp_spark_pip" in ipython_shell.magics_manager.magics["line"]
132+
133+
134+
@mock.patch.object(DataprocSparkSession, "addArtifacts")
135+
def test_dp_spark_pip_install_single_package(
136+
mock_add_artifacts, ipython_shell, capsys
137+
):
138+
"""Test installing a single package with %dp_spark_pip."""
139+
ipython_shell.run_line_magic("dp_spark_pip", "install pandas")
140+
mock_add_artifacts.assert_called_once_with("pandas", pypi=True)
141+
captured = capsys.readouterr()
142+
assert "Installing packages: " in captured.out
143+
assert "Packages successfully added as artifacts." in captured.out
144+
145+
146+
@mock.patch.object(DataprocSparkSession, "addArtifacts")
147+
def test_dp_spark_pip_install_multiple_packages_with_flags(
148+
mock_add_artifacts, ipython_shell, capsys
149+
):
150+
"""Test installing multiple packages with flags like -U."""
151+
ipython_shell.run_line_magic("dp_spark_pip", "install -U numpy scikit-learn")
152+
calls = [
153+
mock.call("numpy", pypi=True),
154+
mock.call("scikit-learn", pypi=True),
155+
]
156+
mock_add_artifacts.assert_has_calls(calls, any_order=True)
157+
assert mock_add_artifacts.call_count == 2
158+
captured = capsys.readouterr()
159+
assert "Installing packages: " in captured.out
160+
assert "Packages successfully added as artifacts." in captured.out
161+
162+
163+
def test_dp_spark_pip_no_install_command(ipython_shell, capsys):
164+
"""Test usage message when 'install' is missing."""
165+
ipython_shell.run_line_magic("dp_spark_pip", "pandas")
166+
captured = capsys.readouterr()
167+
assert "Usage: %dp_spark_pip install <package1> <package2> ..." in captured.out
168+
assert "No packages specified." in captured.out
169+
170+
171+
def test_dp_spark_pip_no_packages(ipython_shell, capsys):
172+
"""Test message when no packages are specified."""
173+
ipython_shell.run_line_magic("dp_spark_pip", "install")
174+
captured = capsys.readouterr()
175+
assert "No packages specified." in captured.out
176+
177+
178+
@mock.patch.object(DataprocSparkSession, "addArtifacts")
179+
def test_dp_spark_pip_no_session(mock_add_artifacts, ipython_shell, capsys):
180+
"""Test message when no Spark session is active."""
181+
ipython_shell.user_ns = {} # Remove spark session from namespace
182+
ipython_shell.run_line_magic("dp_spark_pip", "install pandas")
183+
captured = capsys.readouterr()
184+
assert "No active Spark Sessions found." in captured.out
185+
mock_add_artifacts.assert_not_called()
186+
187+
188+
@mock.patch.object(
189+
DataprocSparkSession, "addArtifacts", side_effect=Exception("Install failed")
190+
)
191+
def test_dp_spark_pip_install_failure(mock_add_artifacts, ipython_shell, capsys):
192+
"""Test error message on installation failure."""
193+
ipython_shell.run_line_magic("dp_spark_pip", "install bad-package")
194+
mock_add_artifacts.assert_called_once_with("bad-package", pypi=True)
195+
captured = capsys.readouterr()
196+
assert "Failed to add artifacts: Install failed" in captured.out

0 commit comments

Comments
 (0)