Skip to content

Commit c3aedae

Browse files
Courses v2 org id tests (#3230)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 3497a5c commit c3aedae

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed

courses/views/v2/views_test.py

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ def test_filter_with_org_id_anonymous():
335335

336336

337337
@pytest.mark.django_db
338+
@pytest.mark.skip_nplusone_check
338339
def test_filter_with_org_id_returns_contracted_course(
339340
mocker, contract_ready_course, mock_course_run_clone
340341
):
@@ -363,6 +364,7 @@ def test_filter_with_org_id_returns_contracted_course(
363364

364365

365366
@pytest.mark.django_db
367+
@pytest.mark.skip_nplusone_check
366368
def test_filter_with_org_id_user_not_associated_with_org_returns_no_courses(
367369
contract_ready_course, mock_course_run_clone
368370
):
@@ -386,6 +388,304 @@ def test_filter_with_org_id_user_not_associated_with_org_returns_no_courses(
386388
assert unrelated_course.title not in titles
387389

388390

391+
@pytest.mark.django_db
392+
@pytest.mark.skip_nplusone_check
393+
def test_filter_with_org_id_multiple_courses_same_org(
394+
contract_ready_course, mock_course_run_clone
395+
):
396+
"""Test that filtering by org_id returns all contracted courses for that org"""
397+
org = OrganizationPageFactory(name="Test Org")
398+
contract = ContractPageFactory(organization=org, active=True)
399+
user = UserFactory()
400+
user.b2b_organizations.add(org)
401+
user.b2b_contracts.add(contract)
402+
user.refresh_from_db()
403+
404+
# Create multiple courses for the same org
405+
(course1, _) = contract_ready_course
406+
create_contract_run(contract, course1)
407+
408+
course2 = CourseFactory()
409+
CourseRunFactory(course=course2, is_source_run=True)
410+
create_contract_run(contract, course2)
411+
412+
course3 = CourseFactory()
413+
CourseRunFactory(course=course3, is_source_run=True)
414+
create_contract_run(contract, course3)
415+
416+
# Create unrelated course (no contract_run - should not appear in results)
417+
unrelated_course = CourseFactory()
418+
CourseRunFactory(course=unrelated_course)
419+
420+
client = APIClient()
421+
client.force_authenticate(user=user)
422+
423+
url = reverse("v2:courses_api-list")
424+
response = client.get(url, {"org_id": org.id})
425+
426+
course_ids = [result["id"] for result in response.data["results"]]
427+
assert course1.id in course_ids
428+
assert course2.id in course_ids
429+
assert course3.id in course_ids
430+
assert unrelated_course.id not in course_ids
431+
assert len(course_ids) == 3
432+
433+
434+
@pytest.mark.django_db
435+
@pytest.mark.skip_nplusone_check
436+
def test_filter_with_org_id_inactive_contract_excluded(
437+
contract_ready_course, mock_course_run_clone
438+
):
439+
"""Test that courses from inactive contracts are not returned"""
440+
org = OrganizationPageFactory(name="Test Org")
441+
contract = ContractPageFactory(organization=org, active=False)
442+
user = UserFactory()
443+
user.b2b_organizations.add(org)
444+
user.b2b_contracts.add(contract)
445+
user.refresh_from_db()
446+
447+
(course, _) = contract_ready_course
448+
create_contract_run(contract, course)
449+
450+
client = APIClient()
451+
client.force_authenticate(user=user)
452+
453+
url = reverse("v2:courses_api-list")
454+
response = client.get(url, {"org_id": org.id})
455+
456+
assert response.data["results"] == []
457+
458+
459+
@pytest.mark.django_db
460+
@pytest.mark.skip_nplusone_check
461+
def test_filter_with_org_id_multiple_orgs(contract_ready_course, mock_course_run_clone):
462+
"""Test that filtering by org_id returns courses only for that specific org"""
463+
org1 = OrganizationPageFactory(name="Test Org 1")
464+
org2 = OrganizationPageFactory(name="Test Org 2")
465+
contract1 = ContractPageFactory(organization=org1, active=True)
466+
contract2 = ContractPageFactory(organization=org2, active=True)
467+
468+
user = UserFactory()
469+
user.b2b_organizations.add(org1, org2)
470+
user.b2b_contracts.add(contract1, contract2)
471+
user.refresh_from_db()
472+
473+
(course1, _) = contract_ready_course
474+
create_contract_run(contract1, course1)
475+
476+
course2 = CourseFactory()
477+
CourseRunFactory(course=course2, is_source_run=True)
478+
create_contract_run(contract2, course2)
479+
480+
client = APIClient()
481+
client.force_authenticate(user=user)
482+
483+
url = reverse("v2:courses_api-list")
484+
485+
# Filter for org1
486+
response = client.get(url, {"org_id": org1.id})
487+
course_ids = [result["id"] for result in response.data["results"]]
488+
assert course1.id in course_ids
489+
assert course2.id not in course_ids
490+
491+
# Filter for org2
492+
response = client.get(url, {"org_id": org2.id})
493+
course_ids = [result["id"] for result in response.data["results"]]
494+
assert course2.id in course_ids
495+
assert course1.id not in course_ids
496+
497+
498+
@pytest.mark.django_db
499+
@pytest.mark.skip_nplusone_check
500+
def test_filter_with_org_id_user_in_org_but_no_contract(
501+
contract_ready_course, mock_course_run_clone
502+
):
503+
"""Test that user in org can see org's contracted courses"""
504+
org = OrganizationPageFactory(name="Test Org")
505+
contract = ContractPageFactory(organization=org, active=True)
506+
user = UserFactory()
507+
user.b2b_organizations.add(org)
508+
# User is in org but NOT added to contract
509+
user.refresh_from_db()
510+
511+
(course, _) = contract_ready_course
512+
create_contract_run(contract, course)
513+
514+
client = APIClient()
515+
client.force_authenticate(user=user)
516+
517+
url = reverse("v2:courses_api-list")
518+
response = client.get(url, {"org_id": org.id})
519+
520+
titles = [result["title"] for result in response.data["results"]]
521+
assert course.title in titles
522+
523+
524+
@pytest.mark.django_db
525+
def test_filter_with_org_id_nonexistent_org_id(user_drf_client):
526+
"""Test that filtering with a nonexistent org_id returns no results"""
527+
course = CourseFactory(title="Test Course")
528+
CourseRunFactory(course=course)
529+
530+
url = reverse("v2:courses_api-list")
531+
response = user_drf_client.get(url, {"org_id": 99999})
532+
533+
assert response.data["results"] == []
534+
535+
536+
@pytest.mark.django_db
537+
@pytest.mark.skip_nplusone_check
538+
def test_filter_with_org_id_returns_detail_view(
539+
contract_ready_course, mock_course_run_clone
540+
):
541+
"""Test that org_id filter works on detail view endpoint"""
542+
org = OrganizationPageFactory(name="Test Org")
543+
contract = ContractPageFactory(organization=org, active=True)
544+
user = UserFactory()
545+
user.b2b_organizations.add(org)
546+
user.b2b_contracts.add(contract)
547+
user.refresh_from_db()
548+
549+
(course, _) = contract_ready_course
550+
create_contract_run(contract, course)
551+
552+
client = APIClient()
553+
client.force_authenticate(user=user)
554+
555+
url = reverse("v2:courses_api-detail", kwargs={"pk": course.id})
556+
response = client.get(url, {"org_id": org.id})
557+
558+
assert response.status_code == status.HTTP_200_OK
559+
assert response.data["id"] == course.id
560+
assert response.data["title"] == course.title
561+
562+
563+
@pytest.mark.django_db
564+
@pytest.mark.skip_nplusone_check
565+
def test_filter_with_org_id_detail_view_unauthorized_user(
566+
contract_ready_course, mock_course_run_clone
567+
):
568+
"""Test that org_id filter prevents unauthorized users from viewing contracted courses"""
569+
org = OrganizationPageFactory(name="Test Org")
570+
contract = ContractPageFactory(organization=org, active=True)
571+
user = UserFactory()
572+
# User NOT added to org
573+
user.refresh_from_db()
574+
575+
(course, _) = contract_ready_course
576+
create_contract_run(contract, course)
577+
578+
client = APIClient()
579+
client.force_authenticate(user=user)
580+
581+
url = reverse("v2:courses_api-detail", kwargs={"pk": course.id})
582+
response = client.get(url, {"org_id": org.id})
583+
584+
# User doesn't have access to this org, so should get 404
585+
assert response.status_code == status.HTTP_404_NOT_FOUND
586+
587+
588+
@pytest.mark.django_db
589+
@pytest.mark.skip_nplusone_check
590+
def test_filter_with_org_id_respects_course_live_status(
591+
contract_ready_course, mock_course_run_clone
592+
):
593+
"""Test that org_id filter returns contracted courses regardless of live status"""
594+
org = OrganizationPageFactory(name="Test Org")
595+
contract = ContractPageFactory(organization=org, active=True)
596+
user = UserFactory()
597+
user.b2b_organizations.add(org)
598+
user.b2b_contracts.add(contract)
599+
user.refresh_from_db()
600+
601+
(course, _) = contract_ready_course
602+
create_contract_run(contract, course)
603+
604+
# Make course page not live - org_id filter doesn't filter by live status
605+
course.page.live = False
606+
course.page.save()
607+
608+
client = APIClient()
609+
client.force_authenticate(user=user)
610+
611+
url = reverse("v2:courses_api-list")
612+
response = client.get(url, {"org_id": org.id})
613+
614+
# Course should appear even though it's not live
615+
course_ids = [result["id"] for result in response.data["results"]]
616+
assert course.id in course_ids
617+
618+
619+
@pytest.mark.django_db
620+
@pytest.mark.skip_nplusone_check
621+
def test_filter_with_org_id_pagination(contract_ready_course, mock_course_run_clone):
622+
"""Test that org_id filter works correctly with pagination"""
623+
org = OrganizationPageFactory(name="Test Org")
624+
contract = ContractPageFactory(organization=org, active=True)
625+
user = UserFactory()
626+
user.b2b_organizations.add(org)
627+
user.b2b_contracts.add(contract)
628+
user.refresh_from_db()
629+
630+
(course1, _) = contract_ready_course
631+
create_contract_run(contract, course1)
632+
633+
# Create more courses to test pagination
634+
for i in range(15):
635+
course = CourseFactory(title=f"Org Course {i}")
636+
CourseRunFactory(course=course, is_source_run=True)
637+
create_contract_run(contract, course)
638+
639+
client = APIClient()
640+
client.force_authenticate(user=user)
641+
642+
url = reverse("v2:courses_api-list")
643+
response = client.get(url, {"org_id": org.id, "page_size": 5})
644+
645+
assert response.status_code == status.HTTP_200_OK
646+
assert response.data["count"] >= 15
647+
assert len(response.data["results"]) == 5
648+
assert "next" in response.data
649+
assert response.data["next"] is not None
650+
651+
652+
@pytest.mark.django_db
653+
@pytest.mark.skip_nplusone_check
654+
def test_filter_with_org_id_combined_with_other_filters(
655+
contract_ready_course, mock_course_run_clone
656+
):
657+
"""Test that org_id filter can be combined with other filters"""
658+
org = OrganizationPageFactory(name="Test Org")
659+
contract = ContractPageFactory(organization=org, active=True)
660+
user = UserFactory()
661+
user.b2b_organizations.add(org)
662+
user.b2b_contracts.add(contract)
663+
user.refresh_from_db()
664+
665+
(course1, _) = contract_ready_course
666+
create_contract_run(contract, course1)
667+
668+
course2 = CourseFactory()
669+
CourseRunFactory(course=course2, is_source_run=True)
670+
create_contract_run(contract, course2)
671+
672+
unrelated_course = CourseFactory()
673+
CourseRunFactory(course=unrelated_course)
674+
675+
client = APIClient()
676+
client.force_authenticate(user=user)
677+
678+
url = reverse("v2:courses_api-list")
679+
# Filter by org_id and specific course readable_id
680+
response = client.get(url, {"org_id": org.id, "readable_id": course1.readable_id})
681+
682+
assert response.status_code == status.HTTP_200_OK
683+
course_ids = [result["id"] for result in response.data["results"]]
684+
assert course1.id in course_ids
685+
assert course2.id not in course_ids
686+
assert unrelated_course.id not in course_ids
687+
688+
389689
@pytest.mark.django_db
390690
@pytest.mark.skip_nplusone_check
391691
def test_filter_without_org_id_authenticated_user(user_drf_client):

0 commit comments

Comments
 (0)