Skip to content

Commit

Permalink
Unit and property tests for sampling and type unification (#10)
Browse files Browse the repository at this point in the history
* Remove TypeUnificationResult from return type of unify_schema

* Add some tests

* More property tests

* Add identity tests

* Add property test for object type unification

* Add unit test for make_object_type

* Fix type unification bug

* More unit tests

* Make clippy happy
  • Loading branch information
dmoverton committed Mar 21, 2024
1 parent 66ded02 commit b95d249
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 18 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ serde_json = { version = "1.0.113", features = ["raw_value"] }
thiserror = "1.0.57"
tokio = { version = "1.36.0", features = ["full"] }
these = "2.0.0"

[dev-dependencies]
proptest = "1"
11 changes: 11 additions & 0 deletions crates/cli/proptest-regressions/introspection/type_unification.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 45028da671f86113f58b8ec86468ec593b8e33488eecb154950098054ee15675 # shrinks to c = TypeUnificationContext { object_type_name: "", field_name: "" }, t = ArrayOf(Scalar(Null))
cc e7368f0503761c52e2ce47fa2e64454ecd063f2e019c511759162d0be049e665 # shrinks to c = TypeUnificationContext { object_type_name: "", field_name: "" }, t = Nullable(Nullable(Scalar(Double)))
cc bd6f440b7ea7e51d8c369e802b8cbfbc0c3f140c01cd6b54d9c61e6d84d7e77d # shrinks to c = TypeUnificationContext { object_type_name: "", field_name: "" }, t = Nullable(Scalar(Null))
cc d16279848ea51c4be376436423d342afd077a737efcab03ba2d29d5a0dee9df2 # shrinks to left = {"": Scalar(Double)}, right = {"": Scalar(Decimal)}, shared = {}
cc fc85c97eeccb12e144f548fe65fd262d4e7b1ec9c799be69fd30535aa032e26d # shrinks to ta = Nullable(Scalar(Null)), tb = Nullable(Scalar(Undefined))
101 changes: 100 additions & 1 deletion crates/cli/src/introspection/sampling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub async fn sample_schema_from_db(
let collection_name = collection_spec.name;
let collection_schema =
sample_schema_from_collection(&collection_name, sample_size, config).await?;
schema = unify_schema(schema, collection_schema)?;
schema = unify_schema(schema, collection_schema);
}
Ok(schema)
}
Expand Down Expand Up @@ -161,3 +161,102 @@ fn make_field_type(
Bson::DbPointer(_) => scalar(DbPointer),
}
}

#[cfg(test)]
mod tests {
use configuration::schema::{ObjectField, ObjectType, Type};
use mongodb::bson::doc;
use mongodb_support::BsonScalarType;

use crate::introspection::type_unification::{TypeUnificationContext, TypeUnificationError};

use super::make_object_type;

#[test]
fn simple_doc() -> Result<(), anyhow::Error> {
let object_name = "foo";
let doc = doc! {"my_int": 1, "my_string": "two"};
let result = make_object_type(object_name, &doc);

let expected = Ok(vec![ObjectType {
name: object_name.to_owned(),
fields: vec![
ObjectField {
name: "my_int".to_owned(),
r#type: Type::Scalar(BsonScalarType::Int),
description: None,
},
ObjectField {
name: "my_string".to_owned(),
r#type: Type::Scalar(BsonScalarType::String),
description: None,
},
],
description: None,
}]);

assert_eq!(expected, result);

Ok(())
}

#[test]
fn array_of_objects() -> Result<(), anyhow::Error> {
let object_name = "foo";
let doc = doc! {"my_array": [{"foo": 42, "bar": ""}, {"bar": "wut", "baz": 3.77}]};
let result = make_object_type(object_name, &doc);

let expected = Ok(vec![
ObjectType {
name: "foo_my_array".to_owned(),
fields: vec![
ObjectField {
name: "foo".to_owned(),
r#type: Type::Nullable(Box::new(Type::Scalar(BsonScalarType::Int))),
description: None,
},
ObjectField {
name: "bar".to_owned(),
r#type: Type::Scalar(BsonScalarType::String),
description: None,
},
ObjectField {
name: "baz".to_owned(),
r#type: Type::Nullable(Box::new(Type::Scalar(BsonScalarType::Double))),
description: None,
},
],
description: None,
},
ObjectType {
name: object_name.to_owned(),
fields: vec![ObjectField {
name: "my_array".to_owned(),
r#type: Type::ArrayOf(Box::new(Type::Object("foo_my_array".to_owned()))),
description: None,
}],
description: None,
},
]);

assert_eq!(expected, result);

Ok(())
}

#[test]
fn non_unifiable_array_of_objects() -> Result<(), anyhow::Error> {
let object_name = "foo";
let doc = doc! {"my_array": [{"foo": 42, "bar": ""}, {"bar": 17, "baz": 3.77}]};
let result = make_object_type(object_name, &doc);

let expected = Err(TypeUnificationError::ScalarType(
TypeUnificationContext::new("foo_my_array", "bar"),
BsonScalarType::String,
BsonScalarType::Int,
));
assert_eq!(expected, result);

Ok(())
}
}
Loading

0 comments on commit b95d249

Please sign in to comment.