#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::collections::HashMap;
use crate::codegen::generate_from_schema_file;
use crate::codegen::parser::ParsedTraitDef;
use super::support::write_temp_schema_file;
#[test]
fn generates_code_from_dsl_schema() {
let schema = r#"
use valence::prelude::*;
valence_schema! {
TestEntity {
table: "test_entity",
version: "0.1.0",
description: "Test entity for DSL parsing",
fields: [
id: { r#type: FieldType::String, primary_key: true, required: true },
name: { r#type: FieldType::String, required: true },
deleted_at: { r#type: FieldType::Datetime, required: false },
]
}
}
"#;
let path = write_temp_schema_file(schema, "dsl_schema.rs");
let generated = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new())
.expect("DSL codegen failed");
insta::assert_snapshot!(generated);
}
#[test]
fn generates_clear_method_for_optional_fields() {
let schema = r#"
use valence::prelude::*;
valence_schema! {
TestNotification {
table: "test_notification",
version: "0.1.0",
description: "Test entity with optional field for clear method",
fields: [
id: { r#type: FieldType::String, primary_key: true, required: true },
message: { r#type: FieldType::String, required: true },
read_at: { r#type: FieldType::Datetime, required: false },
]
}
}
"#;
let path = write_temp_schema_file(schema, "optional_field_schema.rs");
let generated = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new())
.expect("codegen failed");
assert!(
generated.contains("clear_read_at"),
"Expected clear_read_at method to be generated"
);
assert!(
generated.contains("pub fn clear_read_at"),
"Expected clear_read_at to be a public method"
);
assert!(
generated.contains("Option<Option<chrono::DateTime<chrono::Utc>>>"),
"Expected Option<Option<T>> for optional datetime field"
);
assert!(
generated.contains("Some(Some(value))"),
"Expected setter to use Some(Some(value)) for optional fields"
);
insta::assert_snapshot!(generated);
}
#[test]
fn generates_field_changes_and_dispatch_for_side_effects() {
let schema = r#"
use valence::prelude::*;
valence_schema! {
ScoreTracker {
table: "score_tracker",
version: "0.1.0",
description: "Test entity with side effects",
fields: [
id: { r#type: FieldType::String, primary_key: true, required: true },
user: { r#type: FieldType::Record("user"), required: true },
value: { r#type: FieldType::Integer, required: true },
],
side_effects: [ScoreNotifier, AuditLogger],
}
}
"#;
let path = write_temp_schema_file(schema, "side_effects_schema.rs");
let generated = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new())
.expect("codegen failed");
assert!(
generated.contains("pub struct ScoreTrackerFieldChanges"),
"Expected ScoreTrackerFieldChanges struct to be generated"
);
assert!(
generated.contains("pub value: valence::FieldChange<i64>"),
"Expected typed FieldChange<i64> for value field"
);
assert!(
generated.contains("pub user: valence::FieldChange<valence::RecordId>"),
"Expected typed FieldChange<RecordId> for user field"
);
assert!(
generated.contains("type FieldChanges = ScoreTrackerFieldChanges"),
"Expected FieldChanges associated type on Model impl"
);
assert!(
generated.contains("Box::new(ScoreNotifier)"),
"Expected ScoreNotifier instantiation in dispatch"
);
assert!(
generated.contains("Box::new(AuditLogger)"),
"Expected AuditLogger instantiation in dispatch"
);
assert!(
generated.contains("dyn valence::SideEffect<Self>"),
"Expected SideEffect trait object in dispatch"
);
assert!(
generated.contains("valence::MutationKind::Create"),
"Expected MutationKind::Create in generated code"
);
assert!(
generated.contains("valence::MutationKind::Update"),
"Expected MutationKind::Update in generated code"
);
assert!(
generated.contains("valence::deletion::DeletionService::create_run"),
"Expected queued deletion run creation in generated delete()"
);
assert!(
generated.contains("record_side_effect_error"),
"Expected side-effect error instrumentation in dispatch"
);
insta::assert_snapshot!(generated);
}
#[test]
fn generates_no_op_dispatch_without_side_effects() {
let schema = r#"
use valence::prelude::*;
valence_schema! {
PlainModel {
table: "plain_model",
version: "0.1.0",
description: "Model with no side effects",
fields: [
id: { r#type: FieldType::String, primary_key: true, required: true },
name: { r#type: FieldType::String, required: true },
]
}
}
"#;
let path = write_temp_schema_file(schema, "no_side_effects_schema.rs");
let generated = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new())
.expect("codegen failed");
assert!(
generated.contains("pub struct PlainModelFieldChanges"),
"Expected PlainModelFieldChanges struct"
);
assert!(
generated.contains("#[allow(unused_variables)]"),
"Expected #[allow(unused_variables)] on no-op dispatch"
);
assert!(
!generated.contains("record_side_effect_error"),
"No-op dispatch should not contain side-effect error instrumentation"
);
assert!(
!generated.contains("Box::new"),
"No-op dispatch should not contain side effect instantiations"
);
insta::assert_snapshot!(generated);
}
#[test]
fn rejects_toml_schema() {
let schema = r##"
use valence::prelude::*;
valence_schema! {
r#"
[table_properties]
name = "toml_entity"
version = "1.0.0"
description = "TOML based schema"
[privacy]
gdpr_compliant = false
deletion_tracking = true
roles = ["public"]
[mutation]
create_roles = ["system", "user"]
update_roles = ["system", "user"]
delete_roles = ["system", "admin"]
[[fields]]
name = "id"
type = "string"
primary_key = true
required = true
[[fields]]
name = "count"
type = "integer"
required = true
default = "0"
validations = ["non_negative"]
"#
}
"##;
let path = write_temp_schema_file(schema, "toml_schema.rs");
let result = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new());
assert!(result.is_err());
}
#[test]
fn test_union_join_methods_generated() {
let schema = r#"
use valence::prelude::*;
valence_schema! {
TestEntity {
table: "test_entity",
version: "0.1.0",
description: "Simple entity for union/join test",
fields: [
id: { r#type: FieldType::String, primary_key: true, required: true },
name: { r#type: FieldType::String, required: true },
]
}
}
"#;
let path = write_temp_schema_file(schema, "union_join_schema.rs");
let generated = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new())
.expect("union/join codegen failed");
assert!(
generated.contains("fn union"),
"Expected union method to be generated on query builder"
);
assert!(
generated.contains("fn join"),
"Expected join method to be generated on query builder"
);
assert!(
generated.contains("union_with"),
"Expected union_with call in generated union method"
);
assert!(
generated.contains("join_with"),
"Expected join_with call in generated join method"
);
}
#[test]
fn generates_json_as_and_currency_and_record_target() {
let schema = r#"
use valence::prelude::*;
valence_schema! {
TypedFields {
table: "typed_fields",
version: "0.1.0",
fields: [
id: { r#type: FieldType::String, primary_key: true, required: true },
payload: { r#type: FieldType::JsonAs("crate::Payload"), required: true },
panic_payload: {
r#type: FieldType::JsonAs("crate::Payload").serde_error(JsonAsSerdeError::Panic),
required: true
},
price: { r#type: FieldType::Currency, required: true },
author: {
r#type: FieldType::Record("user").target("other_crate::generated::User"),
required: true
},
blob: { r#type: FieldType::Json, required: true },
at: { r#type: FieldType::DateTime, required: true },
]
}
}
"#;
let path = write_temp_schema_file(schema, "typed_fields_schema.rs");
let generated = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new())
.expect("typed fields codegen failed");
assert!(
generated.contains("payload: crate :: Payload")
|| generated.contains("payload: crate::Payload"),
"Expected JsonAs Rust type on payload"
);
assert!(
generated.contains("serde_json::Value"),
"Expected plain Json to remain serde_json::Value"
);
assert!(
generated.contains("valence::Currency"),
"Expected Currency field type"
);
assert!(
generated.contains("where_price_code") && generated.contains("where_price_minor"),
"Expected currency subfield query methods"
);
assert!(
generated.contains("other_crate::generated::User")
|| generated.contains("other_crate :: generated :: User"),
"Expected Record.target model path in connection helpers"
);
assert!(
generated.contains("valence::datetime_unix"),
"Expected datetime unix serde module"
);
assert!(
generated.contains("JsonAsSerdeError::Panic")
|| generated.contains("valence::JsonAsSerdeError::Panic"),
"Expected Panic serde policy helper"
);
}
#[test]
fn connection_target_alias_sets_model_path() {
let schema = r#"
use valence::prelude::*;
valence_schema! {
Note {
table: "note",
version: "0.1.0",
fields: [
id: { r#type: FieldType::String, primary_key: true, required: true },
author: { r#type: FieldType::Record("user"), required: true },
],
connections: [
author: {
table: "user",
cardinality: HasOne,
required: true,
on_delete: Cascade,
target: "other_crate::generated::User",
},
],
}
}
"#;
let path = write_temp_schema_file(schema, "note_target_alias.rs");
let generated = generate_from_schema_file(&path, &HashMap::<String, ParsedTraitDef>::new())
.expect("target alias codegen failed");
assert!(
generated.contains("other_crate::generated::User")
|| generated.contains("other_crate :: generated :: User"),
"Expected target: alias to populate model_path"
);
assert!(
generated.contains("query_author"),
"Expected typed hop for cross-crate target"
);
}