onspring-api-sdk-rust 0.0.1

Rust SDK for the Onspring API
Documentation
mod common;

use std::collections::HashMap;

use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, ResponseTemplate};

#[tokio::test]
async fn test_list_records() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("GET"))
    .and(path("/Records/appId/1"))
    .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
        "pageNumber": 1,
        "pageSize": 50,
        "totalPages": 1,
        "totalRecords": 1,
        "items": [
            {
                "appId": 1,
                "recordId": 100,
                "fieldData": [
                    {"type": "String", "fieldId": 10, "value": "hello"}
                ]
            }
        ]
    })))
    .mount(&mock_server)
    .await;

  let result = client.list_records(1, None, None, None).await.unwrap();
  assert_eq!(result.total_records, Some(1));
  let items = result.items.unwrap();
  assert_eq!(items[0].record_id, 100);
  let field_data = items[0].field_data.as_ref().unwrap();
  assert_eq!(field_data[0].field_id, 10);
  assert_eq!(field_data[0].value, serde_json::json!("hello"));
}

#[tokio::test]
async fn test_list_records_with_params() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("GET"))
    .and(path("/Records/appId/1"))
    .and(query_param("fieldIds", "10,20"))
    .and(query_param("dataFormat", "Formatted"))
    .and(query_param("PageNumber", "2"))
    .and(query_param("PageSize", "10"))
    .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
        "pageNumber": 2,
        "pageSize": 10,
        "totalPages": 5,
        "totalRecords": 50,
        "items": []
    })))
    .mount(&mock_server)
    .await;

  let paging = onspring::PagingRequest {
    page_number: 2,
    page_size: 10,
  };
  let result = client
    .list_records(
      1,
      Some(paging),
      Some(&[10, 20]),
      Some(onspring::DataFormat::Formatted),
    )
    .await
    .unwrap();
  assert_eq!(result.page_number, Some(2));
}

#[tokio::test]
async fn test_get_record() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("GET"))
    .and(path("/Records/appId/1/recordId/42"))
    .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
        "appId": 1,
        "recordId": 42,
        "fieldData": [
            {"type": "Integer", "fieldId": 5, "value": 123}
        ]
    })))
    .mount(&mock_server)
    .await;

  let record = client.get_record(1, 42, None, None).await.unwrap();
  assert_eq!(record.record_id, 42);
  assert_eq!(record.app_id, 1);
}

#[tokio::test]
async fn test_save_record_create() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("PUT"))
    .and(path("/Records"))
    .respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
        "id": 99,
        "warnings": []
    })))
    .mount(&mock_server)
    .await;

  let mut fields = HashMap::new();
  fields.insert("10".to_string(), serde_json::json!("test value"));

  let request = onspring::models::record::SaveRecordRequest {
    app_id: 1,
    record_id: None,
    fields,
  };
  let result = client.save_record(request).await.unwrap();
  assert_eq!(result.id, 99);
}

#[tokio::test]
async fn test_delete_record() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("DELETE"))
    .and(path("/Records/appId/1/recordId/42"))
    .respond_with(ResponseTemplate::new(204))
    .mount(&mock_server)
    .await;

  let result = client.delete_record(1, 42).await;
  assert!(result.is_ok());
}

#[tokio::test]
async fn test_batch_get_records() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("POST"))
    .and(path("/Records/batch-get"))
    .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
        "count": 2,
        "items": [
            {"appId": 1, "recordId": 1, "fieldData": []},
            {"appId": 1, "recordId": 2, "fieldData": []}
        ]
    })))
    .mount(&mock_server)
    .await;

  let request = onspring::models::record::BatchGetRecordsRequest {
    app_id: 1,
    record_ids: vec![1, 2],
    field_ids: None,
    data_format: None,
  };
  let result = client.batch_get_records(request).await.unwrap();
  assert_eq!(result.count, Some(2));
}

#[tokio::test]
async fn test_query_records() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("POST"))
    .and(path("/Records/Query"))
    .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
        "pageNumber": 1,
        "pageSize": 50,
        "totalPages": 1,
        "totalRecords": 1,
        "items": [
            {"appId": 1, "recordId": 5, "fieldData": []}
        ]
    })))
    .mount(&mock_server)
    .await;

  let request = onspring::models::record::QueryRecordsRequest {
    app_id: 1,
    filter: "fieldId eq 'test'".to_string(),
    field_ids: None,
    data_format: None,
  };
  let result = client.query_records(request, None).await.unwrap();
  assert_eq!(result.total_records, Some(1));
}

#[tokio::test]
async fn test_batch_delete_records() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("POST"))
    .and(path("/Records/batch-delete"))
    .respond_with(ResponseTemplate::new(204))
    .mount(&mock_server)
    .await;

  let request = onspring::models::record::BatchDeleteRecordsRequest {
    app_id: 1,
    record_ids: vec![1, 2, 3],
  };
  let result = client.batch_delete_records(request).await;
  assert!(result.is_ok());
}

#[tokio::test]
async fn test_get_record_not_found() {
  let (mock_server, client) = common::setup().await;

  Mock::given(method("GET"))
    .and(path("/Records/appId/1/recordId/999"))
    .respond_with(ResponseTemplate::new(404))
    .mount(&mock_server)
    .await;

  let result = client.get_record(1, 999, None, None).await;
  assert!(result.is_err());
  if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
    assert_eq!(status_code, 404);
  } else {
    panic!("Expected Api error");
  }
}