canvas-lms-api 0.1.0

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

#[tokio::test]
async fn test_bad_request() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
            "errors": [{"message": "invalid"}]
        })))
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    assert!(
        matches!(err, CanvasError::BadRequest { .. }),
        "expected BadRequest, got {:?}",
        err
    );
}

#[tokio::test]
async fn test_invalid_access_token() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(
            ResponseTemplate::new(401)
                .insert_header("WWW-Authenticate", "Bearer realm=\"canvas-lms\"")
                .set_body_json(serde_json::json!({
                    "errors": [{"message": "Invalid access token."}]
                })),
        )
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "bad-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    assert!(
        matches!(err, CanvasError::InvalidAccessToken(_)),
        "expected InvalidAccessToken, got {:?}",
        err
    );
}

#[tokio::test]
async fn test_unauthorized() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(ResponseTemplate::new(401).set_body_json(serde_json::json!({
            "errors": [{"message": "Unauthorized"}]
        })))
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    assert!(
        matches!(err, CanvasError::Unauthorized(_)),
        "expected Unauthorized, got {:?}",
        err
    );
}

#[tokio::test]
async fn test_forbidden() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(ResponseTemplate::new(403).set_body_json(serde_json::json!({
            "errors": [{"message": "Forbidden"}]
        })))
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    assert!(
        matches!(err, CanvasError::Forbidden(_)),
        "expected Forbidden, got {:?}",
        err
    );
}

#[tokio::test]
async fn test_not_found() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
            "errors": [{"message": "The specified resource does not exist."}]
        })))
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    assert!(
        matches!(err, CanvasError::ResourceDoesNotExist),
        "expected ResourceDoesNotExist, got {:?}",
        err
    );
}

#[tokio::test]
async fn test_conflict() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(ResponseTemplate::new(409).set_body_json(serde_json::json!({
            "errors": [{"message": "Conflict"}]
        })))
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    assert!(
        matches!(err, CanvasError::Conflict(_)),
        "expected Conflict, got {:?}",
        err
    );
}

#[tokio::test]
async fn test_unprocessable() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(ResponseTemplate::new(422).set_body_json(serde_json::json!({
            "errors": [{"message": "Unprocessable entity"}]
        })))
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    assert!(
        matches!(err, CanvasError::UnprocessableEntity(_)),
        "expected UnprocessableEntity, got {:?}",
        err
    );
}

#[tokio::test]
async fn test_rate_limited() {
    let server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/v1/courses/1"))
        .respond_with(
            ResponseTemplate::new(429)
                .insert_header("X-Rate-Limit-Remaining", "0")
                .set_body_json(serde_json::json!({
                    "errors": [{"message": "Rate limit exceeded."}]
                })),
        )
        .mount(&server)
        .await;

    let canvas = Canvas::new(&server.uri(), "test-token").unwrap();
    let err = canvas.get_course(1).await.unwrap_err();

    match &err {
        CanvasError::RateLimitExceeded { remaining } => {
            assert_eq!(remaining.as_deref(), Some("0"));
        }
        other => panic!("expected RateLimitExceeded, got {:?}", other),
    }
}