use audb::parser::GoldParser;
use audb::validation::AstValidator;
use std::path::Path;
#[test]
fn test_parse_and_validate_complete_example() {
let input = r#"
/// User schema with authentication fields
@schema User {
id: EntityId
name: String
email: String
created_at: Timestamp
}
/// Post schema for blog content
@schema Post {
id: EntityId
title: String
content: String
author_id: EntityId
created_at: Timestamp
}
/// Get a user by ID
@query get_user(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
/// List all posts
@query list_posts(limit: Integer) -> Vec<Post> {
language = "hyperql"
---
SELECT * FROM posts ORDER BY created_at DESC LIMIT :limit
---
}
/// User endpoint
@endpoint GET "/api/users/:id" {
query = get_user
auth = true
}
/// Posts endpoint
@endpoint GET "/api/posts" {
query = list_posts
auth = false
}
/// Database configuration
@config database {
path = "./data"
engine = "manifold"
}
"#;
let result = GoldParser::parse_str(input, Path::new("test.au"));
assert!(result.is_ok(), "Parse failed: {:?}", result.err());
let gold_file = result.unwrap();
assert_eq!(gold_file.blocks.len(), 7);
let validator = AstValidator::new();
let validation_result = validator.validate(&gold_file);
assert!(
validation_result.is_ok(),
"Validation failed: {:?}",
validation_result.err()
);
let report = validation_result.unwrap();
assert!(report.is_valid());
assert_eq!(report.errors.len(), 0);
}
#[test]
fn test_validation_catches_undefined_schema_reference() {
let input = r#"
@query get_user(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
assert!(result.is_ok());
let report = result.unwrap();
assert!(!report.warnings.is_empty());
}
#[test]
fn test_validation_catches_undefined_query_reference() {
let input = r#"
@endpoint GET "/api/users" {
query = get_users
auth = false
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
assert!(result.is_err());
}
#[test]
fn test_validation_catches_duplicate_schemas() {
let input = r#"
@schema User {
id: EntityId
name: String
}
@schema User {
id: EntityId
email: String
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
assert!(result.is_err());
}
#[test]
fn test_validation_catches_duplicate_field_names() {
let input = r#"
@schema User {
id: EntityId
id: String
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
assert!(result.is_err());
}
#[test]
fn test_validation_catches_duplicate_endpoints() {
let input = r#"
@query get_user(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users
---
}
@endpoint GET "/api/users" {
query = get_user
auth = false
}
@endpoint GET "/api/users" {
query = get_user
auth = true
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
assert!(result.is_err());
}
#[test]
fn test_strict_mode_treats_warnings_as_errors() {
let input = r#"
@query get_user(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
assert!(result.is_ok());
let strict_validator = AstValidator::strict();
let result = strict_validator.validate(&gold_file);
assert!(result.is_err());
}
#[test]
fn test_parse_and_validate_multi_language_queries() {
let input = r#"
@schema User {
id: EntityId
name: String
}
@query get_user_hyperql(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
@query get_user_sql(id: EntityId) -> User {
language = "sql"
---
SELECT * FROM users WHERE id = $1
---
}
@query get_user_cypher(id: EntityId) -> User {
language = "cypher"
---
MATCH (u:User {id: $id}) RETURN u
---
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
assert_eq!(gold_file.blocks.len(), 4);
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
assert!(result.is_ok());
}
#[test]
fn test_parse_complex_return_types() {
let input = r#"
@schema User {
id: EntityId
name: String
}
@query list_users() -> Vec<User> {
language = "hyperql"
---
SELECT * FROM users
---
}
@query get_user_option(id: EntityId) -> Option<User> {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
"#;
let gold_file = match GoldParser::parse_str(input, Path::new("test.au")) {
Ok(f) => f,
Err(e) => {
eprintln!("Parse error: {}", e);
panic!("Failed to parse");
}
};
let validator = AstValidator::new();
let result = validator.validate(&gold_file);
if let Err(e) = &result {
eprintln!("Validation error: {:?}", e);
}
match result {
Ok(report) => {
if !report.errors.is_empty() || !report.warnings.is_empty() {
eprintln!("Validation report:");
for error in &report.errors {
eprintln!(" ERROR: {}", error);
}
for warning in &report.warnings {
eprintln!(" WARNING: {}", warning);
}
}
assert!(report.errors.is_empty(), "Validation errors found");
}
Err(e) => {
eprintln!("Validation failed: {:?}", e);
panic!("Validation failed");
}
}
}
#[test]
fn test_project_from_gold_files() {
use audb::model::Project;
let input = r#"
@config project {
name = "test-app"
version = "0.1.0"
}
@config database {
engine = "manifold"
path = "./data"
}
@schema User {
id: EntityId
name: String
email: String
}
@query get_user(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
@endpoint GET "/api/users/:id" {
query = get_user
auth = true
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let validator = AstValidator::new();
validator.validate(&gold_file).unwrap();
let result = Project::from_gold_files(vec![gold_file]);
assert!(
result.is_ok(),
"Failed to create project: {:?}",
result.err()
);
let project = result.unwrap();
assert_eq!(project.metadata.name, "test-app");
assert_eq!(project.metadata.version, "0.1.0");
assert_eq!(project.database.engine, "manifold");
assert_eq!(project.database.path, "./data");
assert_eq!(project.schemas.len(), 1);
assert!(project.schemas.contains_key("User"));
let user_schema = project.get_schema("User").unwrap();
assert_eq!(user_schema.fields.len(), 3);
assert_eq!(project.queries.len(), 1);
assert!(project.queries.contains_key("get_user"));
let query = project.get_query("get_user").unwrap();
assert_eq!(query.params.len(), 1);
assert_eq!(query.return_type, "User");
assert_eq!(project.endpoints.len(), 1);
assert_eq!(project.endpoints[0].method, "GET");
assert_eq!(project.endpoints[0].path, "/api/users/:id");
}
#[test]
fn test_project_from_multiple_files() {
use audb::model::Project;
let schemas_file = r#"
@schema User {
id: EntityId
name: String
}
@schema Post {
id: EntityId
title: String
author_id: EntityId
}
"#;
let queries_file = r#"
@query get_user(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
@query list_posts() -> Vec<Post> {
language = "hyperql"
---
SELECT * FROM posts
---
}
"#;
let gold_file1 = GoldParser::parse_str(schemas_file, Path::new("schemas.au")).unwrap();
let gold_file2 = GoldParser::parse_str(queries_file, Path::new("queries.au")).unwrap();
let project = Project::from_gold_files(vec![gold_file1, gold_file2]).unwrap();
assert_eq!(project.schemas.len(), 2);
assert_eq!(project.queries.len(), 2);
}
#[test]
fn test_project_validation() {
use audb::model::Project;
let input = r#"
@schema User {
id: EntityId
name: String
}
@query get_user(id: EntityId) -> User {
language = "hyperql"
---
SELECT * FROM users WHERE id = :id
---
}
@endpoint GET "/api/users/:id" {
query = get_user
auth = false
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let project = Project::from_gold_files(vec![gold_file]).unwrap();
let result = project.validate();
assert!(result.is_ok());
}
#[test]
fn test_project_validation_undefined_query_reference() {
use audb::model::Project;
let input = r#"
@endpoint GET "/api/users" {
query = undefined_query
auth = false
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let project = Project::from_gold_files(vec![gold_file]).unwrap();
let result = project.validate();
assert!(result.is_err());
}
#[test]
fn test_dependency_management() {
use audb::model::{DependencySpec, Project};
let mut project = Project::new("test-app".to_string());
project.add_dependency(DependencySpec::new("serde").version("1.0"));
project.add_dependency(
DependencySpec::new("tokio")
.version("1.0")
.features(vec!["full"]),
);
project.add_dependency(DependencySpec::new("anyhow"));
let deps = project.list_dependencies();
assert_eq!(deps.len(), 3);
assert!(deps.contains(&"serde"));
assert!(deps.contains(&"tokio"));
assert!(deps.contains(&"anyhow"));
project.remove_dependency("anyhow");
assert_eq!(project.list_dependencies().len(), 2);
}
#[test]
fn test_json_schema_format() {
use audb::model::Project;
let input = r#"
@schema User {
format = "json_schema"
---
{
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"email": {"type": "string"},
"age": {"type": "integer"},
"active": {"type": "boolean"}
},
"required": ["id", "name", "email"]
}
---
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let project = Project::from_gold_files(vec![gold_file]).unwrap();
let schema = project.get_schema("User").unwrap();
assert_eq!(schema.fields.len(), 5);
let id_field = schema.get_field("id").unwrap();
assert!(!id_field.nullable);
let age_field = schema.get_field("age").unwrap();
assert!(age_field.nullable); }
#[test]
fn test_typescript_format() {
use audb::model::Project;
let input = r#"
@schema Post {
format = "typescript"
---
interface Post {
id: string;
title: string;
content: string;
author_id: string;
published: boolean;
created_at: Date;
tags?: string[];
}
---
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let project = Project::from_gold_files(vec![gold_file]).unwrap();
let schema = project.get_schema("Post").unwrap();
assert_eq!(schema.fields.len(), 7);
let tags_field = schema.get_field("tags").unwrap();
assert!(tags_field.nullable);
let published_field = schema.get_field("published").unwrap();
assert!(!published_field.nullable); }
#[test]
fn test_mixed_schema_formats() {
use audb::model::Project;
let input = r#"
@schema User {
id: EntityId
name: String
}
@schema Profile {
format = "json_schema"
---
{
"type": "object",
"properties": {
"bio": {"type": "string"},
"avatar": {"type": "string"}
}
}
---
}
@schema Settings {
format = "typescript"
---
interface Settings {
theme: string;
notifications: boolean;
}
---
}
"#;
let gold_file = GoldParser::parse_str(input, Path::new("test.au")).unwrap();
let project = Project::from_gold_files(vec![gold_file]).unwrap();
assert_eq!(project.schemas.len(), 3);
assert!(project.get_schema("User").is_some());
assert!(project.get_schema("Profile").is_some());
assert!(project.get_schema("Settings").is_some());
}