use std::path::PathBuf;
use std::thread;
use gelx_core::*;
use proc_macro2::Span;
use rstest::fixture;
use rstest::rstest;
#[allow(clippy::perf)]
fn get_features() -> String {
let mut features = Vec::new();
#[cfg(feature = "query")]
features.push("query");
#[cfg(feature = "serde")]
features.push("serde");
if !features.is_empty() {
features.insert(0, "_");
}
features.join("_")
}
macro_rules! set_snapshot_suffix {
($($expr:expr),*) => {
let mut settings = insta::Settings::clone_current();
let path = format!($($expr,)*);
settings.set_snapshot_suffix(format!("{path}{}", get_features()));
let _guard = settings.bind_to_scope();
}
}
#[fixture]
pub fn testname() -> String {
thread::current()
.name()
.unwrap()
.split("::")
.last()
.unwrap()
.to_string()
}
#[rstest]
#[case::str("select 'i ❤️ gel'")]
#[case::bool("select true")]
#[case::int64("select 3")]
#[case::float64("select 314e-2")]
#[case::bigint("select 42n")]
#[case::decimal("select 42e+100n")]
#[case::uuid("select <uuid>'a5ea6360-75bd-4c20-b69c-8f317b0d2857'")]
#[case::datetime("select <datetime>'1999-03-31T15:17:00Z'")]
#[case::duration("select <duration>'45.6 seconds'")]
#[case::array("select [1, 2, 3]")]
#[case::tuple("select ('Apple', 7, true)")]
#[case::named_tuple("select (fruit := 'Apple', quantity := 3.14, fresh := true)")]
#[case::set(r#"select {"set", "of", "strings"}"#)]
#[case::empty_set("select <int64>{}")]
#[case::input_shape(
"select { my_string := RelationshipType.Follow, my_number := 42, several_numbers := {1, 2, \
3}, array := [1, 2, 3] };"
)]
#[case::input_shape_with_args(r#"select { hello := "world", custom := <str>$custom }"#)]
#[case::object_shape("select Team {**}")]
#[case::object_shape_with_args(
"select Team {**} filter .name like <str>$starts_with ++ '%' and .description like '%' ++ \
<str>$ends_with;"
)]
#[case::enums("select Account { provider }")]
#[case::types_query(TYPES_QUERY)]
#[case::bytes("select b'bina\\x01ry'")]
#[case::geometry("select ext::postgis::makepoint(1.0, 1.0)")]
#[case::geography("select <ext::postgis::geography>ext::postgis::makepoint(1.0, 1.0)")]
#[case::custom_scalar("select (insert Simple { position := <default::Position>$position }) {**};")]
#[tokio::test]
#[rustversion::attr(not(nightly), ignore = "requires nightly")]
async fn codegen_literals(testname: String, #[case] query: &str) -> GelxCoreResult<()> {
set_snapshot_suffix!("{}", testname);
let metadata = GelxMetadata::default();
let relative_path = format!("tests/compile/codegen/{testname}.rs");
let descriptor = get_descriptor(query, &metadata).await?;
let code = generate_query_token_stream(&descriptor, "example", query, &metadata, true)?;
let content = prettify(&code.to_string())?;
insta::assert_snapshot!(&content);
prepare_compile_test(&content, &relative_path).await?;
Ok(())
}
#[rstest]
#[case("insert_user")]
#[case("remove_user")]
#[tokio::test]
async fn codegen_files(#[case] path: &str) -> GelxCoreResult<()> {
set_snapshot_suffix!("{}", path);
let metadata = GelxMetadata::default();
let query_path = resolve_path(format!("queries/{path}.edgeql"), Span::call_site())?;
let relative_path = format!("tests/compile/codegen/{path}.rs");
let query = tokio::fs::read_to_string(&query_path).await?;
let descriptor = get_descriptor(&query, &metadata).await?;
let code = generate_query_token_stream(&descriptor, "example", &query, &metadata, true)?;
let content = prettify(&code.to_string())?;
insta::assert_snapshot!(&content);
prepare_compile_test(&content, &relative_path).await?;
Ok(())
}
const CRATE_DIR: &str = env!("CARGO_MANIFEST_DIR");
async fn prepare_compile_test(content: &str, relative_path: &str) -> GelxCoreResult<()> {
#[cfg(all(
feature = "builder",
feature = "query",
feature = "serde",
feature = "strum"
))]
{
let is_ci = std::env::var("CI")
.ok()
.is_some_and(|v| ["1", "true"].contains(&v.as_str()));
let path = PathBuf::from(CRATE_DIR).join(relative_path);
let generated = generate_contents(content)?;
let should_update = match tokio::fs::read_to_string(&path).await {
Ok(current) => current != generated,
Err(_) => true,
};
if should_update {
assert2::assert!(!is_ci, "attempted updating compilation tests in CI");
tokio::fs::write(&path, generated).await?;
}
}
Ok(())
}
fn generate_contents(content: &str) -> GelxCoreResult<String> {
let updated = format!("{content}\n\nfn main() {{}}");
Ok(prettify(&updated)?)
}
#[tokio::test]
async fn can_generate_enums() -> GelxCoreResult<()> {
set_snapshot_suffix!("enums");
let metadata = GelxMetadata::default();
let outputs = generate_module_outputs(&metadata).await?;
let map = outputs.to_map()?;
let value = map
.iter()
.map(|(path, content)| format!("{}\n{content}", path.display()))
.collect::<Vec<_>>()
.join("\n\n");
insta::with_settings!({filters => vec![
(r"__g::uuid::Uuid::from_bytes\(\[[0-9u8,\s]+\]\)", "Default::default()"),
]}, {
insta::assert_snapshot!(value);
});
Ok(())
}
#[tokio::test]
async fn can_generate_aliased_enums() -> GelxCoreResult<()> {
set_snapshot_suffix!("aliased_enums");
let metadata = GelxMetadata::builder()
.features(
GelxFeatures::builder()
.query("ssr")
.builder("ssr")
.strum("ssr")
.build(),
)
.build();
let outputs = generate_module_outputs(&metadata).await?;
let map = outputs.to_map()?;
let value = map
.iter()
.map(|(path, content)| format!("{}\n{content}", path.display()))
.collect::<Vec<_>>()
.join("\n\n");
insta::with_settings!({filters => vec![
(r"__g::uuid::Uuid::from_bytes\(\[[0-9u8,\s]+\]\)", "Default::default()"),
]}, {
insta::assert_snapshot!(value);
});
Ok(())
}