657 lines
21 KiB
Rust
657 lines
21 KiB
Rust
|
|
//! Integration tests for Nickel ↔ typedialog bidirectional workflows
|
||
|
|
//!
|
||
|
|
//! Tests the complete workflow:
|
||
|
|
//! 1. Nickel schema → metadata extraction
|
||
|
|
//! 2. Metadata → TOML form generation
|
||
|
|
//! 3. Form results → Nickel output serialization
|
||
|
|
//! 4. Template rendering with form results
|
||
|
|
|
||
|
|
use typedialog::nickel::{
|
||
|
|
NickelSchemaIR, NickelFieldIR, NickelType, MetadataParser, TomlGenerator,
|
||
|
|
NickelSerializer, ContractValidator, TemplateEngine,
|
||
|
|
};
|
||
|
|
use typedialog::form_parser;
|
||
|
|
use serde_json::json;
|
||
|
|
use std::collections::HashMap;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_simple_schema_roundtrip() {
|
||
|
|
// Create a simple schema
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "user_config".to_string(),
|
||
|
|
description: Some("Simple user configuration".to_string()),
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["username".to_string()],
|
||
|
|
flat_name: "username".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("User login name".to_string()),
|
||
|
|
default: Some(json!("admin")),
|
||
|
|
optional: false,
|
||
|
|
contract: Some("String | std.string.NonEmpty".to_string()),
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["email".to_string()],
|
||
|
|
flat_name: "email".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("User email address".to_string()),
|
||
|
|
default: None,
|
||
|
|
optional: true,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Generate form
|
||
|
|
let form = TomlGenerator::generate(&schema, false, false).expect("Form generation failed");
|
||
|
|
assert_eq!(form.name, "user_config");
|
||
|
|
assert_eq!(form.fields.len(), 2);
|
||
|
|
|
||
|
|
// Verify form fields have nickel metadata
|
||
|
|
assert_eq!(form.fields[0].nickel_contract, Some("String | std.string.NonEmpty".to_string()));
|
||
|
|
assert_eq!(form.fields[0].nickel_path, Some(vec!["username".to_string()]));
|
||
|
|
|
||
|
|
// Create form results
|
||
|
|
let mut results = HashMap::new();
|
||
|
|
results.insert("username".to_string(), json!("alice"));
|
||
|
|
results.insert("email".to_string(), json!("alice@example.com"));
|
||
|
|
|
||
|
|
// Serialize to Nickel
|
||
|
|
let nickel_output = NickelSerializer::serialize(&results, &schema)
|
||
|
|
.expect("Serialization failed");
|
||
|
|
|
||
|
|
// Verify output contains expected content
|
||
|
|
assert!(nickel_output.contains("alice"));
|
||
|
|
assert!(nickel_output.contains("alice@example.com"));
|
||
|
|
assert!(nickel_output.contains("String | std.string.NonEmpty"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_nested_schema_with_flatten() {
|
||
|
|
// Create schema with nested structure
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "server_config".to_string(),
|
||
|
|
description: None,
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["server".to_string(), "hostname".to_string()],
|
||
|
|
flat_name: "server_hostname".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("Server hostname".to_string()),
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: Some("server".to_string()),
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["server".to_string(), "port".to_string()],
|
||
|
|
flat_name: "server_port".to_string(),
|
||
|
|
nickel_type: NickelType::Number,
|
||
|
|
doc: Some("Server port".to_string()),
|
||
|
|
default: Some(json!(8080)),
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: Some("server".to_string()),
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["database".to_string(), "host".to_string()],
|
||
|
|
flat_name: "database_host".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("Database host".to_string()),
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: Some("database".to_string()),
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Generate form with grouping
|
||
|
|
let form = TomlGenerator::generate(&schema, false, true)
|
||
|
|
.expect("Form generation failed");
|
||
|
|
|
||
|
|
// Verify groups are created
|
||
|
|
assert!(form.items.len() > 0);
|
||
|
|
|
||
|
|
// Verify fields have groups
|
||
|
|
let server_hostname = form.fields.iter().find(|f| f.name == "server_hostname").unwrap();
|
||
|
|
assert_eq!(server_hostname.group, Some("server".to_string()));
|
||
|
|
|
||
|
|
// Create results
|
||
|
|
let mut results = HashMap::new();
|
||
|
|
results.insert("server_hostname".to_string(), json!("api.example.com"));
|
||
|
|
results.insert("server_port".to_string(), json!(9000));
|
||
|
|
results.insert("database_host".to_string(), json!("db.example.com"));
|
||
|
|
|
||
|
|
// Serialize and verify nested structure is restored
|
||
|
|
let nickel_output = NickelSerializer::serialize(&results, &schema)
|
||
|
|
.expect("Serialization failed");
|
||
|
|
|
||
|
|
assert!(nickel_output.contains("server"));
|
||
|
|
assert!(nickel_output.contains("database"));
|
||
|
|
assert!(nickel_output.contains("api.example.com"));
|
||
|
|
assert!(nickel_output.contains("db.example.com"));
|
||
|
|
assert!(nickel_output.contains("9000"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_array_field_serialization() {
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "array_config".to_string(),
|
||
|
|
description: None,
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["tags".to_string()],
|
||
|
|
flat_name: "tags".to_string(),
|
||
|
|
nickel_type: NickelType::Array(Box::new(NickelType::String)),
|
||
|
|
doc: Some("Configuration tags".to_string()),
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut results = HashMap::new();
|
||
|
|
results.insert("tags".to_string(), json!(["prod", "critical", "network"]));
|
||
|
|
|
||
|
|
let nickel_output = NickelSerializer::serialize(&results, &schema)
|
||
|
|
.expect("Serialization failed");
|
||
|
|
|
||
|
|
assert!(nickel_output.contains("prod"));
|
||
|
|
assert!(nickel_output.contains("critical"));
|
||
|
|
assert!(nickel_output.contains("network"));
|
||
|
|
assert!(nickel_output.contains("["));
|
||
|
|
assert!(nickel_output.contains("]"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_contract_validation_non_empty_string() {
|
||
|
|
// Valid non-empty string
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!("hello"),
|
||
|
|
"String | std.string.NonEmpty",
|
||
|
|
);
|
||
|
|
assert!(result.is_ok());
|
||
|
|
|
||
|
|
// Empty string should fail
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!(""),
|
||
|
|
"String | std.string.NonEmpty",
|
||
|
|
);
|
||
|
|
assert!(result.is_err());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_contract_validation_number_range() {
|
||
|
|
// Valid number in range
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!(50),
|
||
|
|
"Number | std.number.between 0 100",
|
||
|
|
);
|
||
|
|
assert!(result.is_ok());
|
||
|
|
|
||
|
|
// Number out of range
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!(150),
|
||
|
|
"Number | std.number.between 0 100",
|
||
|
|
);
|
||
|
|
assert!(result.is_err());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_contract_validation_string_length() {
|
||
|
|
// Valid length
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!("hello"),
|
||
|
|
"String | std.string.length.min 3",
|
||
|
|
);
|
||
|
|
assert!(result.is_ok());
|
||
|
|
|
||
|
|
// Too short
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!("hi"),
|
||
|
|
"String | std.string.length.min 3",
|
||
|
|
);
|
||
|
|
assert!(result.is_err());
|
||
|
|
|
||
|
|
// Valid max length
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!("hi"),
|
||
|
|
"String | std.string.length.max 5",
|
||
|
|
);
|
||
|
|
assert!(result.is_ok());
|
||
|
|
|
||
|
|
// Too long
|
||
|
|
let result = ContractValidator::validate(
|
||
|
|
&json!("hello world"),
|
||
|
|
"String | std.string.length.max 5",
|
||
|
|
);
|
||
|
|
assert!(result.is_err());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_form_definition_from_schema_ir() {
|
||
|
|
// Create schema
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "test_form".to_string(),
|
||
|
|
description: Some("Test form".to_string()),
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["name".to_string()],
|
||
|
|
flat_name: "name".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("Your name".to_string()),
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: Some("String | std.string.NonEmpty".to_string()),
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Generate form
|
||
|
|
let form = TomlGenerator::generate(&schema, false, false)
|
||
|
|
.expect("Form generation failed");
|
||
|
|
|
||
|
|
// Convert to TOML
|
||
|
|
let toml_str = toml::to_string_pretty(&form)
|
||
|
|
.expect("TOML serialization failed");
|
||
|
|
|
||
|
|
// Parse back
|
||
|
|
let parsed_form: form_parser::FormDefinition = toml::from_str(&toml_str)
|
||
|
|
.expect("TOML parsing failed");
|
||
|
|
|
||
|
|
// Verify round-trip
|
||
|
|
assert_eq!(parsed_form.name, "test_form");
|
||
|
|
assert_eq!(parsed_form.fields.len(), 1);
|
||
|
|
assert_eq!(parsed_form.fields[0].name, "name");
|
||
|
|
assert_eq!(
|
||
|
|
parsed_form.fields[0].nickel_contract,
|
||
|
|
Some("String | std.string.NonEmpty".to_string())
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_metadata_extraction_with_optional_fields() {
|
||
|
|
// Parse JSON metadata (simulating nickel query output)
|
||
|
|
let metadata = json!({
|
||
|
|
"name": {
|
||
|
|
"doc": "User full name",
|
||
|
|
"type": "String",
|
||
|
|
"default": "Anonymous"
|
||
|
|
},
|
||
|
|
"age": {
|
||
|
|
"doc": "User age in years",
|
||
|
|
"type": "Number",
|
||
|
|
"optional": true
|
||
|
|
},
|
||
|
|
"active": {
|
||
|
|
"type": "Bool",
|
||
|
|
"optional": false,
|
||
|
|
"default": true
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
let schema = MetadataParser::parse(metadata)
|
||
|
|
.expect("Metadata parsing failed");
|
||
|
|
|
||
|
|
// Verify fields
|
||
|
|
assert_eq!(schema.fields.len(), 3);
|
||
|
|
|
||
|
|
// Check optional flags
|
||
|
|
let name_field = schema.fields.iter().find(|f| f.flat_name == "name").unwrap();
|
||
|
|
assert!(!name_field.optional);
|
||
|
|
|
||
|
|
let age_field = schema.fields.iter().find(|f| f.flat_name == "age").unwrap();
|
||
|
|
assert!(age_field.optional);
|
||
|
|
|
||
|
|
let active_field = schema.fields.iter().find(|f| f.flat_name == "active").unwrap();
|
||
|
|
assert!(!active_field.optional);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_type_mapping_all_types() {
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "types_test".to_string(),
|
||
|
|
description: None,
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["str_field".to_string()],
|
||
|
|
flat_name: "str_field".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: None,
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["num_field".to_string()],
|
||
|
|
flat_name: "num_field".to_string(),
|
||
|
|
nickel_type: NickelType::Number,
|
||
|
|
doc: None,
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["bool_field".to_string()],
|
||
|
|
flat_name: "bool_field".to_string(),
|
||
|
|
nickel_type: NickelType::Bool,
|
||
|
|
doc: None,
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["array_field".to_string()],
|
||
|
|
flat_name: "array_field".to_string(),
|
||
|
|
nickel_type: NickelType::Array(Box::new(NickelType::String)),
|
||
|
|
doc: None,
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
let form = TomlGenerator::generate(&schema, false, false)
|
||
|
|
.expect("Form generation failed");
|
||
|
|
|
||
|
|
// Verify type mapping
|
||
|
|
assert_eq!(form.fields[0].field_type, form_parser::FieldType::Text);
|
||
|
|
assert_eq!(form.fields[1].field_type, form_parser::FieldType::Custom);
|
||
|
|
assert_eq!(form.fields[1].custom_type, Some("f64".to_string()));
|
||
|
|
assert_eq!(form.fields[2].field_type, form_parser::FieldType::Confirm);
|
||
|
|
assert_eq!(form.fields[3].field_type, form_parser::FieldType::Editor);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_enum_options_extraction_from_doc() {
|
||
|
|
let field = NickelFieldIR {
|
||
|
|
path: vec!["status".to_string()],
|
||
|
|
flat_name: "status".to_string(),
|
||
|
|
nickel_type: NickelType::Array(Box::new(NickelType::String)),
|
||
|
|
doc: Some("Status selection. Options: pending, active, completed".to_string()),
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
};
|
||
|
|
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "test".to_string(),
|
||
|
|
description: None,
|
||
|
|
fields: vec![field],
|
||
|
|
};
|
||
|
|
|
||
|
|
let form = TomlGenerator::generate(&schema, false, false)
|
||
|
|
.expect("Form generation failed");
|
||
|
|
|
||
|
|
// Verify options extracted
|
||
|
|
let status_field = &form.fields[0];
|
||
|
|
assert_eq!(
|
||
|
|
status_field.options,
|
||
|
|
Some(vec![
|
||
|
|
"pending".to_string(),
|
||
|
|
"active".to_string(),
|
||
|
|
"completed".to_string(),
|
||
|
|
])
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_template_rendering_simple() {
|
||
|
|
#[cfg(feature = "templates")]
|
||
|
|
{
|
||
|
|
let mut engine = TemplateEngine::new();
|
||
|
|
let mut values = HashMap::new();
|
||
|
|
values.insert("hostname".to_string(), json!("server1"));
|
||
|
|
values.insert("port".to_string(), json!(8080));
|
||
|
|
|
||
|
|
let template = r#"
|
||
|
|
server {
|
||
|
|
hostname : String = "{{ hostname }}"
|
||
|
|
port : Number = {{ port }}
|
||
|
|
}
|
||
|
|
"#;
|
||
|
|
|
||
|
|
let result = engine.render_str(template, &values);
|
||
|
|
assert!(result.is_ok());
|
||
|
|
|
||
|
|
let output = result.unwrap();
|
||
|
|
assert!(output.contains("server1"));
|
||
|
|
assert!(output.contains("8080"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_template_rendering_with_loop() {
|
||
|
|
#[cfg(feature = "templates")]
|
||
|
|
{
|
||
|
|
let mut engine = TemplateEngine::new();
|
||
|
|
let mut values = HashMap::new();
|
||
|
|
values.insert("servers".to_string(), json!([
|
||
|
|
{"name": "web-01", "ip": "192.168.1.10"},
|
||
|
|
{"name": "web-02", "ip": "192.168.1.11"},
|
||
|
|
]));
|
||
|
|
|
||
|
|
let template = r#"servers = [
|
||
|
|
{% for server in servers %}
|
||
|
|
{ name = "{{ server.name }}", ip = "{{ server.ip }}" },
|
||
|
|
{% endfor %}
|
||
|
|
]"#;
|
||
|
|
|
||
|
|
let result = engine.render_str(template, &values);
|
||
|
|
assert!(result.is_ok());
|
||
|
|
|
||
|
|
let output = result.unwrap();
|
||
|
|
assert!(output.contains("web-01"));
|
||
|
|
assert!(output.contains("web-02"));
|
||
|
|
assert!(output.contains("192.168.1.10"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_template_rendering_with_conditional() {
|
||
|
|
#[cfg(feature = "templates")]
|
||
|
|
{
|
||
|
|
let mut engine = TemplateEngine::new();
|
||
|
|
let mut values = HashMap::new();
|
||
|
|
values.insert("production".to_string(), json!(true));
|
||
|
|
values.insert("replicas".to_string(), json!(3));
|
||
|
|
|
||
|
|
let template = r#"{% if production %}
|
||
|
|
replicas : Number = {{ replicas }}
|
||
|
|
{% else %}
|
||
|
|
replicas : Number = 1
|
||
|
|
{% endif %}"#;
|
||
|
|
|
||
|
|
let result = engine.render_str(template, &values);
|
||
|
|
assert!(result.is_ok());
|
||
|
|
|
||
|
|
let output = result.unwrap();
|
||
|
|
assert!(output.contains("replicas"));
|
||
|
|
assert!(output.contains("3"));
|
||
|
|
assert!(!output.contains("= 1"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_full_workflow_integration() {
|
||
|
|
// Create a realistic schema
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "app_config".to_string(),
|
||
|
|
description: Some("Application configuration".to_string()),
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["app".to_string(), "name".to_string()],
|
||
|
|
flat_name: "app_name".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("Application name".to_string()),
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: Some("String | std.string.NonEmpty".to_string()),
|
||
|
|
group: Some("app".to_string()),
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["app".to_string(), "version".to_string()],
|
||
|
|
flat_name: "app_version".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("Application version (e.g., 1.0.0)".to_string()),
|
||
|
|
default: Some(json!("1.0.0")),
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: Some("app".to_string()),
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["server".to_string(), "host".to_string()],
|
||
|
|
flat_name: "server_host".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: Some("Server hostname".to_string()),
|
||
|
|
default: Some(json!("localhost")),
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: Some("server".to_string()),
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["server".to_string(), "port".to_string()],
|
||
|
|
flat_name: "server_port".to_string(),
|
||
|
|
nickel_type: NickelType::Number,
|
||
|
|
doc: Some("Server port".to_string()),
|
||
|
|
default: Some(json!(8000)),
|
||
|
|
optional: false,
|
||
|
|
contract: Some("Number | std.number.between 1 65535".to_string()),
|
||
|
|
group: Some("server".to_string()),
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Step 1: Generate form
|
||
|
|
let form = TomlGenerator::generate(&schema, false, true)
|
||
|
|
.expect("Form generation failed");
|
||
|
|
assert_eq!(form.fields.len(), 4);
|
||
|
|
|
||
|
|
// Step 2: Serialize to TOML and parse back
|
||
|
|
let toml_str = toml::to_string_pretty(&form)
|
||
|
|
.expect("TOML serialization failed");
|
||
|
|
let parsed_form: form_parser::FormDefinition = toml::from_str(&toml_str)
|
||
|
|
.expect("TOML parsing failed");
|
||
|
|
assert_eq!(parsed_form.fields.len(), 4);
|
||
|
|
|
||
|
|
// Step 3: Create form results
|
||
|
|
let mut results = HashMap::new();
|
||
|
|
results.insert("app_name".to_string(), json!("MyApp"));
|
||
|
|
results.insert("app_version".to_string(), json!("2.0.0"));
|
||
|
|
results.insert("server_host".to_string(), json!("0.0.0.0"));
|
||
|
|
results.insert("server_port".to_string(), json!(3000));
|
||
|
|
|
||
|
|
// Step 4: Validate contracts
|
||
|
|
assert!(ContractValidator::validate(&json!("MyApp"), "String | std.string.NonEmpty").is_ok());
|
||
|
|
assert!(ContractValidator::validate(&json!(3000), "Number | std.number.between 1 65535").is_ok());
|
||
|
|
|
||
|
|
// Step 5: Serialize to Nickel
|
||
|
|
let nickel_output = NickelSerializer::serialize(&results, &schema)
|
||
|
|
.expect("Serialization failed");
|
||
|
|
|
||
|
|
// Step 6: Verify output
|
||
|
|
assert!(nickel_output.contains("MyApp"));
|
||
|
|
assert!(nickel_output.contains("2.0.0"));
|
||
|
|
assert!(nickel_output.contains("0.0.0.0"));
|
||
|
|
assert!(nickel_output.contains("3000"));
|
||
|
|
assert!(nickel_output.contains("String | std.string.NonEmpty"));
|
||
|
|
assert!(nickel_output.contains("Number | std.number.between 1 65535"));
|
||
|
|
|
||
|
|
// Verify nested structure
|
||
|
|
assert!(nickel_output.contains("app"));
|
||
|
|
assert!(nickel_output.contains("server"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_optional_fields_handling() {
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "optional_test".to_string(),
|
||
|
|
description: None,
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["required_field".to_string()],
|
||
|
|
flat_name: "required_field".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: None,
|
||
|
|
default: None,
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["optional_field".to_string()],
|
||
|
|
flat_name: "optional_field".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: None,
|
||
|
|
default: None,
|
||
|
|
optional: true,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
let form = TomlGenerator::generate(&schema, false, false)
|
||
|
|
.expect("Form generation failed");
|
||
|
|
|
||
|
|
// Check required field
|
||
|
|
let required = form.fields.iter().find(|f| f.name == "required_field").unwrap();
|
||
|
|
assert_eq!(required.required, Some(true));
|
||
|
|
|
||
|
|
// Check optional field
|
||
|
|
let optional = form.fields.iter().find(|f| f.name == "optional_field").unwrap();
|
||
|
|
assert_eq!(optional.required, Some(false));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_defaults_preservation() {
|
||
|
|
let schema = NickelSchemaIR {
|
||
|
|
name: "defaults_test".to_string(),
|
||
|
|
description: None,
|
||
|
|
fields: vec![
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["string_with_default".to_string()],
|
||
|
|
flat_name: "string_with_default".to_string(),
|
||
|
|
nickel_type: NickelType::String,
|
||
|
|
doc: None,
|
||
|
|
default: Some(json!("default_value")),
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
NickelFieldIR {
|
||
|
|
path: vec!["number_with_default".to_string()],
|
||
|
|
flat_name: "number_with_default".to_string(),
|
||
|
|
nickel_type: NickelType::Number,
|
||
|
|
doc: None,
|
||
|
|
default: Some(json!(42)),
|
||
|
|
optional: false,
|
||
|
|
contract: None,
|
||
|
|
group: None,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
let form = TomlGenerator::generate(&schema, false, false)
|
||
|
|
.expect("Form generation failed");
|
||
|
|
|
||
|
|
// Check defaults are preserved
|
||
|
|
let string_field = form.fields.iter().find(|f| f.name == "string_with_default").unwrap();
|
||
|
|
assert_eq!(string_field.default, Some("default_value".to_string()));
|
||
|
|
|
||
|
|
let number_field = form.fields.iter().find(|f| f.name == "number_with_default").unwrap();
|
||
|
|
assert_eq!(number_field.default, Some("42".to_string()));
|
||
|
|
}
|