コード例 #1
0
def test_get_course_availability(mitx_valid_data):
    """ Test that availability is calculated as expected """
    ocw_course = CourseFactory.create(platform=PlatformType.ocw.value)
    # test mitx course with raw_json
    assert get_course_availability(ocw_course) == AvailabilityType.current.value
    mitx_course_with_json = CourseFactory.create(
        course_id=mitx_valid_data["course_runs"][0]["key"],
        raw_json=mitx_valid_data,
        platform=PlatformType.mitx.value,
    )
    # test mitx course without raw_json
    assert (
        get_course_availability(mitx_course_with_json)
        == mitx_valid_data["course_runs"][0]["availability"]
    )
    mitx_course_no_json = CourseFactory.create(
        raw_json=None, platform=PlatformType.mitx.value
    )
    assert get_course_availability(mitx_course_no_json) is None
    # test mitx course without course_runs
    mitx_valid_data["course_runs"] = None  # pop course_runs json
    mitx_course_no_runs_json = CourseFactory.create(
        raw_json=mitx_valid_data, platform=PlatformType.mitx.value
    )
    assert get_course_availability(mitx_course_no_runs_json) is None
コード例 #2
0
def test_list_course_endpoint(client):
    """Test course endpoint"""
    course = CourseFactory.create()
    # this should be filtered out
    CourseFactory.create(runs=None)

    resp = client.get(reverse("courses-list"))
    assert resp.data.get("count") == 1
    assert resp.data.get("results")[0]["id"] == course.id
コード例 #3
0
def test_parse_mitx_json_data_overwrite(mocker, force_overwrite):
    """
    Test that valid mitx json data is skipped if it doesn't need an update
    """
    CourseFactory.create(course_id=mitx_valid_data["course_runs"][0]["key"],
                         last_modified=datetime.now().astimezone(pytz.utc))
    mock_save = mocker.patch(
        'course_catalog.tasks_helpers.CourseSerializer.save')
    parse_mitx_json_data(mitx_valid_data, force_overwrite=force_overwrite)
    assert mock_save.call_count == (1 if force_overwrite else 0)
コード例 #4
0
def test_featured_courses_endpoint(client, featured):
    """Test featured courses endpoint"""
    course = CourseFactory.create(featured=featured)
    # this should be filtered out
    CourseFactory.create(runs=None)

    resp = client.get(reverse("courses-list") + "featured/")

    assert resp.data.get("count") == (1 if featured else 0)
    if featured:
        assert resp.data.get("results")[0]["id"] == course.id
コード例 #5
0
def test_upcoming_courses_endpoint(client, kwargs, is_upcoming):
    """Test upcoming courses endpoint"""
    course = CourseFactory.create(runs=None)
    LearningResourceRunFactory.create(content_object=course, **kwargs)
    # this should be filtered out
    CourseFactory.create(runs=None)

    resp = client.get(reverse("courses-list") + "upcoming/")

    assert resp.data.get("count") == (1 if is_upcoming else 0)
    if is_upcoming:
        assert resp.data.get("results")[0]["id"] == course.id
コード例 #6
0
def test_get_course_endpoint(client):
    """Test course detail endpoint"""
    course = CourseFactory.create()

    resp = client.get(reverse("courses-detail", args=[course.id]))

    assert resp.data.get("course_id") == course.course_id
コード例 #7
0
def test_load_run(run_exists):
    """Test that load_run loads the course run"""
    course = CourseFactory.create(runs=None)
    learning_resource_run = (LearningResourceRunFactory.create(
        content_object=course) if run_exists else
                             LearningResourceRunFactory.build())

    props = model_to_dict(
        LearningResourceRunFactory.build(
            run_id=learning_resource_run.run_id,
            platform=learning_resource_run.platform))
    del props["content_type"]
    del props["object_id"]
    del props["id"]

    assert LearningResourceRun.objects.count() == (1 if run_exists else 0)

    result = load_run(course, props)

    assert LearningResourceRun.objects.count() == 1

    assert result.content_object == course

    # assert we got a course run back
    assert isinstance(result, LearningResourceRun)

    for key, value in props.items():
        assert getattr(result,
                       key) == value, f"Property {key} should equal {value}"
コード例 #8
0
def test_favorites_serializer():
    """
    Test that the favorite serializer generic foreign key works and also rejects unexpected classes
    """
    user = UserFactory.create()
    course = CourseFactory.create()
    bootcamp = BootcampFactory.create()
    user_list = UserListFactory.create(author=user)
    program = ProgramFactory.create()
    course_topic = CourseTopicFactory.create()

    favorite_item = FavoriteItem(user=user, item=course)
    serializer = FavoriteItemSerializer(favorite_item)
    assert serializer.data.get("content_data") == CourseSerializer(course).data

    favorite_item = FavoriteItem(user=user, item=bootcamp)
    serializer = FavoriteItemSerializer(favorite_item)
    assert serializer.data.get("content_data") == BootcampSerializer(
        bootcamp).data

    favorite_item = FavoriteItem(user=user, item=user_list)
    serializer = FavoriteItemSerializer(favorite_item)
    assert serializer.data.get("content_data") == UserListSerializer(
        user_list).data

    favorite_item = FavoriteItem(user=user, item=program)
    serializer = FavoriteItemSerializer(favorite_item)
    assert serializer.data.get("content_data") == ProgramSerializer(
        program).data

    favorite_item = FavoriteItem(user=user, item=course_topic)
    serializer = FavoriteItemSerializer(favorite_item)
    with pytest.raises(Exception):
        assert serializer.data.get("content_data").get("id") == course_topic.id
コード例 #9
0
def test_es_course_serializer(offered_by):
    """
    Test that ESCourseSerializer correctly serializes a course object
    """
    course = CourseFactory.create(offered_by=offered_by)
    serialized = ESCourseSerializer(course).data

    assert_json_equal(
        serialized,
        {
            "object_type": COURSE_TYPE,
            "id": course.id,
            "course_id": course.course_id,
            "coursenum": course.course_id.split("+")[-1],
            "short_description": course.short_description,
            "full_description": course.full_description,
            "platform": course.platform,
            "title": course.title,
            "image_src": course.image_src,
            "topics": list(course.topics.values_list("name", flat=True)),
            "runs": [
                ESRunSerializer(course_run).data
                for course_run in course.runs.order_by("-best_start_date")
            ],
            "published": True,
            "offered_by": list(course.offered_by.values_list("name", flat=True)),
            "created": drf_datetime(course.created_on),
            "default_search_priority": 1,
            "minimum_price": minimum_price(course),
            "resource_relations": {"name": "resource"},
        },
    )
コード例 #10
0
def test_sync_ocw_course_files(mock_ocw_learning_bucket, mocker, with_error):
    """Test that sync_ocw_course_files calls load_content_files for each run"""
    fake_data = '{"key": "data"}'
    mock_log = mocker.patch("course_catalog.api.log.exception")
    mock_transform = mocker.patch(
        "course_catalog.api.transform_content_files", return_value=fake_data
    )
    mock_load_content_files = mocker.patch(
        "course_catalog.api.load_content_files", autospec=True, return_value=[]
    )
    if with_error:
        mock_load_content_files.side_effect = Exception
    course = CourseFactory.create(published=True, platform=PlatformType.ocw.value)
    runs = course.runs.all()
    for run in runs:
        mock_ocw_learning_bucket.bucket.put_object(
            Key="{}/{}_master.json".format(run.url.split("/")[-1], run.run_id),
            Body=fake_data,
            ACL="public-read",
        )
    sync_ocw_course_files(ids=[course.id])
    assert mock_load_content_files.call_count == len(runs)
    for run in runs:
        mock_load_content_files.assert_any_call(run, mock_transform.return_value)
        if with_error:
            mock_log.assert_any_call("Error syncing files for course run %d", run.id)
コード例 #11
0
def test_upsert_course(mocker):
    """
    Tests that upsert_course calls update_field_values_by_query with the right parameters
    """
    patched_task = mocker.patch("search.tasks.upsert_course")
    course = CourseFactory.create()
    upsert_course(course.id)
    patched_task.delay.assert_called_once_with(course.id)
コード例 #12
0
def test_content_type_interactions_serializer_valid():
    """Verifies that valid data results in no errors"""
    course = CourseFactory.create()
    serializer = ContentTypeInteractionSerializer(
        data={
            "interaction_type": "view",
            "content_type": "course",
            "content_id": course.id,
        })

    assert serializer.is_valid() is True
    assert serializer.errors == {}
コード例 #13
0
def test_serialize_course_for_bulk():
    """
    Test that serialize_course_for_bulk yields a valid ESCourseSerializer
    """
    course = CourseFactory.create()
    assert_json_equal(
        serialize_course_for_bulk(course),
        {
            "_id": gen_course_id(course.platform, course.course_id),
            **ESCourseSerializer(course).data,
        },
    )
コード例 #14
0
def test_generate_duplicates_yaml(settings, mocker):
    """Test for generate_duplicates_yaml"""
    duplicate_course1 = CourseFactory.create(title="course1", platform="mitx")
    desired_course1 = CourseFactory.create(title="course1", platform="mitx")
    extra_offered_by = LearningResourceOfferorFactory.create()
    desired_course1.offered_by.add(extra_offered_by)

    duplicate_course2 = CourseFactory.create(title="course2", platform="mitx")
    desired_course2 = CourseFactory.create(title="course2", platform="mitx")

    settings.DUPLICATE_COURSES_URL = "url"
    duplicate_file_content = f"""mitx:
- course_id: {desired_course2.course_id}
  duplicate_course_ids:
  - {desired_course2.course_id}
  - {duplicate_course2.course_id}
  title: course2
"""
    mocker.patch(
        "requests.get",
        autospec=True,
        return_value=mocker.Mock(text=duplicate_file_content),
    )

    expected_output = f"""mitx:
- course_id: {desired_course1.course_id}
  duplicate_course_ids:
  - {desired_course1.course_id}
  - {duplicate_course1.course_id}
  title: course1
- course_id: {desired_course2.course_id}
  duplicate_course_ids:
  - {desired_course2.course_id}
  - {duplicate_course2.course_id}
  title: course2
"""

    assert generate_duplicates_yaml() == expected_output
コード例 #15
0
def test_delete_course(mocker):
    """
    Tests that delete_course calls the delete tasks for the course and its content files
    """
    patched_delete_task = mocker.patch("search.task_helpers.delete_document")
    course = CourseFactory.create()
    course_es_id = gen_course_id(course.platform, course.course_id)
    content_files = [ContentFileFactory.create(run=run) for run in course.runs.all()]

    delete_course(course)
    patched_delete_task.delay.assert_any_call(course_es_id, COURSE_TYPE)
    for content_file in content_files:
        patched_delete_task.delay.assert_any_call(
            gen_content_file_id(content_file.key), COURSE_TYPE, routing=course_es_id
        )
コード例 #16
0
def test_course_detail_endpoint_lists(user_client, user):
    """Test that author's list ids are included"""
    course = CourseFactory.create()
    user_lists = UserListFactory.create_batch(3, author=user)
    list_items = sorted(
        [
            UserListItemFactory.create(content_object=course,
                                       user_list=user_list)
            for user_list in user_lists
        ],
        key=lambda x: x.id,
    )
    resp = user_client.get(reverse("courses-detail", args=[course.id]))
    assert sorted(resp.data.get("lists"), key=lambda x: x["item_id"]) == [
        MicroUserListItemSerializer(list_item).data for list_item in list_items
    ]
コード例 #17
0
def test_bulk_index_content_files(mocked_es, mocker, settings, errors,
                                  indexing_func_name, doc):  # pylint: disable=too-many-arguments
    """
    index functions for content files should call bulk with correct arguments
    """
    settings.ELASTICSEARCH_INDEXING_CHUNK_SIZE = 3
    course = CourseFactory.create()
    run = LearningResourceRunFactory.create(content_object=course)
    content_files = ContentFileFactory.create_batch(5, run=run)
    mock_get_aliases = mocker.patch("search.indexing_api.get_active_aliases",
                                    autospec=True,
                                    return_value=["a", "b"])
    bulk_mock = mocker.patch("search.indexing_api.bulk",
                             autospec=True,
                             return_value=(0, errors))
    mocker.patch(
        f"search.indexing_api.serialize_content_file_for_bulk",
        autospec=True,
        return_value=doc,
    )
    mocker.patch(
        f"search.indexing_api.serialize_content_file_for_bulk_deletion",
        autospec=True,
        return_value=doc,
    )

    index_func = getattr(indexing_api, indexing_func_name)
    if errors:
        with pytest.raises(ReindexException):
            index_func(run.id)
    else:
        index_func(run.id)
        for alias in mock_get_aliases.return_value:
            for chunk in chunks(
                [doc for _ in content_files],
                    chunk_size=settings.ELASTICSEARCH_INDEXING_CHUNK_SIZE,
            ):
                bulk_mock.assert_any_call(
                    mocked_es.conn,
                    chunk,
                    index=alias,
                    doc_type=GLOBAL_DOC_TYPE,
                    chunk_size=settings.ELASTICSEARCH_INDEXING_CHUNK_SIZE,
                    routing=gen_course_id(course.platform, course.course_id),
                )
コード例 #18
0
def test_find_similar_resources(mocker, client):
    """The view should return the results of the API method for finding similar resources"""
    course = CourseFactory.create()
    doc_vals = {
        "id": course.id,
        "object_id": COURSE_TYPE,
        "title": course.title,
        "short_description": course.short_description,
    }
    fake_response = {"similar": "resources"}
    similar_resources_mock = mocker.patch(
        "search.views.find_similar_resources",
        autospec=True,
        return_value=fake_response)
    resp = client.post(reverse("similar-resources"), data=doc_vals)
    assert resp.json() == fake_response
    similar_resources_mock.assert_called_once_with(user=AnonymousUser(),
                                                   value_doc=doc_vals)
コード例 #19
0
def test_user_list_items_endpoint_create_item_bad_data(client, user):
    """Test userlistitems endpoint for creating a UserListItem"""
    userlist = UserListFactory.create(author=user,
                                      privacy_level=PrivacyLevel.public.value)
    course = CourseFactory.create()

    client.force_login(user)

    data = {"content_type": "bad_content", "object_id": course.id}

    resp = client.post(reverse("userlistitems-list", args=[userlist.id]),
                       data=data,
                       format="json")
    assert resp.status_code == 400
    assert resp.json() == {
        "non_field_errors": ["Incorrect object type bad_content"],
        "error_type": "ValidationError",
    }
コード例 #20
0
def test_user_list_items_endpoint_create_item(client, user, is_author,
                                              mock_user_list_index):
    """Test userlistitems endpoint for creating a UserListItem"""
    author = UserFactory.create()
    userlist = UserListFactory.create(author=author,
                                      privacy_level=PrivacyLevel.public.value)
    course = CourseFactory.create()

    client.force_login(author if is_author else user)

    data = {"content_type": "course", "object_id": course.id}

    resp = client.post(reverse("userlistitems-list", args=[userlist.id]),
                       data=data,
                       format="json")
    assert resp.status_code == (201 if is_author else 403)
    if resp.status_code == 201:
        assert resp.json().get("object_id") == course.id
        mock_user_list_index.upsert_user_list.assert_called_once_with(
            userlist.id)
コード例 #21
0
def test_load_course(mock_upsert_tasks, course_exists, is_published,
                     blacklisted):
    """Test that load_course loads the course"""
    course = (CourseFactory.create(runs=None, published=is_published)
              if course_exists else CourseFactory.build())
    assert Course.objects.count() == (1 if course_exists else 0)
    assert LearningResourceRun.objects.count() == 0

    props = model_to_dict(
        CourseFactory.build(course_id=course.course_id,
                            platform=course.platform,
                            published=is_published))
    del props["id"]
    run = model_to_dict(
        LearningResourceRunFactory.build(platform=course.platform))
    del run["content_type"]
    del run["object_id"]
    del run["id"]
    props["runs"] = [run]

    blacklist = [course.course_id] if blacklisted else []

    result = load_course(props, blacklist, [])

    if course_exists and not is_published and not blacklisted:
        mock_upsert_tasks.delete_course.assert_called_with(result)
    elif is_published and not blacklisted:
        mock_upsert_tasks.upsert_course.assert_called_with(result.id)
    else:
        mock_upsert_tasks.delete_program.assert_not_called()
        mock_upsert_tasks.upsert_course.assert_not_called()

    assert Course.objects.count() == 1
    assert LearningResourceRun.objects.count() == 1

    # assert we got a course back
    assert isinstance(result, Course)

    for key, value in props.items():
        assert getattr(result,
                       key) == value, f"Property {key} should equal {value}"
コード例 #22
0
def test_popular_content_serializer(mocker, is_deleted, user):
    """Test PopularContentSerializer"""
    resources = [
        VideoFactory.create(),
        ProgramFactory.create(),
        CourseFactory.create(),
        UserListFactory.create(),
        BootcampFactory.create(),
    ]

    data = [{
        "content_type_id": ContentType.objects.get_for_model(resource).id,
        "content_id": resource.id,
    } for resource in resources]

    if is_deleted:
        for resource in resources:
            resource.delete()
        resources = []

    resources = [
        type(resource).objects.filter(
            id=resource.id).prefetch_list_items_for_user(
                user).annotate_is_favorite_for_user(user).first()
        for resource in resources
    ]

    context = {"request": mocker.Mock(user=user)}
    # NOTE: we test PopularContentSerializer instead of PopularContentListSerializer
    #       because the list serializer is never used directly, but rather many=True tells
    #       PopularContentSerializer to delegate to PopularContentListSerializer
    results = PopularContentSerializer(data, context=context, many=True).data

    # should be sorted by the same order they were passed in
    assert_json_equal(
        results,
        [
            GenericForeignKeyFieldSerializer(resource, context=context).data
            for resource in resources
        ],
    )
コード例 #23
0
def test_course_report(client):
    """Test ocw course report"""
    CourseFactory.create(
        platform=PlatformType.ocw.value,
        learning_resource_type=ResourceType.course.value,
        published=False,
    )
    CourseFactory.create(
        platform=PlatformType.ocw.value,
        learning_resource_type=ResourceType.course.value,
        published=True,
        image_src="",
    )
    CourseFactory.create(
        platform=PlatformType.ocw.value,
        learning_resource_type=ResourceType.course.value,
        published=True,
        image_src="abc123",
    )
    CourseFactory.create(
        platform=PlatformType.ocw.value,
        learning_resource_type=ResourceType.ocw_resource.value,
        published=False,
    )

    username = "******"
    password = "******"
    User.objects.create_user(username=username, password=password)
    client.login(username=username, password=password)
    resp = client.get(reverse("ocw-course-report"))
    assert resp.data == {
        "total_number_of_ocw_courses": 3,
        "published_ocw_courses_with_image": 2,
        "unpublished_ocw_courses": 1,
        "ocw_courses_without_image": 1,
        "ocw_resources": 1,
    }
コード例 #24
0
def deserializing_a_valid_ocw_course_with_existing_newer_run(
    mock_course_index_functions, ocw_valid_data
):
    """
    Verify that course values are not overwritten if the course already has a newer run
    """

    course = CourseFactory.create(
        platform=PlatformType.ocw.value,
        course_id=ocw_valid_data["course_id"],
        title="existing",
    )

    assert course.runs.count() == 3
    existing_run = course.runs.first()
    existing_run.best_start_date = datetime.now(timezone.utc)
    existing_run.save()

    digest_ocw_course(ocw_valid_data, timezone.now(), True)
    assert Course.objects.count() == 1
    course = Course.objects.last()
    assert course.title == "existing"
    assert course.runs.count() == 4
コード例 #25
0
def test_content_type_interaction_create(client):
    """Test that the content type interaction API creates a record"""
    course = CourseFactory.create()
    content_type = ContentType.objects.get_for_model(course)

    assert ContentTypeInteraction.objects.count() == 0

    payload = {
        "interaction_type": "view",
        "content_type": content_type.name,
        "content_id": course.id,
    }

    response = client.post(reverse("interactions-list"), payload)

    assert response.status_code == status.HTTP_201_CREATED
    assert response.json() == payload

    assert ContentTypeInteraction.objects.count() == 1

    interaction = ContentTypeInteraction.objects.first()
    assert interaction.interaction_type == "view"
    assert interaction.content == course
コード例 #26
0
def test_upload_ocw_master_json(settings, mocker):
    """
    Test that ocw_upload_master_json uploads to S3
    """
    setup_s3(settings)

    course = CourseFactory.create(platform=PlatformType.ocw.value)
    course.url = "http://ocw.mit.edu/courses/brain-and-cognitive-sciences/9-15-biochemistry-and-pharmacology-of-synaptic-transmission-fall-2007"
    course.raw_json = {"test": "json"}
    course.save()

    upload_ocw_master_json.delay()

    s3 = boto3.resource(
        "s3",
        aws_access_key_id=settings.OCW_LEARNING_COURSE_BUCKET_NAME,
        aws_secret_access_key=settings.OCW_LEARNING_COURSE_ACCESS_KEY,
    )
    # The filename was pulled from the uid 1.json in the TEST_JSON_PATH files.
    obj = s3.Object(
        settings.OCW_LEARNING_COURSE_BUCKET_NAME,
        f"9-15-biochemistry-and-pharmacology-of-synaptic-transmission-fall-2007/{course.course_id}_master.json",
    )
    assert json.loads(obj.get()["Body"].read())
コード例 #27
0
def test_load_duplicate_course(mock_upsert_tasks, course_exists,
                               course_id_is_duplicate,
                               duplicate_course_exists):
    """Test that load_course loads the course"""
    course = CourseFactory.create(
        runs=None) if course_exists else CourseFactory.build()

    duplicate_course = (CourseFactory.create(runs=None,
                                             platform=course.platform)
                        if duplicate_course_exists else CourseFactory.build())

    if course_exists and duplicate_course_exists:
        assert Course.objects.count() == 2
    elif course_exists or duplicate_course_exists:
        assert Course.objects.count() == 1
    else:
        assert Course.objects.count() == 0

    assert LearningResourceRun.objects.count() == 0

    duplicates = [{
        "course_id":
        course.course_id,
        "duplicate_course_ids": [course.course_id, duplicate_course.course_id],
    }]

    course_id = (duplicate_course.course_id
                 if course_id_is_duplicate else course.course_id)

    props = model_to_dict(
        CourseFactory.build(course_id=course_id, platform=course.platform))

    del props["id"]
    run = model_to_dict(
        LearningResourceRunFactory.build(platform=course.platform))
    del run["content_type"]
    del run["object_id"]
    del run["id"]
    props["runs"] = [run]

    result = load_course(props, [], duplicates)

    if course_id_is_duplicate and duplicate_course_exists:
        mock_upsert_tasks.delete_course.assert_called()

    mock_upsert_tasks.upsert_course.assert_called_with(result.id)

    assert Course.objects.count() == (2 if duplicate_course_exists else 1)

    assert LearningResourceRun.objects.count() == 1

    # assert we got a course back
    assert isinstance(result, Course)

    saved_course = Course.objects.filter(course_id=course.course_id).first()

    for key, value in props.items():
        assert getattr(result,
                       key) == value, f"Property {key} should equal {value}"
        assert (
            getattr(saved_course, key) == value
        ), f"Property {key} should be updated to {value} in the database"
コード例 #28
0
def test_load_program(
    mock_upsert_tasks,
    program_exists,
    is_published,
    courses_exist,
    has_prices,
    has_retired_course,
):  # pylint: disable=too-many-arguments
    """Test that load_program loads the program"""
    program = (ProgramFactory.create(published=is_published, runs=[]) if
               program_exists else ProgramFactory.build(published=is_published,
                                                        runs=[]))
    courses = (CourseFactory.create_batch(2, platform="fake-platform")
               if courses_exist else CourseFactory.build_batch(
                   2, platform="fake-platform"))
    prices = CoursePriceFactory.build_batch(2) if has_prices else []

    before_course_count = len(courses) if courses_exist else 0
    after_course_count = len(courses)

    if program_exists and has_retired_course:
        course = CourseFactory.create(platform="fake-platform")
        before_course_count += 1
        after_course_count += 1
        ProgramItem.objects.create(
            program=program,
            content_type=ContentType.objects.get(model="course"),
            object_id=course.id,
            position=1,
        )
        assert program.items.count() == 1
    else:
        assert program.items.count() == 0

    assert Program.objects.count() == (1 if program_exists else 0)
    assert Course.objects.count() == before_course_count

    run_data = {
        "prices": [{
            "price": price.price,
            "mode": price.mode,
            "upgrade_deadline": price.upgrade_deadline,
        } for price in prices],
        "platform":
        PlatformType.mitx.value,
        "run_id":
        program.program_id,
        "enrollment_start":
        "2017-01-01T00:00:00Z",
        "start_date":
        "2017-01-20T00:00:00Z",
        "end_date":
        "2017-06-20T00:00:00Z",
        "best_start_date":
        "2017-06-20T00:00:00Z",
        "best_end_date":
        "2017-06-20T00:00:00Z",
    }

    result = load_program(
        {
            "program_id":
            program.program_id,
            "title":
            program.title,
            "url":
            program.url,
            "image_src":
            program.image_src,
            "published":
            is_published,
            "runs": [run_data],
            "courses": [{
                "course_id": course.course_id,
                "platform": course.platform
            } for course in courses],
        },
        [],
        [],
    )

    if program_exists and not is_published:
        mock_upsert_tasks.delete_program.assert_called_with(result)
    elif is_published:
        mock_upsert_tasks.upsert_program.assert_called_with(result.id)
    else:
        mock_upsert_tasks.delete_program.assert_not_called()
        mock_upsert_tasks.upsert_program.assert_not_called()

    assert Program.objects.count() == 1
    assert Course.objects.count() == after_course_count

    # assert we got a program back and that each course is in a program
    assert isinstance(result, Program)
    assert result.items.count() == len(courses)
    assert result.runs.count() == 1
    assert result.runs.first().prices.count() == len(prices)
    assert sorted([(price.price, price.mode, price.upgrade_deadline)
                   for price in result.runs.first().prices.all()]) == sorted([
                       (price.price, price.mode, price.upgrade_deadline)
                       for price in prices
                   ])

    assert result.runs.first().best_start_date == _parse_datetime(
        run_data["best_start_date"])

    for item, data in zip(result.items.all(), courses):
        course = item.item
        assert isinstance(course, Course)
        assert course.course_id == data.course_id