use parse_rust_core::{ErrorCode, ParseMap, ParseValue};
use parse_rust_mongo::MongoAdapter;
use parse_rust_schema::default_schema;
use parse_rust_storage::{
ClassSchema, Comparison, Constraint, FieldType, QueryOptions, SortDirection, StorageAdapter,
};
const URI: &str = "mongodb://127.0.0.1:27017";
async fn adapter(test: &str) -> (MongoAdapter, String) {
let db = format!("parse_rust_it_{}_{}", std::process::id(), test);
let a = MongoAdapter::connect(URI, &db)
.await
.expect("MongoDB must be running on 27017 for this test");
(a, db)
}
async fn drop_db(db: &str) {
if let Ok(client) = mongodb::Client::with_uri_str(URI).await {
let _ = client.database(db).drop().await;
}
}
fn row(pairs: Vec<(&str, ParseValue)>) -> ParseMap {
let mut m = ParseMap::new();
for (k, v) in pairs {
m.insert(k.to_string(), v);
}
m
}
fn post_schema() -> ClassSchema {
let mut s = default_schema("Post");
s.fields.insert("title".into(), FieldType::String);
s.fields.insert("views".into(), FieldType::Number);
s.fields.insert(
"author".into(),
FieldType::Pointer {
target_class: "_User".into(),
},
);
s
}
#[tokio::test]
#[ignore = "requires MongoDB on 27017; run via tools/test.sh"]
async fn create_then_find_round_trips() {
let (a, db) = adapter("roundtrip").await;
let schema = post_schema();
a.create(
&schema,
&row(vec![
("objectId", ParseValue::String("oid0000001".into())),
("title", ParseValue::String("hello".into())),
("views", ParseValue::Number(7.0)),
]),
)
.await
.expect("create");
let found = a
.find(&schema, &[], &QueryOptions::default())
.await
.expect("find");
assert_eq!(found.len(), 1);
assert!(matches!(found[0].get("title"), Some(ParseValue::String(s)) if s == "hello"));
assert!(matches!(found[0].get("views"), Some(ParseValue::Number(n)) if *n == 7.0));
assert!(matches!(found[0].get("objectId"), Some(ParseValue::String(s)) if s == "oid0000001"));
drop_db(&db).await;
}
#[tokio::test]
#[ignore = "requires MongoDB on 27017; run via tools/test.sh"]
async fn a_pointer_query_matches_the_stored_form() {
let (a, db) = adapter("pointerquery").await;
let schema = post_schema();
let author = ParseValue::Pointer {
class_name: "_User".into(),
object_id: "u123".into(),
};
a.create(
&schema,
&row(vec![
("objectId", ParseValue::String("oid0000002".into())),
("author", author.clone()),
]),
)
.await
.expect("create");
let found = a
.find(
&schema,
&[Constraint::equal("author", author)],
&QueryOptions::default(),
)
.await
.expect("find");
assert_eq!(
found.len(),
1,
"a pointer query must find the row it stored"
);
drop_db(&db).await;
}
#[tokio::test]
#[ignore = "requires MongoDB on 27017; run via tools/test.sh"]
async fn constraints_on_one_field_merge_into_a_range() {
let (a, db) = adapter("range").await;
let schema = post_schema();
for (i, id) in [(1.0, "a"), (5.0, "b"), (9.0, "c")] {
a.create(
&schema,
&row(vec![
("objectId", ParseValue::String(format!("oid000000{id}"))),
("views", ParseValue::Number(i)),
]),
)
.await
.expect("create");
}
let found = a
.find(
&schema,
&[
Constraint {
field: "views".into(),
comparison: Comparison::GreaterThan(ParseValue::Number(1.0)),
},
Constraint {
field: "views".into(),
comparison: Comparison::LessThan(ParseValue::Number(9.0)),
},
],
&QueryOptions::default(),
)
.await
.expect("find");
assert_eq!(
found.len(),
1,
"range must be the intersection, not the last constraint"
);
assert!(matches!(found[0].get("views"), Some(ParseValue::Number(n)) if *n == 5.0));
drop_db(&db).await;
}
#[tokio::test]
#[ignore = "requires MongoDB on 27017; run via tools/test.sh"]
async fn order_limit_and_skip() {
let (a, db) = adapter("orderlimit").await;
let schema = post_schema();
for (n, id) in [(3.0, "a"), (1.0, "b"), (2.0, "c")] {
a.create(
&schema,
&row(vec![
("objectId", ParseValue::String(format!("oid000000{id}"))),
("views", ParseValue::Number(n)),
]),
)
.await
.expect("create");
}
let opts = QueryOptions {
order: vec![("views".into(), SortDirection::Descending)],
limit: Some(2),
..Default::default()
};
let found = a.find(&schema, &[], &opts).await.expect("find");
let views: Vec<f64> = found
.iter()
.filter_map(|r| match r.get("views") {
Some(ParseValue::Number(n)) => Some(*n),
_ => None,
})
.collect();
assert_eq!(views, vec![3.0, 2.0]);
drop_db(&db).await;
}
#[tokio::test]
#[ignore = "requires MongoDB on 27017; run via tools/test.sh"]
async fn update_and_delete_report_matched_counts() {
let (a, db) = adapter("updatedelete").await;
let schema = post_schema();
a.create(
&schema,
&row(vec![
("objectId", ParseValue::String("oid0000003".into())),
("title", ParseValue::String("before".into())),
]),
)
.await
.expect("create");
let matched = a
.update(
&schema,
&[Constraint::equal(
"objectId",
ParseValue::String("oid0000003".into()),
)],
&row(vec![("title", ParseValue::String("after".into()))]),
)
.await
.expect("update");
assert_eq!(matched, 1);
let found = a
.find(&schema, &[], &QueryOptions::default())
.await
.expect("find");
assert!(matches!(found[0].get("title"), Some(ParseValue::String(s)) if s == "after"));
let missed = a
.update(
&schema,
&[Constraint::equal(
"objectId",
ParseValue::String("nope000000".into()),
)],
&row(vec![("title", ParseValue::String("x".into()))]),
)
.await
.expect("update");
assert_eq!(missed, 0);
let deleted = a
.delete(
&schema,
&[Constraint::equal(
"objectId",
ParseValue::String("oid0000003".into()),
)],
)
.await
.expect("delete");
assert_eq!(deleted, 1);
assert_eq!(a.count(&schema, &[]).await.expect("count"), 0);
drop_db(&db).await;
}
#[tokio::test]
#[ignore = "requires MongoDB on 27017; run via tools/test.sh"]
async fn a_unique_index_collision_maps_to_duplicate_value() {
let (a, db) = adapter("uniqueindex").await;
let mut schema = default_schema("_User");
schema.fields.insert("username".into(), FieldType::String);
a.ensure_unique_index("_User", &["username"], None)
.await
.expect("index");
a.create(
&schema,
&row(vec![
("objectId", ParseValue::String("u000000001".into())),
("username", ParseValue::String("alice".into())),
]),
)
.await
.expect("first create");
let err = a
.create(
&schema,
&row(vec![
("objectId", ParseValue::String("u000000002".into())),
("username", ParseValue::String("alice".into())),
]),
)
.await
.expect_err("second create must collide");
assert_eq!(err.code, ErrorCode::DuplicateValue);
assert!(
err.message.contains("username_1"),
"the auto-generated index name must appear in the error: {}",
err.message
);
drop_db(&db).await;
}
#[tokio::test]
#[ignore = "requires MongoDB on 27017; run via tools/test.sh"]
async fn schemas_round_trip_through_the_schema_collection() {
let (a, db) = adapter("schemaroundtrip").await;
let schema = post_schema();
a.upsert_schema(&schema).await.expect("upsert");
let loaded = a.all_schemas().await.expect("all_schemas");
let post = loaded
.iter()
.find(|s| s.class_name == "Post")
.expect("Post schema");
assert_eq!(post.field("title"), Some(&FieldType::String));
assert_eq!(post.field("views"), Some(&FieldType::Number));
assert_eq!(
post.field("author"),
Some(&FieldType::Pointer {
target_class: "_User".into()
}),
"a pointer's target class must survive the _SCHEMA round trip"
);
assert_eq!(post.field("ACL"), Some(&FieldType::Acl));
drop_db(&db).await;
}