Skip to content

Commit d34759b

Browse files
Merge branch 'dev' into exp-changes
2 parents 5bc1a80 + 58b83cb commit d34759b

21 files changed

+3458
-45
lines changed

.github/workflows/test.yml

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- main
77
- dev
88
- demo
9+
- psl-backend-unittest
910
pull_request:
1011
types:
1112
- opened
@@ -18,49 +19,48 @@ on:
1819
- demo
1920

2021
jobs:
21-
# frontend_tests:
22-
# runs-on: ubuntu-latest
23-
24-
# steps:
25-
# - name: Checkout code
26-
# uses: actions/checkout@v3
27-
28-
# - name: Set up Node.js
29-
# uses: actions/setup-node@v3
30-
# with:
31-
# node-version: '20'
32-
33-
# - name: Check if Frontend Test Files Exist
34-
# id: check_frontend_tests
35-
# run: |
36-
# if [ -z "$(find App/frontend/src -type f -name '*.test.js' -o -name '*.test.ts' -o -name '*.test.tsx')" ]; then
37-
# echo "No frontend test files found, skipping frontend tests."
38-
# echo "skip_frontend_tests=true" >> $GITHUB_ENV
39-
# else
40-
# echo "Frontend test files found, running tests."
41-
# echo "skip_frontend_tests=false" >> $GITHUB_ENV
42-
# fi
43-
44-
# - name: Install Frontend Dependencies
45-
# if: env.skip_frontend_tests == 'false'
46-
# run: |
47-
# cd App/frontend
48-
# npm install
49-
50-
# - name: Run Frontend Tests with Coverage
51-
# if: env.skip_frontend_tests == 'false'
52-
# run: |
53-
# cd App/frontend
54-
# npm run test -- --coverage
55-
56-
# - name: Skip Frontend Tests
57-
# if: env.skip_frontend_tests == 'true'
58-
# run: |
59-
# echo "Skipping frontend tests because no test files were found."
22+
# frontend_tests:
23+
# runs-on: ubuntu-latest
24+
25+
# steps:
26+
# - name: Checkout code
27+
# uses: actions/checkout@v3
28+
29+
# - name: Set up Node.js
30+
# uses: actions/setup-node@v3
31+
# with:
32+
# node-version: '20'
33+
34+
# - name: Check if Frontend Test Files Exist
35+
# id: check_frontend_tests
36+
# run: |
37+
# if [ -z "$(find App/frontend/src -type f -name '*.test.js' -o -name '*.test.ts' -o -name '*.test.tsx')" ]; then
38+
# echo "No frontend test files found, skipping frontend tests."
39+
# echo "skip_frontend_tests=true" >> $GITHUB_ENV
40+
# else
41+
# echo "Frontend test files found, running tests."
42+
# echo "skip_frontend_tests=false" >> $GITHUB_ENV
43+
# fi
44+
45+
# - name: Install Frontend Dependencies
46+
# if: env.skip_frontend_tests == 'false'
47+
# run: |
48+
# cd App/frontend
49+
# npm install
50+
51+
# - name: Run Frontend Tests with Coverage
52+
# if: env.skip_frontend_tests == 'false'
53+
# run: |
54+
# cd App/frontend
55+
# npm run test -- --coverage
56+
57+
# - name: Skip Frontend Tests
58+
# if: env.skip_frontend_tests == 'true'
59+
# run: |
60+
# echo "Skipping frontend tests because no test files were found."
6061

6162
backend_tests:
6263
runs-on: ubuntu-latest
63-
6464

6565
steps:
6666
- name: Checkout code
@@ -69,7 +69,7 @@ jobs:
6969
- name: Set up Python
7070
uses: actions/setup-python@v4
7171
with:
72-
python-version: '3.11'
72+
python-version: "3.11"
7373

7474
- name: Install Backend Dependencies
7575
run: |
@@ -81,7 +81,7 @@ jobs:
8181
- name: Check if Backend Test Files Exist
8282
id: check_backend_tests
8383
run: |
84-
if [ -z "$(find src/api -type f -name 'test_*.py')" ]; then
84+
if [ -z "$(find src/tests/api -type f -name 'test_*.py')" ]; then
8585
echo "No backend test files found, skipping backend tests."
8686
echo "skip_backend_tests=true" >> $GITHUB_ENV
8787
else
@@ -94,8 +94,6 @@ jobs:
9494
run: |
9595
pytest --cov=. --cov-report=term-missing --cov-report=xml
9696
97-
98-
9997
- name: Skip Backend Tests
10098
if: env.skip_backend_tests == 'true'
10199
run: |

pytest.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[pytest]
2+
markers =
3+
unittest: Unit Tests (relatively fast)
4+
functional: Functional Tests (tests that require a running server, with stubbed downstreams)
5+
azure: marks tests as extended (run less frequently, relatively slow)
6+
pythonpath = ./src/api
7+
log_level=debug

src/api/requirements.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,9 @@ opentelemetry-sdk==1.31.1
3131
opentelemetry-api==1.31.1
3232
opentelemetry-semantic-conventions==0.52b1
3333
opentelemetry-instrumentation==0.52b1
34-
azure-monitor-opentelemetry==1.6.8
34+
azure-monitor-opentelemetry==1.6.8
35+
36+
# Development tools
37+
pytest==8.3.5
38+
pytest-cov==6.1.1
39+
pytest-asyncio==0.26.0

src/api/services/history_service.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ async def update_conversation(self, user_id: str, request_json: dict):
206206
input_message=messages[-1],
207207
)
208208
else:
209+
await cosmos_conversation_client.cosmosdb_client.close()
209210
raise HTTPException(
210211
status_code=status.HTTP_400_BAD_REQUEST,
211212
detail="No assistant message found")

src/tests/__init__.py

Whitespace-only changes.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import pytest
2+
import asyncio
3+
from unittest.mock import AsyncMock, patch, MagicMock
4+
5+
from agents.agent_factory import AgentFactory
6+
7+
8+
@pytest.fixture(autouse=True)
9+
def reset_agent_factory():
10+
AgentFactory._instance = None
11+
yield
12+
AgentFactory._instance = None
13+
14+
15+
@pytest.fixture
16+
def config_mock():
17+
return MagicMock(
18+
azure_ai_project_conn_string="fake_conn_str",
19+
azure_openai_deployment_model="gpt-4"
20+
)
21+
22+
23+
@pytest.mark.asyncio
24+
@patch("agents.agent_factory.ChatWithDataPlugin", autospec=True)
25+
@patch("agents.agent_factory.AzureAIAgent", autospec=True)
26+
@patch("agents.agent_factory.DefaultAzureCredential", autospec=True)
27+
async def test_get_instance_creates_new_agent(
28+
mock_credential,
29+
mock_azure_agent,
30+
mock_plugin,
31+
config_mock
32+
):
33+
mock_client = AsyncMock()
34+
mock_agent_definition = MagicMock()
35+
mock_client.agents.create_agent.return_value = mock_agent_definition
36+
mock_azure_agent.create_client.return_value = mock_client
37+
38+
agent_instance = MagicMock()
39+
mock_azure_agent.return_value = agent_instance
40+
41+
result = await AgentFactory.get_instance(config_mock)
42+
43+
assert result == agent_instance
44+
mock_azure_agent.create_client.assert_called_once_with(
45+
credential=mock_credential.return_value,
46+
conn_str="fake_conn_str"
47+
)
48+
mock_client.agents.create_agent.assert_awaited_once()
49+
mock_azure_agent.assert_called_once()
50+
51+
52+
@pytest.mark.asyncio
53+
async def test_get_instance_returns_existing_instance(config_mock):
54+
AgentFactory._instance = MagicMock()
55+
result = await AgentFactory.get_instance(config_mock)
56+
assert result == AgentFactory._instance
57+
58+
59+
@pytest.mark.asyncio
60+
@patch("agents.agent_factory.AzureAIAgentThread", autospec=True)
61+
@patch("agents.agent_factory.AzureAIAgent", autospec=True)
62+
@patch("agents.agent_factory.ChatService", autospec=True)
63+
async def test_delete_instance_deletes_agent_and_threads(
64+
mock_chat_service,
65+
mock_azure_agent,
66+
mock_agent_thread
67+
):
68+
# Setup instance
69+
mock_client = AsyncMock()
70+
mock_agent = MagicMock()
71+
mock_agent.id = "test-id"
72+
mock_agent.client = mock_client
73+
AgentFactory._instance = mock_agent
74+
75+
# Setup thread cache
76+
mock_chat_service.thread_cache = {
77+
"conv1": "thread1",
78+
"conv2": "thread2"
79+
}
80+
81+
mock_thread_instance = AsyncMock()
82+
mock_agent_thread.side_effect = lambda client, thread_id: mock_thread_instance
83+
84+
await AgentFactory.delete_instance()
85+
86+
mock_agent_thread.assert_any_call(client=mock_client, thread_id="thread1")
87+
mock_agent_thread.assert_any_call(client=mock_client, thread_id="thread2")
88+
assert mock_thread_instance.delete.await_count == 2
89+
mock_client.agents.delete_agent.assert_awaited_once_with("test-id")
90+
assert AgentFactory._instance is None
91+
92+
93+
@pytest.mark.asyncio
94+
@patch("agents.agent_factory.AzureAIAgent", autospec=True)
95+
@patch("agents.agent_factory.ChatService", autospec=True)
96+
async def test_delete_instance_handles_missing_thread_cache(
97+
mock_chat_service,
98+
mock_azure_agent
99+
):
100+
mock_client = AsyncMock()
101+
mock_agent = MagicMock()
102+
mock_agent.id = "some-id"
103+
mock_agent.client = mock_client
104+
AgentFactory._instance = mock_agent
105+
106+
del mock_chat_service.thread_cache # simulate attribute not existing
107+
108+
await AgentFactory.delete_instance()
109+
110+
mock_client.agents.delete_agent.assert_awaited_once_with("some-id")
111+
assert AgentFactory._instance is None
112+
113+
114+
@pytest.mark.asyncio
115+
@patch("agents.agent_factory.AzureAIAgent", autospec=True)
116+
async def test_delete_instance_does_nothing_if_none(mock_azure_agent):
117+
AgentFactory._instance = None
118+
await AgentFactory.delete_instance()
119+

0 commit comments

Comments
 (0)