Skip to content

Commit 950f37d

Browse files
committed
feat(fdc): Added unit tests for Data Connect client factory and _DataConnectService
1 parent f493fb0 commit 950f37d

1 file changed

Lines changed: 287 additions & 0 deletions

File tree

tests/test_data_connect.py

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import pytest
2+
3+
import firebase_admin
4+
from firebase_admin import _utils
5+
from firebase_admin import dataconnect
6+
from firebase_admin import credentials
7+
from tests import testutils
8+
from unittest import mock
9+
10+
class TestConnectorConfig:
11+
def teardown_method(self, method):
12+
del method
13+
testutils.cleanup_apps()
14+
15+
def test_connector_config_initialization(self):
16+
config = dataconnect.ConnectorConfig(
17+
service_id="starterproject",
18+
location = "us-east4",
19+
connector="my_connector"
20+
)
21+
assert config.service_id=="starterproject"
22+
assert config.location=="us-east4"
23+
assert config.connector=="my_connector"
24+
25+
def test_connector_config_is_frozen(self):
26+
config = dataconnect.ConnectorConfig(
27+
service_id="starterproject",
28+
location = "us-east4",
29+
connector="my_connector"
30+
)
31+
32+
with pytest.raises(AttributeError, match="cannot assign to field 'service_id'"):
33+
config.service_id = "changed_id"
34+
with pytest.raises(AttributeError, match="cannot assign to field 'location'"):
35+
config.location = "us-central1"
36+
with pytest.raises(AttributeError, match="cannot assign to field 'connector'"):
37+
config.connector = "changed_connector"
38+
39+
def testing_connector_config_string_written(self):
40+
config = dataconnect.ConnectorConfig(
41+
service_id="starterproject",
42+
location = "us-east4",
43+
connector="my_connector"
44+
)
45+
repr_str=repr(config)
46+
assert "service_id='starterproject'" in repr_str
47+
assert "location='us-east4'" in repr_str
48+
assert "connector='my_connector'" in repr_str
49+
50+
def test_connector_config_empty_strings(self):
51+
with pytest.raises(ValueError, match="service_id cannot be empty"):
52+
dataconnect.ConnectorConfig(service_id="", location="us-east4", connector="my_connector")
53+
54+
with pytest.raises(ValueError, match="location cannot be empty"):
55+
dataconnect.ConnectorConfig(service_id="starterproject", location="", connector="my_connector")
56+
57+
with pytest.raises(ValueError, match="connector cannot be empty"):
58+
dataconnect.ConnectorConfig(service_id="starterproject", location="us-east4", connector="")
59+
60+
def test_connector_config_invalid_types(self):
61+
with pytest.raises(ValueError, match="service_id cannot be empty"):
62+
dataconnect.ConnectorConfig(service_id=None, location="us-east4", connector="my_connector")
63+
with pytest.raises(ValueError, match="location cannot be empty"):
64+
dataconnect.ConnectorConfig(service_id="starterproject", location=123, connector="my_connector")
65+
66+
class TestDataConnect:
67+
def teardown_method(self, method):
68+
del method
69+
testutils.cleanup_apps()
70+
71+
def test_init_property_assignment(self):
72+
cred = testutils.MockCredential()
73+
try:
74+
app = firebase_admin.initialize_app(cred, name = "starter_app")
75+
except Exception:
76+
pytest.fail("initialize app has an error")
77+
78+
config = dataconnect.ConnectorConfig(service_id="starterproject", location = "us-east4", connector="my_connector")
79+
80+
try:
81+
data_connect_instance = dataconnect.DataConnect(app, config)
82+
except Exception:
83+
pytest.fail("DataConnect initialization failed.")
84+
85+
assert data_connect_instance._app is app
86+
assert data_connect_instance._config is config
87+
assert data_connect_instance.app is app
88+
assert data_connect_instance.config is config
89+
90+
assert data_connect_instance._app.name == "starter_app"
91+
assert data_connect_instance._config.service_id == "starterproject"
92+
93+
class TestDataConnectClientFactory:
94+
def teardown_method(self, method):
95+
del method
96+
testutils.cleanup_apps()
97+
98+
def setup_method(self):
99+
self.cred = testutils.MockCredential()
100+
self.app = firebase_admin.initialize_app(self.cred, name = 'starter_app')
101+
self.config1 = dataconnect.ConnectorConfig(service_id='starterproject', location='us-east3', connector='my_connector')
102+
self.config2 = dataconnect.ConnectorConfig(service_id='starterproject2', location='us-east4', connector='my_connector2')
103+
104+
@mock.patch('firebase_admin.dataconnect._DataConnectService.get_client', wraps=dataconnect._DataConnectService.get_client)
105+
def test_client_successful(self, mock_get_client):
106+
client_instance = dataconnect.client(self.config1, app=self.app)
107+
mock_get_client.assert_called_once_with(self.config1)
108+
assert isinstance(client_instance, dataconnect.DataConnect)
109+
assert client_instance.config is self.config1
110+
assert client_instance.app is self.app
111+
112+
def test_client_retrieval_diff_configs(self):
113+
client1 = dataconnect.client(self.config1, app=self.app)
114+
client2 = dataconnect.client(self.config2, app=self.app)
115+
116+
assert client1 is not client2
117+
assert client1.config is self.config1
118+
assert client2.config is self.config2
119+
assert client1.app is self.app
120+
assert client2.app is self.app
121+
122+
def test_client_retrieval_same_config_cached(self):
123+
client1 = dataconnect.client(self.config1, app=self.app)
124+
client2 = dataconnect.client(self.config1, app=self.app)
125+
126+
assert client1 is client2
127+
128+
def test_client_retrieval_different_apps_same_config(self):
129+
app2 = firebase_admin.initialize_app(self.cred, name='app2')
130+
131+
client1 = dataconnect.client(self.config1, app=self.app)
132+
client2 = dataconnect.client(self.config1, app=app2)
133+
134+
assert client1 is not client2
135+
assert client1.app is self.app
136+
assert client1.app is not client2.app
137+
138+
def test_invalid_config_type(self):
139+
with pytest.raises(ValueError, match="Config must be of type firebase_admin.dataconnect.ConnectorConfig"):
140+
dataconnect.client('not-a-config', app=self.app)
141+
142+
def test_invalid_app_type(self):
143+
with pytest.raises(ValueError, match="App must be of type firebase_admin.App"):
144+
dataconnect.client(self.config1, 'not-a-app')
145+
146+
def test_client_default_app(self):
147+
default_app = firebase_admin.initialize_app(self.cred)
148+
client_instance = dataconnect.client(self.config1)
149+
assert client_instance.app is default_app
150+
151+
def test_client_none_config(self):
152+
with pytest.raises(ValueError, match="Config must be of type firebase_admin.dataconnect.ConnectorConfig"):
153+
dataconnect.client(None, app=self.app)
154+
155+
class TestDataConnectService:
156+
def setup_method(self):
157+
self.cred = testutils.MockCredential()
158+
self.app = firebase_admin.initialize_app(self.cred, name = 'starter_app')
159+
self.service = dataconnect._DataConnectService(self.app)
160+
161+
def teardown_method(self, method):
162+
del method
163+
testutils.cleanup_apps()
164+
165+
def test_cache_hit(self):
166+
config = dataconnect.ConnectorConfig('s1', 'l1', 'c1')
167+
client1 = self.service.get_client(config)
168+
client2 = self.service.get_client(config)
169+
assert client1 is client2
170+
171+
assert isinstance(client1, dataconnect.DataConnect)
172+
assert client1.config is config
173+
174+
def test_cache_miss_on_different_config(self):
175+
config1 = dataconnect.ConnectorConfig('s1', 'l1', 'c1')
176+
config2 = dataconnect.ConnectorConfig('s2', 'l2', 'c2')
177+
client1 = self.service.get_client(config1)
178+
client2 = self.service.get_client(config2)
179+
assert client1 is not client2
180+
181+
@pytest.mark.parametrize("config_a, config_b, expect_same", [
182+
(dataconnect.ConnectorConfig('s', 'l', 'c'), dataconnect.ConnectorConfig('s', 'l', 'c_diff'), False),
183+
(dataconnect.ConnectorConfig('s', 'l', 'c'), dataconnect.ConnectorConfig('s', 'l_diff', 'c'), False),
184+
(dataconnect.ConnectorConfig('s', 'l', 'c'), dataconnect.ConnectorConfig('s_diff', 'l', 'c'), False),
185+
(dataconnect.ConnectorConfig('s', 'l', 'c'), dataconnect.ConnectorConfig('s', 'l', 'c'), True),
186+
])
187+
def test_complex_cache_key(self, config_a, config_b, expect_same):
188+
client_a = self.service.get_client(config_a)
189+
client_b = self.service.get_client(config_b)
190+
if expect_same:
191+
assert client_a is client_b
192+
else:
193+
assert client_a is not client_b
194+
195+
def test_config_equivalence(self):
196+
config1 = dataconnect.ConnectorConfig('s1', 'l1', 'c1')
197+
config2 = dataconnect.ConnectorConfig('s1', 'l1', 'c1')
198+
client1 = self.service.get_client(config1)
199+
client2 = self.service.get_client(config2)
200+
assert client1 is client2
201+
202+
@mock.patch('firebase_admin.dataconnect.DataConnect', autospec=True)
203+
def test_client_creation_mocking(self, MockDataConnect):
204+
config1 = dataconnect.ConnectorConfig('s_mock', 'l_mock', 'c_mock1')
205+
config2 = dataconnect.ConnectorConfig('s_mock', 'l_mock', 'c_mock2')
206+
207+
self.service.get_client(config1)
208+
MockDataConnect.assert_called_once_with(app=self.app, config=config1)
209+
210+
MockDataConnect.reset_mock()
211+
212+
self.service.get_client(config1)
213+
MockDataConnect.assert_not_called()
214+
215+
MockDataConnect.reset_mock()
216+
217+
# first call using config2
218+
self.service.get_client(config2)
219+
MockDataConnect.assert_called_once_with(app=self.app, config=config2)
220+
221+
@mock.patch('firebase_admin.dataconnect.DataConnect', autospec=True)
222+
def test_error_handling_in_creation(self, MockDataConnect):
223+
config = dataconnect.ConnectorConfig('s_err', 'l_err', 'c_err')
224+
test_error = RuntimeError("Failed to create client")
225+
MockDataConnect.side_effect = test_error
226+
227+
with pytest.raises(RuntimeError, match="Failed to create client"):
228+
self.service.get_client(config)
229+
230+
# Ensure the failed creation wasn't cached
231+
MockDataConnect.side_effect = None
232+
self.service.get_client(config)
233+
assert MockDataConnect.call_count == 2
234+
235+
def test_invalid_config_in_service(self):
236+
with pytest.raises(ValueError, match="Config must be of type firebase_admin.dataconnect.ConnectorConfig"):
237+
self.service.get_client(None)
238+
239+
class TestDataConnectServiceIntegration:
240+
def setup_method(self):
241+
self.cred = testutils.MockCredential()
242+
self.app1 = firebase_admin.initialize_app(self.cred, name='integ_app1')
243+
self.app2 = firebase_admin.initialize_app(self.cred, name='integ_app2')
244+
245+
self.config1 = dataconnect.ConnectorConfig( service_id='service1', location='us-central1', connector='conn1')
246+
self.config2 = dataconnect.ConnectorConfig(service_id='service2', location='us-east4', connector='conn2')
247+
self.config1_copy = dataconnect.ConnectorConfig(service_id='service1', location='us-central1', connector='conn1')
248+
249+
def teardown_method(self, method):
250+
del method
251+
testutils.cleanup_apps()
252+
253+
def test_overall_client_retrieval_and_caching(self):
254+
client1a = dataconnect.client(self.config1, app=self.app1)
255+
client1b = dataconnect.client(self.config1_copy, app=self.app1)
256+
client2 = dataconnect.client(self.config2, app=self.app1)
257+
258+
assert isinstance(client1a, dataconnect.DataConnect), "Client should be a DataConnect instance"
259+
assert client1a.app is self.app1, "Client should be associated with app1"
260+
assert client1a.config is self.config1, "Client should hold the specific config1"
261+
262+
# Same config
263+
assert client1b is client1a, "Client should be cached for an equivalent config on the same app"
264+
265+
# Different config
266+
assert isinstance(client2, dataconnect.DataConnect), "Client should be a DataConnect instance"
267+
assert client2.app is self.app1, "Client should be associated with app1"
268+
assert client2.config is self.config2, "Client should hold the specific config2"
269+
assert client2 is not client1a, "Clients with different configs should be different instances"
270+
271+
# Different app
272+
client1_app2 = dataconnect.client(self.config1, app=self.app2)
273+
274+
assert isinstance(client1_app2, dataconnect.DataConnect), "Client on app2 should be a DataConnect instance"
275+
assert client1_app2.app is self.app2, "Client should be associated with app2"
276+
assert client1_app2.config is self.config1, "Client on app2 should hold config1"
277+
assert client1_app2 is not client1a, "Clients on different apps should be different instances"
278+
279+
@mock.patch.object(_utils, 'get_app_service', wraps=_utils.get_app_service)
280+
def test_uses_app_service_mechanism(self, mock_get_app_service):
281+
"""Ensures dataconnect.client uses the standard app service loader."""
282+
dataconnect.client(self.config1, app=self.app1)
283+
mock_get_app_service.assert_called_once()
284+
args, _ = mock_get_app_service.call_args
285+
assert args[0] is self.app1
286+
assert args[1] == '_data_connect_service'
287+
assert args[2] == dataconnect._DataConnectService

0 commit comments

Comments
 (0)