예제 #1
0
def test_obtain_token():
    api = API(base_url=TEST_BASE_URL)
    auth_result = api.obtain_token(**TEST_CREDENTIALS)
    assert "access_token" in auth_result
    assert len(auth_result["access_token"]) > 0
    assert "user_id" in auth_result
    assert auth_result["user_id"] == TEST_CREDENTIALS["user_id"]
    assert "expires" in auth_result
예제 #2
0
def test_course_crud():
    api = API(base_url=TEST_BASE_URL)

    # obtain token
    auth_result = api.obtain_token(**TEST_CREDENTIALS)
    api.access_token = auth_result["access_token"]

    # create course
    title = "SDK Course"
    lti_context_id = generate_ctx_id()
    lti_tool_consumer_instance_guid = "test.institution.edu"
    course_created = api.create_course(
        title=title,
        lti_context_id=lti_context_id,
        lti_tool_consumer_instance_guid=lti_tool_consumer_instance_guid,
    )
    assert "id" in course_created
    assert course_created["title"] == title
    course_id = course_created["id"]

    # get course by its primary key
    course_read = api.get_course(course_id)
    assert "id" in course_read
    assert course_read["id"] == course_id

    # find course by context id and instance guid
    # exact match expected because these two attributes uniquely identify a course
    courses_found = api.list_courses(
        lti_context_id=lti_context_id,
        lti_tool_consumer_instance_guid=lti_tool_consumer_instance_guid)
    assert len(courses_found) == 1
    assert courses_found[0]["id"] == course_id

    # search courses by title, which should include the course that was just created
    courses_search = api.search_courses(text=title)
    assert len(courses_search) > 0
    assert course_id in [course["id"] for course in courses_search]

    # update course details
    update_params = dict(
        title=title + " Updated",
        sis_course_id="test123",
        canvas_course_id=100,
        lti_context_title="My Test Context",
        lti_context_label="TCX",
        lti_context_id=course_created[
            "lti_context_id"],  # TODO: why is this required for an update?
        lti_tool_consumer_instance_guid=course_created[
            "lti_tool_consumer_instance_guid"],  # TODO: why is this required for an update?
    )
    course_updated = api.update_course(course_id, **update_params)
    course_updated_subset = {k: course_updated[k] for k in update_params}
    assert update_params == course_updated_subset

    # delete the course
    course_deleted = api.delete_course(course_id)
    assert course_deleted == {}
예제 #3
0
def test_obtain_token_with_invalid_course_permission():
    api = API(base_url=TEST_BASE_URL)
    with pytest.raises(ValueError):
        api.obtain_token(
            client_id=TEST_CLIENT_ID,
            client_secret=TEST_CLIENT_SECRET,
            user_id=TEST_USER_ID,
            course_permission="invalid",
        )
예제 #4
0
    def __init__(self, client_id: str, client_secret: str, base_url: str):
        if client_id is None or client_secret is None:
            raise ValueError("Missing client credentials")
        if base_url is None:
            raise ValueError("Missing base URL to use for API requests")

        self.client_id = client_id
        self.client_secret = client_secret
        self.base_url = base_url
        self.api = API(base_url=base_url)
예제 #5
0
def test_upload_multiple_images_and_add_to_collection():
    api = API(base_url=TEST_BASE_URL)
    auth_result = api.obtain_token(**TEST_CREDENTIALS)
    api.access_token = auth_result["access_token"]

    # create course
    course_created = api.create_course(
        title="SDK Course",
        lti_context_id=generate_ctx_id(),
        lti_tool_consumer_instance_guid="test.institution.edu",
    )
    course_id = course_created["id"]

    # upload multiple images to the course
    image_ids = []
    content_type = "image/png"
    upload_files = [(file_name,
                     open(os.path.join(TEST_IMAGES_DIR, file_name),
                          "rb"), content_type) for file_name in (
                              "16x16-ff00ff7f.png",
                              "32x32-faaa1aff.png",
                              "64x64-ff0000c1.png",
                          )]
    try:
        images_uploaded = api.upload_images(
            course_id,
            upload_files=upload_files,
            title="Color Block",
        )
        assert len(
            images_uploaded) == 3, "list returned with the uploaded image"
        image_ids = [image["id"] for image in images_uploaded]
    finally:
        for (file_name, fp, content_type) in upload_files:
            fp.close()

    # create collection
    collection_created = api.create_collection(course_id,
                                               title="SDK Course",
                                               description="Just a test")
    collection_id = collection_created["id"]

    # add images to collection
    update_params = dict(
        course_id=course_id,
        title=collection_created["title"],
        course_image_ids=image_ids,
    )
    collection_updated = api.update_collection(collection_id, **update_params)
    assert collection_updated["course_image_ids"] == image_ids

    # delete course
    api.delete_course(course_id)
예제 #6
0
def test_api_implements_required_methods():
    methods = (
        "obtain_token",
        "list_courses",
        "search_courses",
        "get_course",
        "create_course",
        "update_course",
        "delete_course",
        "copy_course",
        "list_collections",
        "get_collection",
        "get_collection_images",
        "create_collection",
        "update_collection",
        "delete_collection",
        "upload_image",
        "upload_images",
        "update_image",
        "get_image",
        "delete_image",
    )
    api = API()
    for method in methods:
        assert hasattr(api, method)
        assert callable(getattr(api, method))
예제 #7
0
def test_do_request_raises_exception_for_status(http_method, status_code,
                                                error_class):
    expected_url = f"{TEST_BASE_URL}/test/123"
    mock_resp = Mock()
    mock_resp.status_code = status_code
    mock_resp.raise_for_status = Mock(side_effect=error_class)
    mock_resp.json = Mock(return_value=None)
    with patch(f"requests.{http_method}",
               return_value=mock_resp) as mock_method:
        with pytest.raises(error_class):
            API()._do_request(method=http_method, url=expected_url)
예제 #8
0
def test_collection_crud():
    api = API(base_url=TEST_BASE_URL)
    auth_result = api.obtain_token(**TEST_CREDENTIALS)
    api.access_token = auth_result["access_token"]

    # create course
    title = "SDK Course"
    course_created = api.create_course(
        title=title,
        lti_context_id=generate_ctx_id(),
        lti_tool_consumer_instance_guid="test.institution.edu",
    )
    course_id = course_created["id"]

    # create collection
    collection_params = dict(
        title="SDK Course",
        description="This is a fascinating collection",
    )
    collection_created = api.create_collection(course_id, **collection_params)
    assert "id" in collection_created
    assert collection_created["title"] == collection_params["title"]
    assert collection_created["description"] == collection_params[
        "description"]
    collection_id = collection_created["id"]

    # update collection
    update_params = dict(
        course_id=course_id,  # TODO: why is this required for an update?
        title=collection_created["title"] + " Updated",
        description=collection_created["description"] + " Updated",
    )
    collection_updated = api.update_collection(collection_id, **update_params)
    collection_updated_subset = {
        k: collection_updated[k]
        for k in update_params
    }
    assert update_params == collection_updated_subset

    # delete collection
    collection_deleted = api.delete_collection(collection_id)
    assert collection_deleted == {}

    # delete course
    course_deleted = api.delete_course(course_id)
    assert course_deleted == {}
예제 #9
0
def test_obtain_token_for_user():
    headers = TEST_HEADERS
    data = {
        "client_id": TEST_CLIENT_ID,
        "client_secret": TEST_CLIENT_SECRET,
        "user_id": TEST_USER_ID,
    }
    expected_token = "b0a4e9e4ae4a4cbcb079eab3637f2b22"

    api = API(base_url=TEST_BASE_URL)
    api._do_request = Mock(return_value={"access_token": expected_token})
    actual_response = api.obtain_token(client_id=TEST_CLIENT_ID,
                                       client_secret=TEST_CLIENT_SECRET,
                                       user_id=TEST_USER_ID)
    api._do_request.assert_called_with(
        method="post",
        url=f"{TEST_BASE_URL}/auth/obtain-token",
        headers=headers,
        json=data,
    )
    assert actual_response["access_token"] == expected_token
예제 #10
0
def test_do_request_with_method(http_method, status_code):
    url = f"{TEST_BASE_URL}/test/123"
    expected_response = {"id": "100"}

    mock_resp = Mock()
    mock_resp.status_code = status_code
    mock_resp.json = Mock(return_value=expected_response)

    with patch(f"requests.{http_method}",
               return_value=mock_resp) as mock_method:
        actual_response = API()._do_request(method=http_method, url=url)
        mock_method.assert_called_once_with(url, timeout=DEFAULT_TIMEOUT)
        assert actual_response == expected_response
예제 #11
0
def test_list_courses_filtered_by_lti_params(courses_fixture):
    course = courses_fixture[0]

    access_token = "token123"
    headers = dict(**TEST_HEADERS, Authorization=f"Token {access_token}")
    params = dict(
        lti_context_id=course["lti_context_id"],
        lti_tool_consumer_instance_guid=course[
            "lti_tool_consumer_instance_guid"],
        canvas_course_id=None,
        sis_course_id=None,
        title=None,
    )

    api = API(base_url=TEST_BASE_URL, access_token=access_token)
    api._do_request = Mock(return_value=[course])
    actual_response = api.list_courses(**params)
    api._do_request.assert_called_with(method="get",
                                       url=f"{TEST_BASE_URL}/courses",
                                       headers=headers,
                                       params=params)
    assert actual_response == [course]
예제 #12
0
def test_image_crud():
    api = API(base_url=TEST_BASE_URL)
    auth_result = api.obtain_token(**TEST_CREDENTIALS)
    api.access_token = auth_result["access_token"]

    # create course
    course_created = api.create_course(
        title="SDK Course",
        lti_context_id=generate_ctx_id(),
        lti_tool_consumer_instance_guid="test.institution.edu",
    )
    course_id = course_created["id"]

    # upload a single image to the course
    image_id = None
    file_name = "16x16-ff00ff7f.png"
    content_type = "image/png"
    with open(os.path.join(TEST_IMAGES_DIR, file_name), "rb") as upload_file:
        image_uploaded = api.upload_image(
            course_id,
            upload_file=upload_file,
            file_name=file_name,
            content_type=content_type,
            title="Color Block Image",
        )
        assert len(
            image_uploaded) == 1, "list returned with the uploaded image"
        assert "id" in image_uploaded[0], "the image has an id"
        image_id = image_uploaded[0]["id"]

    # get image details
    image_read = api.get_image(image_id)
    assert image_read["id"] == image_id

    # update image details
    update_params = dict(
        course_id=course_id,
        title=image_read["title"] + " Updated",
        description="Color block is amazing!",
        metadata=dict(Creator="SDK", Audience="Test", Date="2000"),
    )
    image_updated = api.update_image(image_id, **update_params)
    assert image_updated["id"] == image_id
    assert image_updated["title"] == update_params["title"]

    # delete image
    image_deleted = api.delete_image(image_id)
    assert image_deleted == {}

    # delete course
    api.delete_course(course_id)
예제 #13
0
class Client(object):
    """
    Client is a wrapper for interacting with the API.
    """

    def __init__(self, client_id: str, client_secret: str, base_url: str):
        if client_id is None or client_secret is None:
            raise ValueError("Missing client credentials")
        if base_url is None:
            raise ValueError("Missing base URL to use for API requests")

        self.client_id = client_id
        self.client_secret = client_secret
        self.base_url = base_url
        self.api = API(base_url=base_url)

    def authenticate(
        self,
        user_id: str,
        course_id: Optional[int] = None,
        course_permission: Optional[str] = None,
    ) -> None:
        """
        Authenticate with the API by supplying client credentials and obtaining
        a token tied to the user.
        """
        if not user_id:
            raise ValueError("User ID is required to authenticate")
        response = self.api.obtain_token(
            client_id=self.client_id,
            client_secret=self.client_secret,
            user_id=user_id,
            course_id=course_id,
            course_permission=course_permission,
        )
        self.api.access_token = response["access_token"]

    def find_or_create_course(
        self,
        lti_context_id: str,
        lti_tool_consumer_instance_guid: str,
        title: str = "",
        lti_context_title: Optional[str] = None,
        lti_context_label: Optional[str] = None,
        sis_course_id: Optional[str] = None,
        canvas_course_id: Optional[int] = None,
    ) -> dict:
        """
        Helper method to find or create a course.
        """
        courses = self.api.list_courses(
            lti_context_id=lti_context_id,
            lti_tool_consumer_instance_guid=lti_tool_consumer_instance_guid,
        )
        if len(courses) > 1:
            raise ApiError("Multiple courses found")
        elif len(courses) == 1:
            return courses[0]

        return self.api.create_course(
            title=title,
            lti_context_id=lti_context_id,
            lti_tool_consumer_instance_guid=lti_tool_consumer_instance_guid,
            lti_context_title=lti_context_title,
            lti_context_label=lti_context_label,
            sis_course_id=sis_course_id,
            canvas_course_id=canvas_course_id,
        )