forked from feast-dev/feast
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_ui_server.py
More file actions
249 lines (184 loc) · 8.38 KB
/
test_ui_server.py
File metadata and controls
249 lines (184 loc) · 8.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import contextlib
import json
import os
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
import assertpy
import pytest
from fastapi.testclient import TestClient
from feast.ui_server import get_app
# Test constants
EXPECTED_SUCCESS_STATUS = 200
EXPECTED_ERROR_STATUS = 503
TEST_PROJECT_NAME = "test_project"
def _create_mock_ui_files(temp_dir):
"""Helper function to create required UI files structure"""
ui_dir = os.path.join(temp_dir, "ui", "build")
os.makedirs(ui_dir, exist_ok=True)
projects_file = os.path.join(ui_dir, "projects-list.json")
with open(projects_file, "w") as f:
json.dump({"projects": []}, f)
index_file = os.path.join(ui_dir, "index.html")
with open(index_file, "w") as f:
f.write("<html><body>Test UI</body></html>")
@contextlib.contextmanager
def _setup_importlib_mocks(temp_dir):
"""Helper function to setup importlib resource mocks."""
mock_path = Path(temp_dir)
mock_context_manager = MagicMock()
mock_context_manager.__enter__.return_value = mock_path
mock_context_manager.__exit__.return_value = None
mock_file_ref = MagicMock()
mock_file_ref.__truediv__.return_value = MagicMock()
with (
patch("feast.ui_server.importlib_resources.files") as mock_files,
patch("feast.ui_server.importlib_resources.as_file") as mock_as_file,
):
mock_files.return_value = mock_file_ref
mock_as_file.return_value = mock_context_manager
yield mock_files, mock_as_file
def _make_project_mock(name, description=""):
"""Create a mock project object with the given name and description."""
proj = MagicMock()
proj.name = name
proj.description = description
return proj
@pytest.fixture
def mock_feature_store():
"""Fixture for creating a mock feature store"""
mock_store = MagicMock()
return mock_store
@pytest.fixture
def ui_app_with_registry(mock_feature_store):
"""Fixture for UI app with valid registry data."""
mock_registry = MagicMock()
mock_registry.list_projects.return_value = []
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
app = get_app(mock_feature_store, TEST_PROJECT_NAME)
yield app
@pytest.fixture
def ui_app_without_registry(mock_feature_store):
"""Fixture for UI app where registry raises on list_projects."""
mock_registry = MagicMock()
mock_registry.list_projects.side_effect = Exception("registry unavailable")
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
app = get_app(mock_feature_store, TEST_PROJECT_NAME)
yield app
def test_ui_server_health_endpoint(ui_app_with_registry):
"""Health endpoint returns 200 when registry is available."""
client = TestClient(ui_app_with_registry)
response = client.get("/health")
assertpy.assert_that(response.status_code).is_equal_to(EXPECTED_SUCCESS_STATUS)
def test_ui_server_health_endpoint_with_unavailable_registry(ui_app_without_registry):
"""Health endpoint returns 503 when registry is unavailable."""
client = TestClient(ui_app_without_registry)
response = client.get("/health")
assertpy.assert_that(response.status_code).is_equal_to(EXPECTED_ERROR_STATUS)
@pytest.mark.parametrize(
"registry_available,expected_status",
[(True, EXPECTED_SUCCESS_STATUS), (False, EXPECTED_ERROR_STATUS)],
)
def test_health_endpoint_status(
registry_available, expected_status, mock_feature_store
):
"""Health endpoint returns correct status based on registry availability."""
mock_registry = MagicMock()
if registry_available:
mock_registry.list_projects.return_value = []
else:
mock_registry.list_projects.side_effect = Exception("unavailable")
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
app = get_app(mock_feature_store, TEST_PROJECT_NAME)
client = TestClient(app)
response = client.get("/health")
assertpy.assert_that(response.status_code).is_equal_to(expected_status)
def test_catch_all_route(ui_app_with_registry):
"""Test the catch-all route for React router paths."""
client = TestClient(ui_app_with_registry)
with pytest.raises(Exception):
client.get("/p/some/react/path")
# ---------- projects-list.json tests ----------
def _read_projects_list(temp_dir):
"""Read the projects-list.json written by get_app via the mock (ui_dir = temp_dir)."""
projects_file = os.path.join(temp_dir, "projects-list.json")
with open(projects_file) as f:
return json.load(f)
def test_projects_list_registry_path(mock_feature_store):
"""projects-list.json uses /api/v1 as registryPath."""
mock_registry = MagicMock()
mock_registry.list_projects.return_value = []
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
get_app(mock_feature_store, TEST_PROJECT_NAME)
data = _read_projects_list(temp_dir)
assertpy.assert_that(data["projects"][0]["registryPath"]).is_equal_to("/api/v1")
def test_projects_list_with_root_path(mock_feature_store):
"""root_path prefix is included in registryPath."""
mock_registry = MagicMock()
mock_registry.list_projects.return_value = []
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
get_app(
mock_feature_store,
TEST_PROJECT_NAME,
root_path="/feast",
)
data = _read_projects_list(temp_dir)
assertpy.assert_that(data["projects"][0]["registryPath"]).is_equal_to(
"/feast/api/v1"
)
def test_projects_list_multiple_projects(mock_feature_store):
"""Multiple projects get an 'All Projects' entry prepended."""
mock_registry = MagicMock()
mock_registry.list_projects.return_value = [
_make_project_mock("project_alpha", "Alpha project"),
_make_project_mock("project_beta", "Beta project"),
]
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
get_app(mock_feature_store, TEST_PROJECT_NAME)
data = _read_projects_list(temp_dir)
assertpy.assert_that(len(data["projects"])).is_equal_to(3)
assertpy.assert_that(data["projects"][0]["id"]).is_equal_to("all")
assertpy.assert_that(data["projects"][1]["id"]).is_equal_to("project_alpha")
assertpy.assert_that(data["projects"][2]["id"]).is_equal_to("project_beta")
def test_projects_list_fallback_on_empty(mock_feature_store):
"""When list_projects returns empty, fallback project is used."""
mock_registry = MagicMock()
mock_registry.list_projects.return_value = []
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
get_app(mock_feature_store, TEST_PROJECT_NAME)
data = _read_projects_list(temp_dir)
assertpy.assert_that(len(data["projects"])).is_equal_to(1)
assertpy.assert_that(data["projects"][0]["id"]).is_equal_to(TEST_PROJECT_NAME)
def test_projects_list_fallback_on_exception(mock_feature_store):
"""When list_projects raises, fallback project is used."""
mock_registry = MagicMock()
mock_registry.list_projects.side_effect = Exception("not implemented")
mock_feature_store.registry = mock_registry
with tempfile.TemporaryDirectory() as temp_dir:
_create_mock_ui_files(temp_dir)
with _setup_importlib_mocks(temp_dir):
get_app(mock_feature_store, TEST_PROJECT_NAME)
data = _read_projects_list(temp_dir)
assertpy.assert_that(len(data["projects"])).is_equal_to(1)
assertpy.assert_that(data["projects"][0]["id"]).is_equal_to(TEST_PROJECT_NAME)