Skip to content

Commit 0d53ea4

Browse files
committed
feat: add tests
1 parent 35e86be commit 0d53ea4

File tree

1 file changed

+381
-0
lines changed

1 file changed

+381
-0
lines changed

backend/tests/test_api.py

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
#!/usr/bin/env python
2+
"""
3+
Tests for the `openedx-ai-extensions` API endpoints.
4+
"""
5+
6+
import json
7+
from unittest.mock import MagicMock, patch
8+
9+
import pytest
10+
from django.contrib.auth import get_user_model
11+
from django.test import Client
12+
from django.urls import reverse
13+
from opaque_keys.edx.keys import CourseKey
14+
15+
User = get_user_model()
16+
17+
18+
@pytest.fixture
19+
def user():
20+
"""
21+
Create and return a test user.
22+
"""
23+
return User.objects.create_user(
24+
username="testuser", email="testuser@example.com", password="password123"
25+
)
26+
27+
28+
@pytest.fixture
29+
def staff_user():
30+
"""
31+
Create and return a test staff user.
32+
"""
33+
return User.objects.create_user(
34+
username="staffuser",
35+
email="staffuser@example.com",
36+
password="password123",
37+
is_staff=True,
38+
)
39+
40+
41+
@pytest.fixture
42+
def course_key():
43+
"""
44+
Create and return a test course key.
45+
"""
46+
return CourseKey.from_string("course-v1:edX+DemoX+Demo_Course")
47+
48+
49+
@pytest.fixture
50+
def client():
51+
"""
52+
Create and return a Django test client.
53+
"""
54+
return Client()
55+
56+
57+
@pytest.mark.django_db
58+
def test_api_urls_are_registered():
59+
"""
60+
Test that the API URLs are properly registered and accessible.
61+
"""
62+
# Test that the v1 workflows URL can be reversed
63+
url = reverse("openedx_ai_extensions:api:v1:ai_workflows")
64+
assert url == "/openedx-ai-extensions/v1/workflows/"
65+
66+
67+
# @pytest.mark.django_db
68+
# def test_api_namespace_exists():
69+
# """
70+
# Test that the API namespace is properly configured.
71+
# """
72+
# # This will raise NoReverseMatch if the namespace doesn't exist
73+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
74+
# assert url is not None
75+
76+
77+
# @pytest.mark.django_db
78+
# def test_unauthenticated_request_redirects(client):
79+
# """
80+
# Test that unauthenticated requests are redirected to login.
81+
# """
82+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
83+
# response = client.get(url)
84+
85+
# # Django login_required decorator redirects to login page
86+
# assert response.status_code == 302
87+
# assert "/accounts/login/" in response.url
88+
89+
90+
# @pytest.mark.django_db
91+
# def test_authenticated_get_request(client, user):
92+
# """
93+
# Test GET request with authenticated user.
94+
# """
95+
# client.force_login(user)
96+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
97+
98+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
99+
# # Mock the workflow creation and execution
100+
# mock_workflow = MagicMock()
101+
# mock_workflow.execute.return_value = {
102+
# "status": "success",
103+
# "result": "Test result",
104+
# }
105+
# mock_find.return_value = (mock_workflow, True)
106+
107+
# response = client.get(url)
108+
109+
# assert response.status_code == 200
110+
# data = json.loads(response.content)
111+
# assert data["status"] == "success"
112+
# assert "timestamp" in data
113+
# assert "workflow_created" in data
114+
115+
116+
# @pytest.mark.django_db
117+
# def test_authenticated_post_request_with_valid_data(client, user, course_key):
118+
# """
119+
# Test POST request with valid workflow data.
120+
# """
121+
# client.force_login(user)
122+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
123+
124+
# payload = {
125+
# "action": "summarize",
126+
# "courseId": str(course_key),
127+
# "context": {
128+
# "unit_id": "unit-123",
129+
# },
130+
# "user_input": {
131+
# "query": "Summarize this content",
132+
# },
133+
# "requestId": "test-request-123",
134+
# }
135+
136+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
137+
# mock_workflow = MagicMock()
138+
# mock_workflow.execute.return_value = {
139+
# "status": "success",
140+
# "result": "Summary of the content",
141+
# }
142+
# mock_find.return_value = (mock_workflow, False)
143+
144+
# response = client.post(
145+
# url,
146+
# data=json.dumps(payload),
147+
# content_type="application/json",
148+
# )
149+
150+
# assert response.status_code == 200
151+
# data = json.loads(response.content)
152+
# assert data["status"] == "success"
153+
# assert data["requestId"] == "test-request-123"
154+
# assert data["workflow_created"] is False
155+
# assert "timestamp" in data
156+
157+
# # Verify the workflow was called correctly
158+
# mock_find.assert_called_once()
159+
# call_kwargs = mock_find.call_args[1]
160+
# assert call_kwargs["action"] == "summarize"
161+
# assert call_kwargs["course_id"] == str(course_key)
162+
# assert call_kwargs["user"] == user
163+
164+
165+
# @pytest.mark.django_db
166+
# def test_post_request_with_invalid_json(client, user):
167+
# """
168+
# Test POST request with invalid JSON body.
169+
# """
170+
# client.force_login(user)
171+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
172+
173+
# response = client.post(
174+
# url,
175+
# data="invalid json{",
176+
# content_type="application/json",
177+
# )
178+
179+
# assert response.status_code == 400
180+
# data = json.loads(response.content)
181+
# assert data["status"] == "error"
182+
# assert "Invalid JSON" in data["error"]
183+
184+
185+
# @pytest.mark.django_db
186+
# def test_post_request_with_validation_error(client, user):
187+
# """
188+
# Test POST request that triggers a ValidationError.
189+
# """
190+
# client.force_login(user)
191+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
192+
193+
# payload = {
194+
# "action": "invalid_action",
195+
# "courseId": "invalid-course-id",
196+
# }
197+
198+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
199+
# from django.core.exceptions import ValidationError
200+
# mock_find.side_effect = ValidationError("Invalid workflow configuration")
201+
202+
# response = client.post(
203+
# url,
204+
# data=json.dumps(payload),
205+
# content_type="application/json",
206+
# )
207+
208+
# assert response.status_code == 400
209+
# data = json.loads(response.content)
210+
# assert data["status"] == "validation_error"
211+
# assert "Invalid workflow configuration" in data["error"]
212+
213+
214+
# @pytest.mark.django_db
215+
# def test_post_request_with_workflow_error(client, user):
216+
# """
217+
# Test POST request where workflow execution fails.
218+
# """
219+
# client.force_login(user)
220+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
221+
222+
# payload = {
223+
# "action": "summarize",
224+
# "courseId": "course-v1:edX+Test+2024",
225+
# }
226+
227+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
228+
# mock_workflow = MagicMock()
229+
# mock_workflow.execute.return_value = {
230+
# "status": "error",
231+
# "error": "LLM processing failed",
232+
# }
233+
# mock_find.return_value = (mock_workflow, True)
234+
235+
# response = client.post(
236+
# url,
237+
# data=json.dumps(payload),
238+
# content_type="application/json",
239+
# )
240+
241+
# assert response.status_code == 500
242+
# data = json.loads(response.content)
243+
# assert data["status"] == "error"
244+
245+
246+
# @pytest.mark.django_db
247+
# def test_post_request_with_bad_request_status(client, user):
248+
# """
249+
# Test POST request where workflow returns a bad_request status.
250+
# """
251+
# client.force_login(user)
252+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
253+
254+
# payload = {
255+
# "action": "summarize",
256+
# }
257+
258+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
259+
# mock_workflow = MagicMock()
260+
# mock_workflow.execute.return_value = {
261+
# "status": "bad_request",
262+
# "error": "Missing required context",
263+
# }
264+
# mock_find.return_value = (mock_workflow, True)
265+
266+
# response = client.post(
267+
# url,
268+
# data=json.dumps(payload),
269+
# content_type="application/json",
270+
# )
271+
272+
# assert response.status_code == 400
273+
# data = json.loads(response.content)
274+
# assert data["status"] == "bad_request"
275+
276+
277+
# @pytest.mark.django_db
278+
# def test_post_request_with_exception(client, user):
279+
# """
280+
# Test POST request where an unexpected exception occurs.
281+
# """
282+
# client.force_login(user)
283+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
284+
285+
# payload = {
286+
# "action": "summarize",
287+
# }
288+
289+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
290+
# mock_find.side_effect = Exception("Unexpected error occurred")
291+
292+
# response = client.post(
293+
# url,
294+
# data=json.dumps(payload),
295+
# content_type="application/json",
296+
# )
297+
298+
# assert response.status_code == 500
299+
# data = json.loads(response.content)
300+
# assert data["status"] == "error"
301+
# assert "Unexpected error occurred" in data["error"]
302+
303+
304+
# @pytest.mark.django_db
305+
# def test_post_request_without_request_id(client, user):
306+
# """
307+
# Test POST request without requestId in payload.
308+
# """
309+
# client.force_login(user)
310+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
311+
312+
# payload = {
313+
# "action": "quiz_generate",
314+
# "courseId": "course-v1:edX+Test+2024",
315+
# }
316+
317+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
318+
# mock_workflow = MagicMock()
319+
# mock_workflow.execute.return_value = {
320+
# "status": "success",
321+
# }
322+
# mock_find.return_value = (mock_workflow, True)
323+
324+
# response = client.post(
325+
# url,
326+
# data=json.dumps(payload),
327+
# content_type="application/json",
328+
# )
329+
330+
# assert response.status_code == 200
331+
# data = json.loads(response.content)
332+
# # Should default to "no-request-id"
333+
# assert data["requestId"] == "no-request-id"
334+
335+
336+
# @pytest.mark.django_db
337+
# def test_empty_post_request(client, user):
338+
# """
339+
# Test POST request with empty body.
340+
# """
341+
# client.force_login(user)
342+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
343+
344+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
345+
# mock_workflow = MagicMock()
346+
# mock_workflow.execute.return_value = {
347+
# "status": "success",
348+
# }
349+
# mock_find.return_value = (mock_workflow, True)
350+
351+
# response = client.post(url)
352+
353+
# # Should handle empty body gracefully
354+
# assert response.status_code == 200
355+
# mock_find.assert_called_once()
356+
# call_kwargs = mock_find.call_args[1]
357+
# assert call_kwargs["action"] is None
358+
# assert call_kwargs["course_id"] is None
359+
# assert call_kwargs["context"] == {}
360+
361+
362+
# @pytest.mark.django_db
363+
# def test_staff_user_can_access_workflow(client, staff_user):
364+
# """
365+
# Test that staff users can access the workflow endpoint.
366+
# """
367+
# client.force_login(staff_user)
368+
# url = reverse("openedx_ai_extensions:api:v1:ai_pipelines")
369+
370+
# with patch("openedx_ai_extensions.workflows.models.AIWorkflow.find_workflow_for_context") as mock_find:
371+
# mock_workflow = MagicMock()
372+
# mock_workflow.execute.return_value = {
373+
# "status": "success",
374+
# }
375+
# mock_find.return_value = (mock_workflow, True)
376+
377+
# response = client.get(url)
378+
379+
# assert response.status_code == 200
380+
# data = json.loads(response.content)
381+
# assert data["status"] == "success"

0 commit comments

Comments
 (0)