Skip to content

Commit b1489c1

Browse files
committed
♻️ Add Unitest and fix node.js server env read
1 parent 7511450 commit b1489c1

File tree

12 files changed

+1094
-21
lines changed

12 files changed

+1094
-21
lines changed

backend/services/conversation_management_service.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,13 @@ def call_llm_for_title(content: str, tenant_id: str, language: str = LANGUAGE["Z
263263
key=MODEL_CONFIG_MAPPING["llm"], tenant_id=tenant_id)
264264

265265
# Create OpenAIServerModel instance
266-
llm = OpenAIServerModel(model_id=get_model_name_from_config(model_config) if model_config.get("model_name") else "", api_base=model_config.get("base_url", ""),
267-
api_key=model_config.get("api_key", ""), temperature=0.7, top_p=0.95)
266+
llm = OpenAIServerModel(
267+
model_id=get_model_name_from_config(model_config) if model_config.get("model_name") else "",
268+
api_base=model_config.get("base_url", ""),
269+
api_key=model_config.get("api_key", ""),
270+
temperature=0.7,
271+
top_p=0.95
272+
)
268273

269274
# Build messages
270275
user_prompt = Template(prompt_template["USER_PROMPT"], undefined=StrictUndefined).render({

backend/services/vectordatabase_service.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@
1515
import time
1616
import uuid
1717
from datetime import datetime, timezone
18-
from typing import Any, Dict, Generator, List, Optional
18+
from typing import Any, Dict, List, Optional
1919

2020
from fastapi import Body, Depends, Path, Query
2121
from fastapi.responses import StreamingResponse
2222
from nexent.core.models.embedding_model import OpenAICompatibleEmbedding, JinaEmbedding, BaseEmbedding
23-
from nexent.core.nlp.tokenizer import calculate_term_weights
2423
from nexent.vector_database.base import VectorDatabaseCore
2524
from nexent.vector_database.elasticsearch_core import ElasticSearchCore
2625

backend/services/voice_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import logging
3-
from typing import Dict, Any, Optional
3+
from typing import Any, Optional
44

55
from nexent.core.models.stt_model import STTConfig, STTModel
66
from nexent.core.models.tts_model import TTSConfig, TTSModel

frontend/server.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ const { createServer } = require('http');
22
const { parse } = require('url');
33
const next = require('next');
44
const { createProxyServer } = require('http-proxy');
5+
const path = require('path');
6+
7+
// Load environment variables from .env file in parent directory (project root)
8+
// In container environments, env vars are injected directly by Docker, so .env file may not exist
9+
// Using optional: true to avoid errors if .env file is not found
10+
require('dotenv').config({
11+
path: path.resolve(__dirname, '../.env'),
12+
override: false // Don't override existing environment variables (important for Docker)
13+
});
514

615
const dev = process.env.NODE_ENV !== 'production';
716
const app = next({

sdk/nexent/core/agents/core_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def _step_stream(self, memory_step: ActionStep) -> Generator[Any]:
129129
additional_args = {
130130
"grammar": self.grammar} if self.grammar is not None else {}
131131
chat_message: ChatMessage = self.model(input_messages,
132-
stop_sequences=["<END_CODE>", "Observation:", "Calling tools:", "<END_CODE"], **additional_args, )
132+
stop_sequences=["<END_CODE>", "Observation:", "Calling tools:", "<END_CODE"], **additional_args)
133133
memory_step.model_output_message = chat_message
134134
model_output = chat_message.content
135135
memory_step.model_output = model_output

test/backend/database/test_model_managment_db.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,247 @@ def test_get_model_by_model_id_fills_default_chunk_sizes(monkeypatch):
150150
assert out is not None
151151
assert out["expected_chunk_size"] == 1024
152152
assert out["maximum_chunk_size"] == 1536
153+
154+
155+
def test_create_model_record(monkeypatch):
156+
"""Test create_model_record function (covers lines 23-42)"""
157+
mock_result = MagicMock()
158+
mock_result.rowcount = 1
159+
160+
mock_stmt = MagicMock()
161+
mock_stmt.values.return_value = mock_stmt
162+
163+
mock_insert = MagicMock(return_value=mock_stmt)
164+
monkeypatch.setattr("backend.database.model_management_db.insert", mock_insert)
165+
166+
session = MagicMock()
167+
session.execute.return_value = mock_result
168+
169+
mock_ctx = MagicMock()
170+
mock_ctx.__enter__.return_value = session
171+
mock_ctx.__exit__.return_value = None
172+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
173+
174+
# Mock clean_string_values and add_creation_tracking
175+
monkeypatch.setattr("backend.database.model_management_db.db_client.clean_string_values", lambda x: x)
176+
monkeypatch.setattr("backend.database.model_management_db.add_creation_tracking", lambda x, uid: x)
177+
monkeypatch.setattr("backend.database.model_management_db.func.current_timestamp", MagicMock())
178+
179+
model_data = {"model_name": "test", "model_type": "llm"}
180+
result = model_mgmt_db.create_model_record(model_data, user_id="u1", tenant_id="t1")
181+
182+
assert result is True
183+
session.execute.assert_called_once()
184+
185+
186+
def test_update_model_record(monkeypatch):
187+
"""Test update_model_record function (covers lines 63-84)"""
188+
mock_result = MagicMock()
189+
mock_result.rowcount = 1
190+
191+
mock_stmt = MagicMock()
192+
mock_stmt.where.return_value = mock_stmt
193+
mock_stmt.values.return_value = mock_stmt
194+
195+
mock_update = MagicMock(return_value=mock_stmt)
196+
monkeypatch.setattr("backend.database.model_management_db.update", mock_update)
197+
198+
session = MagicMock()
199+
session.execute.return_value = mock_result
200+
201+
mock_ctx = MagicMock()
202+
mock_ctx.__enter__.return_value = session
203+
mock_ctx.__exit__.return_value = None
204+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
205+
206+
# Mock clean_string_values and add_update_tracking
207+
monkeypatch.setattr("backend.database.model_management_db.db_client.clean_string_values", lambda x: x)
208+
monkeypatch.setattr("backend.database.model_management_db.add_update_tracking", lambda x, uid: x)
209+
monkeypatch.setattr("backend.database.model_management_db.func.current_timestamp", MagicMock())
210+
211+
update_data = {"model_name": "updated"}
212+
result = model_mgmt_db.update_model_record(1, update_data, user_id="u1", tenant_id="t1")
213+
214+
assert result is True
215+
session.execute.assert_called_once()
216+
217+
218+
def test_delete_model_record(monkeypatch):
219+
"""Test delete_model_record function (covers lines 99-119)"""
220+
mock_result = MagicMock()
221+
mock_result.rowcount = 1
222+
223+
mock_stmt = MagicMock()
224+
mock_stmt.where.return_value = mock_stmt
225+
mock_stmt.values.return_value = mock_stmt
226+
227+
mock_update = MagicMock(return_value=mock_stmt)
228+
monkeypatch.setattr("backend.database.model_management_db.update", mock_update)
229+
230+
session = MagicMock()
231+
session.execute.return_value = mock_result
232+
233+
mock_ctx = MagicMock()
234+
mock_ctx.__enter__.return_value = session
235+
mock_ctx.__exit__.return_value = None
236+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
237+
238+
# Mock add_update_tracking
239+
monkeypatch.setattr("backend.database.model_management_db.add_update_tracking", lambda x, uid: x)
240+
monkeypatch.setattr("backend.database.model_management_db.func.current_timestamp", MagicMock())
241+
242+
result = model_mgmt_db.delete_model_record(1, user_id="u1", tenant_id="t1")
243+
244+
assert result is True
245+
session.execute.assert_called_once()
246+
247+
248+
def test_get_model_records_with_tenant_id(monkeypatch):
249+
"""Test get_model_records with tenant_id filter (covers lines 137->141)"""
250+
mock_model = SimpleNamespace(
251+
model_id=4,
252+
model_factory="openai",
253+
model_type="llm",
254+
tenant_id="tenant4",
255+
delete_flag="N",
256+
)
257+
mock_scalars = MagicMock()
258+
mock_scalars.all.return_value = [mock_model]
259+
session = MagicMock()
260+
session.scalars.return_value = mock_scalars
261+
262+
mock_ctx = MagicMock()
263+
mock_ctx.__enter__.return_value = session
264+
mock_ctx.__exit__.return_value = None
265+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
266+
monkeypatch.setattr("backend.database.model_management_db.as_dict", lambda obj: obj.__dict__)
267+
268+
records = model_mgmt_db.get_model_records({"model_type": "llm"}, tenant_id="tenant4")
269+
assert len(records) == 1
270+
assert records[0]["tenant_id"] == "tenant4"
271+
272+
273+
def test_get_model_records_with_none_filter(monkeypatch):
274+
"""Test get_model_records with None value in filter (covers line 145)"""
275+
mock_model = SimpleNamespace(
276+
model_id=5,
277+
model_factory="openai",
278+
model_type="llm",
279+
tenant_id="tenant5",
280+
delete_flag="N",
281+
display_name=None,
282+
)
283+
mock_scalars = MagicMock()
284+
mock_scalars.all.return_value = [mock_model]
285+
session = MagicMock()
286+
session.scalars.return_value = mock_scalars
287+
288+
mock_ctx = MagicMock()
289+
mock_ctx.__enter__.return_value = session
290+
mock_ctx.__exit__.return_value = None
291+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
292+
monkeypatch.setattr("backend.database.model_management_db.as_dict", lambda obj: obj.__dict__)
293+
294+
records = model_mgmt_db.get_model_records({"display_name": None}, tenant_id="tenant5")
295+
assert len(records) == 1
296+
297+
298+
def test_get_model_by_display_name(monkeypatch):
299+
"""Test get_model_by_display_name function (covers lines 178-185)"""
300+
mock_model = SimpleNamespace(
301+
model_id=6,
302+
model_factory="openai",
303+
model_name="gpt-4",
304+
display_name="GPT-4",
305+
tenant_id="tenant6",
306+
delete_flag="N",
307+
)
308+
mock_scalars = MagicMock()
309+
mock_scalars.all.return_value = [mock_model]
310+
session = MagicMock()
311+
session.scalars.return_value = mock_scalars
312+
313+
mock_ctx = MagicMock()
314+
mock_ctx.__enter__.return_value = session
315+
mock_ctx.__exit__.return_value = None
316+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
317+
monkeypatch.setattr("backend.database.model_management_db.as_dict", lambda obj: obj.__dict__)
318+
319+
result = model_mgmt_db.get_model_by_display_name("GPT-4", "tenant6")
320+
assert result is not None
321+
assert result["display_name"] == "GPT-4"
322+
323+
324+
def test_get_model_id_by_display_name(monkeypatch):
325+
"""Test get_model_id_by_display_name function (covers lines 199-200)"""
326+
mock_model = SimpleNamespace(
327+
model_id=7,
328+
model_factory="openai",
329+
model_name="gpt-4",
330+
display_name="GPT-4",
331+
tenant_id="tenant7",
332+
delete_flag="N",
333+
)
334+
mock_scalars = MagicMock()
335+
mock_scalars.all.return_value = [mock_model]
336+
session = MagicMock()
337+
session.scalars.return_value = mock_scalars
338+
339+
mock_ctx = MagicMock()
340+
mock_ctx.__enter__.return_value = session
341+
mock_ctx.__exit__.return_value = None
342+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
343+
monkeypatch.setattr("backend.database.model_management_db.as_dict", lambda obj: obj.__dict__)
344+
345+
result = model_mgmt_db.get_model_id_by_display_name("GPT-4", "tenant7")
346+
assert result == 7
347+
348+
349+
def test_get_model_by_model_id_with_tenant_id(monkeypatch):
350+
"""Test get_model_by_model_id with tenant_id filter (covers lines 222->226)"""
351+
mock_model = SimpleNamespace(
352+
model_id=8,
353+
model_factory="openai",
354+
model_type="llm",
355+
tenant_id="tenant8",
356+
delete_flag="N",
357+
)
358+
mock_scalars = MagicMock()
359+
mock_scalars.first.return_value = mock_model
360+
session = MagicMock()
361+
session.scalars.return_value = mock_scalars
362+
363+
mock_ctx = MagicMock()
364+
mock_ctx.__enter__.return_value = session
365+
mock_ctx.__exit__.return_value = None
366+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
367+
368+
result = model_mgmt_db.get_model_by_model_id(8, tenant_id="tenant8")
369+
assert result is not None
370+
assert result["model_id"] == 8
371+
372+
373+
def test_get_model_by_name_factory(monkeypatch):
374+
"""Test get_model_by_name_factory function (covers lines 269-274)"""
375+
mock_model = SimpleNamespace(
376+
model_id=9,
377+
model_factory="openai",
378+
model_name="gpt-4",
379+
tenant_id="tenant9",
380+
delete_flag="N",
381+
)
382+
mock_scalars = MagicMock()
383+
mock_scalars.all.return_value = [mock_model]
384+
session = MagicMock()
385+
session.scalars.return_value = mock_scalars
386+
387+
mock_ctx = MagicMock()
388+
mock_ctx.__enter__.return_value = session
389+
mock_ctx.__exit__.return_value = None
390+
monkeypatch.setattr("backend.database.model_management_db.get_db_session", lambda: mock_ctx)
391+
monkeypatch.setattr("backend.database.model_management_db.as_dict", lambda obj: obj.__dict__)
392+
393+
result = model_mgmt_db.get_model_by_name_factory("gpt-4", "openai", "tenant9")
394+
assert result is not None
395+
assert result["model_name"] == "gpt-4"
396+
assert result["model_factory"] == "openai"

test/backend/services/test_me_model_management_service.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,30 @@ async def test_check_me_connectivity_variables_not_set():
130130

131131
# Assert
132132
assert result is False
133+
134+
135+
@pytest.mark.asyncio
136+
async def test_check_me_connectivity_general_exception():
137+
"""Test ME connectivity check with general exception (covers lines 54-55)"""
138+
with patch.object(svc, 'MODEL_ENGINE_APIKEY', 'mock-api-key'), \
139+
patch.object(svc, 'MODEL_ENGINE_HOST', 'https://me-host.com'), \
140+
patch('backend.services.me_model_management_service.aiohttp.ClientSession') as mock_session_class:
141+
142+
# Create mock session that raises a general exception
143+
mock_session = AsyncMock()
144+
mock_get = AsyncMock()
145+
mock_get.__aenter__.side_effect = ValueError("Unexpected error")
146+
mock_session.get = MagicMock(return_value=mock_get)
147+
148+
# Create mock session factory
149+
mock_client_session = AsyncMock()
150+
mock_client_session.__aenter__.return_value = mock_session
151+
mock_session_class.return_value = mock_client_session
152+
153+
# Execute and expect a generic Exception
154+
with pytest.raises(Exception) as exc_info:
155+
await svc.check_me_connectivity(timeout=30)
156+
157+
# Assert the exception message contains "Unknown error occurred"
158+
assert "Unknown error occurred" in str(exc_info.value)
159+
assert "Unexpected error" in str(exc_info.value)

0 commit comments

Comments
 (0)