build: configure pytest-django and add dev dependencies#40
build: configure pytest-django and add dev dependencies#40SherryShijiarui wants to merge 32 commits intodevfrom
Conversation
A-lexisL
left a comment
There was a problem hiding this comment.
- tests should be comprehensive, so more tests are needed. Please arrange them into classes based on API and Authenticated status.
e.g.
class TestCourseListAuthenticated
class TestCourseListNotAuthenticated
class TestCourseDetailAuthenticated
...
| assert "results" in response.data | ||
|
|
||
| def test_filter_courses_by_department(self, base_client, course_factory): | ||
| """Verify filtering courses by department code.""" |
There was a problem hiding this comment.
For now, only filter courses by department code for nonauthenticated user exists. But tests for all the following params are needed for authenticated/nonauthenticated users.
- department (string): Filter by department code (case-insensitive)
- code (string): Filter by course code (partial match)
- min_quality (integer): Filter by minimum quality score (authenticated only)
- min_difficulty (integer): Filter by minimum difficulty score (authenticated only)
- sort_by (string): Sort field ("course_code", "review_count"),("quality_score", "difficulty_score")(authenticated only)
- sort_order (string): "asc" or "desc" (default: "asc")
- page (integer): Page number for pagination
(docstring of CoursesListAPI in apps/web/views.py)
Same for other apis
There was a problem hiding this comment.
Added coverage for all documented query params across auth states in apps/web/tests/test_course.py (filters, min_quality/min_difficulty, review_count/quality_score/difficulty_score sorts, sort_order). Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for parameter coverage:
+@pytest.mark.django_db
+class TestCourseAPIUnauthenticated:
+ def test_filter_courses_by_department(...):
+ def test_filter_courses_by_code(...):
+ def test_sort_courses_by_review_count_anonymous(...):
+ def test_sort_courses_by_score_anonymous(...):
+ def test_filter_courses_by_score_anonymous(...):
+ def test_filter_courses_by_difficulty_anonymous(...):
+
+@pytest.mark.django_db
+class TestCourseAPIAuthenticated:
+ def test_filter_courses_by_department_authenticated(...):
+ def test_filter_courses_by_code_authenticated(...):
+ def test_sort_by_review_count(...):
+ def test_sort_courses_by_score(...):
+ def test_sort_courses_by_difficulty_score(...):
+ def test_filter_courses_by_quality(...):
+ def test_filter_courses_by_difficulty(...):
+ def test_sort_order_asc_and_desc(...):These tests are now split by auth state and cover the documented query parameters.
apps/web/tests/test_review.py
Outdated
| url = reverse("user_review_api", kwargs={"review_id": review.id}) | ||
| response = auth_client.delete(url) | ||
| assert response.status_code == 204 | ||
| assert Review.objects.filter(id=review.id).count() == 0 |
There was a problem hiding this comment.
Missing tests for base_client
There was a problem hiding this comment.
Added unauthenticated coverage in TestReviewAPIUnauthenticated for list, personal list, delete, and detail in apps/web/tests/test_review.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for missing base_client tests:
+class TestReviewAPIUnauthenticated:
+ def test_get_course_reviews_anonymous(...):
+ def test_get_personal_reviews_anonymous(...):
+ def test_delete_review_anonymous_forbidden(...):
+ def test_review_detail_anonymous_forbidden(...):This adds unauthenticated coverage for the review endpoints.
|
|
||
| assert response.status_code == 200 | ||
| # Verify the title matches the fixture-created course | ||
| assert response.data["course_title"] == course.course_title |
There was a problem hiding this comment.
base_client and auth_client will receive different response data:
- auth_client: full details
- base_client:
"quality_score" ,"difficulty_score","difficulty_vote","quality_vote","quality_vote_count","difficulty_vote_count"fields are removed(the docstrings for CoursesDetailAPI is not complete)
There was a problem hiding this comment.
Added course detail field assertions for auth vs unauth in apps/web/tests/test_course.py (scores and votes hidden for base_client, present for auth_client). Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for auth vs unauth response fields:
+def test_course_detail_fields_unauthenticated(self, base_client, course):
+ hidden_fields = [
+ "quality_score", "difficulty_score",
+ "difficulty_vote", "quality_vote",
+ "quality_vote_count", "difficulty_vote_count",
+ ]
+ for field in hidden_fields:
+ assert field not in response.data
+
+def test_course_detail_fields_authenticated(self, auth_client, course):
+ required_fields = [
+ "quality_score", "difficulty_score",
+ "difficulty_vote", "quality_vote",
+ "quality_vote_count", "difficulty_vote_count",
+ ]
+ for field in required_fields:
+ assert field in response.dataThis directly validates the serializer behavior difference.
…fix the format of the course code later
d40df23 to
bc0334e
Compare
…sc and desc) pagnition)
A-lexisL
left a comment
There was a problem hiding this comment.
The tests are comprehensive. But some test cases still need polishing.
| term_data | ||
| and term_data.group("year") == "16" | ||
| and term_data.group("term") == "w" | ||
| and term_data.group("term") == "F" |
There was a problem hiding this comment.
Fixed the term regex test to check 16W, 16w, and 16F separately in apps/web/tests/lib_tests/test_terms.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for the term assertion fix:
term_data = terms.term_regex.match("16w")
self.assertTrue(
term_data
and term_data.group("year") == "16"
- and term_data.group("term") == "w"
- and term_data.group("term") == "F"
+ and term_data.group("term") == "w"
)
+term_data = terms.term_regex.match("16F")
+self.assertTrue(
+ term_data
+ and term_data.group("year") == "16"
+ and term_data.group("term") == "F"
+)This removes the contradictory condition and tests both cases explicitly.
apps/web/tests/conftest.py
Outdated
| @pytest.fixture | ||
| def department_mixed_courses(db): | ||
| """Returns a mixed set of courses for filtering/sorting tests.""" | ||
| # Note: Using 'title' to match your original course.py |
There was a problem hiding this comment.
since title is changed to course_title, this comment is outdated
There was a problem hiding this comment.
Updated the fixture comment to reference course_title in apps/web/tests/conftest.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for the outdated fixture comment:
-# Note: Using title to match your original course.py
+# Note: Using course_title to match current course model field
apps/web/tests/test_course.py
Outdated
| resp_p1 = auth_client.get(url, {"page": 1}) | ||
|
|
||
| assert resp_p1.status_code == 200 | ||
| assert len(resp_p1.data["results"]) == 10 |
There was a problem hiding this comment.
10 is hardcoded.
Its not very necessary to test pagination, as it has nothing to do with data correctness/authentication access
There was a problem hiding this comment.
Removed the pagination hardcode expectation; course list tests now focus on filter and sort behavior in apps/web/tests/test_course.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for removing pagination hardcode:
-def test_pagination_with_default_settings(self, auth_client, course_factory):
- for i in range(11):
- course_factory(course_code=f"CODE_{i:02d}")
- resp_p1 = auth_client.get(reverse("courses_api"), {"page": 1})
- assert len(resp_p1.data["results"]) == 10Course tests now focus on correctness of filtering and sorting behavior.
apps/web/tests/test_review.py
Outdated
| status.HTTP_403_FORBIDDEN, | ||
| ] | ||
|
|
||
| def test_department_api_empty(self, base_client, db): |
There was a problem hiding this comment.
This could be removed: department_api is already tested in test_course.py
There was a problem hiding this comment.
Removed department_api checks from apps/web/tests/test_review.py; department coverage is in apps/web/tests/test_course.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for removing department API assertions from review tests:
-def test_department_api_empty(self, base_client, db):
- ...
-
-def test_department_api_sorting(self, base_client, db):
- ...Department API coverage is kept in apps/web/tests/test_course.py.
| """5. Verify user can list their own reviews.""" | ||
| response = auth_client.get(personal_reviews_list_url) | ||
| assert response.status_code == status.HTTP_200_OK | ||
| assert any(r["id"] == review.id for r in response.data) |
There was a problem hiding this comment.
should check if the api returns and only returns personal reviews. e.g. review + other_review and the api only returns review
There was a problem hiding this comment.
Updated test_list_personal_reviews to include other_review and assert only the current user review is returned in apps/web/tests/test_review.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff below:
-def test_list_personal_reviews(self, auth_client, personal_reviews_list_url, review):
+def test_list_personal_reviews(self, auth_client, personal_reviews_list_url, review, other_review):
response = auth_client.get(personal_reviews_list_url)
assert response.status_code == status.HTTP_200_OK
assert any(r["id"] == review.id for r in response.data)
+ assert all(r["id"] != other_review.id for r in response.data)This now verifies the API returns only personal reviews.
apps/web/tests/test_review.py
Outdated
|
|
||
| ReviewFactory(course=course, professor="UniqueProf", comments="c" * min_len) | ||
| response = auth_client.get(course_reviews_url, {"q": "UniqueProf"}) | ||
| assert any(r["professor"] == "UniqueProf" for r in response.data) |
There was a problem hiding this comment.
should be all
Although search review could also match partial UniqueProf in professor/comment, in this case only reviews that ["professor"] == "UniqueProf" would be returned
There was a problem hiding this comment.
Search test now uses all(...) and adds a different professor so only UniqueProf results are returned. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for strict search assertion:
ReviewFactory(course=course, professor="UniqueProf", comments="c" * min_len)
+ReviewFactory(course=course, professor="OtherProf", comments="c" * min_len)
response = auth_client.get(course_reviews_url, {"q": "UniqueProf"})
-assert any(r["professor"] == "UniqueProf" for r in response.data)
+assert all(r["professor"] == "UniqueProf" for r in response.data)
apps/web/tests/test_review.py
Outdated
| self, auth_client, personal_review_detail_url, review, valid_review_data | ||
| ): | ||
| """9. Verify successful update of user's own review.""" | ||
| valid_review_data["comments"] = "Updated content that is long enough." |
There was a problem hiding this comment.
min length is according to config. So this comment may not be long enough. Use 'b' * min_len
There was a problem hiding this comment.
Updated comment length usage to min_len so validation aligns with config in apps/web/tests/test_review.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for config-safe update payload length:
-valid_review_data["comments"] = "Updated content that is long enough."
+valid_review_data["comments"] = "b" * len(valid_review_data["comments"])This keeps the updated value aligned with the configured minimum length.
apps/web/tests/test_review.py
Outdated
| ] | ||
| assert Review.objects.filter(id=review.id).exists() | ||
|
|
||
| def test_department_api_sorting(self, base_client, db): |
There was a problem hiding this comment.
Removed the redundant non-existent delete test in apps/web/tests/test_review.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for removing the redundant test:
-def test_delete_non_existent_id(self, auth_client):
- url = reverse("user_review_api", kwargs={"review_id": 99999})
- response = auth_client.delete(url)
- assert response.status_code == status.HTTP_404_NOT_FOUND
apps/web/tests/test_review.py
Outdated
| ): | ||
| """14. Verify PUT fails if required fields (professor) are missing.""" | ||
| response = auth_client.put( | ||
| personal_review_detail_url, {"comments": "Valid length..."}, format="json" |
There was a problem hiding this comment.
still min length problem.
Dont know whether 400 is caused by min length or missing fields
There was a problem hiding this comment.
Missing-field validation test now uses min_len comments so 400 is due to missing required fields, not length. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff to isolate missing-field validation from length validation:
-def test_update_validation_missing_field(self, auth_client, personal_review_detail_url):
+def test_update_validation_missing_field(self, auth_client, personal_review_detail_url, min_len):
response = auth_client.put(
personal_review_detail_url,
- {"comments": "Valid length..."},
+ {"comments": "b" * min_len},
format="json",
)\n```\nNow the 400 is specifically from missing required fields.| ): | ||
| """19. Verify vote statistics are included in the response.""" | ||
| response = auth_client.get(personal_review_detail_url) | ||
| assert "kudos_count" in response.data |
There was a problem hiding this comment.
Added dislike_count assertion in test_review_response_contains_votes in apps/web/tests/test_review.py. Evidence: .venv/bin/pytest apps/web/tests -> 52 passed.
There was a problem hiding this comment.
Added concrete diff for vote stats assertion:
response = auth_client.get(personal_review_detail_url)
assert "kudos_count" in response.data
+assert "dislike_count" in response.dataf0ba2fa to
5a7a0b1
Compare
No description provided.