canvas-lms-api 0.6.0

Rust client for the Instructure Canvas LMS REST API
Documentation
use canvas_lms_api::{
    resources::section::{EnrollUserParams, UpdateSectionParams},
    Canvas,
};
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

fn section_json(id: u64, course_id: u64) -> serde_json::Value {
    serde_json::json!({
        "id": id,
        "course_id": course_id,
        "name": "Section A",
        "sis_section_id": null,
        "total_students": 5
    })
}

fn enrollment_json(id: u64, course_id: u64) -> serde_json::Value {
    serde_json::json!({
        "id": id,
        "course_id": course_id,
        "user_id": 10,
        "type": "StudentEnrollment",
        "enrollment_state": "active"
    })
}

async fn setup(server: &MockServer) -> canvas_lms_api::resources::section::Section {
    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"id": 1})))
        .mount(server)
        .await;
    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1/sections/5"))
        .respond_with(ResponseTemplate::new(200).set_body_json(section_json(5, 1)))
        .mount(server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let course = canvas.get_course(1).await.unwrap();
    course.get_section(5).await.unwrap()
}

#[tokio::test]
async fn test_section_edit() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("PUT"))
        .and(path("/api/v1/sections/5"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "id": 5,
            "course_id": 1,
            "name": "Section B"
        })))
        .mount(&server)
        .await;

    let updated = section
        .edit(UpdateSectionParams {
            name: Some("Section B".to_string()),
            ..Default::default()
        })
        .await
        .unwrap();
    assert_eq!(updated.name.as_deref(), Some("Section B"));
}

#[tokio::test]
async fn test_section_delete() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("DELETE"))
        .and(path("/api/v1/sections/5"))
        .respond_with(ResponseTemplate::new(200).set_body_json(section_json(5, 1)))
        .mount(&server)
        .await;

    let deleted = section.delete().await.unwrap();
    assert_eq!(deleted.id, 5);
}

#[tokio::test]
async fn test_section_enroll_user() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("POST"))
        .and(path("/api/v1/sections/5/enrollments"))
        .respond_with(ResponseTemplate::new(200).set_body_json(enrollment_json(20, 1)))
        .mount(&server)
        .await;

    let enrollment = section
        .enroll_user(EnrollUserParams {
            user_id: 10,
            r#type: Some("StudentEnrollment".to_string()),
            ..Default::default()
        })
        .await
        .unwrap();
    assert_eq!(enrollment.id, 20);
    assert_eq!(enrollment.user_id, Some(10));
}

#[tokio::test]
async fn test_section_get_enrollments() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("GET"))
        .and(path("/api/v1/sections/5/enrollments"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
            enrollment_json(20, 1),
            enrollment_json(21, 1)
        ])))
        .mount(&server)
        .await;

    let enrollments: Vec<_> = section.get_enrollments().collect_all().await.unwrap();
    assert_eq!(enrollments.len(), 2);
    assert_eq!(enrollments[0].id, 20);
}

#[tokio::test]
async fn test_section_cross_list_section() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("POST"))
        .and(path("/api/v1/sections/5/crosslist/2"))
        .respond_with(ResponseTemplate::new(200).set_body_json(section_json(5, 2)))
        .mount(&server)
        .await;

    let moved = section.cross_list_section(2).await.unwrap();
    assert_eq!(moved.course_id, Some(2));
}

#[tokio::test]
async fn test_section_decross_list_section() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("DELETE"))
        .and(path("/api/v1/sections/5/crosslist"))
        .respond_with(ResponseTemplate::new(200).set_body_json(section_json(5, 1)))
        .mount(&server)
        .await;

    let restored = section.decross_list_section().await.unwrap();
    assert_eq!(restored.course_id, Some(1));
}

#[tokio::test]
async fn test_section_get_assignment_override() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("GET"))
        .and(path("/api/v1/sections/5/assignments/10/override"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "id": 100,
            "assignment_id": 10,
            "course_section_id": 5
        })))
        .mount(&server)
        .await;

    let override_val = section.get_assignment_override(10).await.unwrap();
    assert_eq!(override_val["assignment_id"], 10);
}

#[tokio::test]
async fn test_section_get_multiple_submissions() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("GET"))
        .and(path("/api/v1/sections/5/students/submissions"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
            {"id": 30, "assignment_id": 10, "user_id": 5, "grade": "A"}
        ])))
        .mount(&server)
        .await;

    let subs: Vec<_> = section
        .get_multiple_submissions()
        .collect_all()
        .await
        .unwrap();
    assert_eq!(subs.len(), 1);
    assert_eq!(subs[0].id, 30);
}

#[tokio::test]
async fn test_section_submissions_bulk_update() {
    let server = MockServer::start().await;
    let section = setup(&server).await;

    Mock::given(method("POST"))
        .and(path("/api/v1/sections/5/submissions/update_grades"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "id": 99,
            "workflow_state": "queued",
            "completion": 0
        })))
        .mount(&server)
        .await;

    let progress = section
        .submissions_bulk_update(&[(
            "grade_data[5][10][posted_grade]".to_string(),
            "A".to_string(),
        )])
        .await
        .unwrap();
    assert_eq!(progress.id, 99);
    assert_eq!(progress.workflow_state.as_deref(), Some("queued"));
}