//! Comprehensive tests for the el-arch architectural checker. use crate::{ArchChecker, ArchDiagnostic, Severity}; // ── Test helpers ────────────────────────────────────────────────────────────── fn check(src: &str) -> Vec { let tokens = el_lexer::tokenize(src).expect("lex failed"); let prog = el_parser::parse(tokens, src.to_string()).expect("parse failed"); ArchChecker::new().check(&prog) } fn errors(src: &str) -> Vec { check(src).into_iter().filter(|d| d.severity == Severity::Error).collect() } fn warnings(src: &str) -> Vec { check(src).into_iter().filter(|d| d.severity == Severity::Warning).collect() } fn has_rule(diags: &[ArchDiagnostic], rule: &str) -> bool { diags.iter().any(|d| d.rule == rule) } // ── 1. VBD-001: @accessor calling @manager → error ────────────────────────── #[test] fn test_accessor_calls_manager_error() { let src = r#" @manager fn orchestrate(x: String) -> String { return x } @accessor fn fetch_user(id: String) -> String { return orchestrate(id) } "#; let errs = errors(src); assert!(!errs.is_empty(), "expected error when @accessor calls @manager"); assert!(has_rule(&errs, "VBD-001"), "expected VBD-001 rule"); } // ── 2. @accessor calling @accessor → no VBD-001 error ──────────────────────── #[test] fn test_accessor_calls_accessor_no_error() { let src = r#" @accessor fn get_name(id: String) -> String { return id } @accessor fn get_user(id: String) -> String { return get_name(id) } "#; let errs = errors(src); let vbd001: Vec<_> = errs.iter().filter(|d| d.rule == "VBD-001").collect(); assert!(vbd001.is_empty(), "accessor->accessor should not trigger VBD-001"); } // ── 3. @experience calling @experience → VBD-002 error ─────────────────────── #[test] fn test_experience_calls_experience_error() { let src = r#" @experience fn checkout(cart: String) -> Result { return cart } @experience fn payment(amount: String) -> Result { return checkout(amount) } "#; let errs = errors(src); assert!(!errs.is_empty(), "expected error when @experience calls @experience"); assert!(has_rule(&errs, "VBD-002"), "expected VBD-002 rule"); } // ── 4. @experience calling non-experience → no VBD-002 error ───────────────── #[test] fn test_experience_calls_non_experience_no_error() { let src = r#" @accessor fn load_cart(id: String) -> String { return id } @experience fn checkout(cart: String) -> Result { return load_cart(cart) } "#; let errs = errors(src); let vbd002: Vec<_> = errs.iter().filter(|d| d.rule == "VBD-002").collect(); assert!(vbd002.is_empty(), "experience->non-experience should not trigger VBD-002"); } // ── 5. @public function with activate inside → SEC-001 error ───────────────── #[test] fn test_public_fn_with_activate_error() { let src = r#" @public fn list_users() -> String { let users: String = activate User where "all users" return users } "#; let errs = errors(src); assert!(!errs.is_empty(), "expected error for @public fn with activate"); assert!(has_rule(&errs, "SEC-001"), "expected SEC-001 rule"); } // ── 6. @public function without activate → no SEC-001 error ────────────────── #[test] fn test_public_fn_without_activate_no_error() { let src = r#" @public fn greet(name: String) -> String { return name } "#; let errs = errors(src); let sec001: Vec<_> = errs.iter().filter(|d| d.rule == "SEC-001").collect(); assert!(sec001.is_empty(), "@public without activate should not trigger SEC-001"); } // ── 7. N+1: activate inside a for loop → GRAPH-001 warning ─────────────────── #[test] fn test_activate_in_for_loop_n1_warning() { let src = r#" fn process_ids(ids: String) -> String { for id in ids { let u: String = activate User where "user by id" } return "done" } "#; let warns = warnings(src); assert!(!warns.is_empty(), "expected N+1 warning for activate inside loop"); assert!(has_rule(&warns, "GRAPH-001"), "expected GRAPH-001 rule"); } // ── 8. activate NOT in loop → no GRAPH-001 warning ─────────────────────────── #[test] fn test_activate_not_in_loop_no_n1_warning() { let src = r#" fn fetch_users() -> String { let users: String = activate User where "recent users" return users } "#; let warns = warnings(src); let graph001: Vec<_> = warns.iter().filter(|d| d.rule == "GRAPH-001").collect(); assert!(graph001.is_empty(), "activate outside loop should not trigger GRAPH-001"); } // ── 9. Duplicate activate same type → GRAPH-002 warning ────────────────────── #[test] fn test_duplicate_activate_same_type_warning() { let src = r#" fn inefficient_fn(x: String) -> String { let a: String = activate User where "active users" let b: String = activate User where "recent users" return a } "#; let warns = warnings(src); assert!(!warns.is_empty(), "expected warning for duplicate activate on same type"); assert!(has_rule(&warns, "GRAPH-002"), "expected GRAPH-002 rule"); } // ── 10. Different activate types → no GRAPH-002 warning ────────────────────── #[test] fn test_different_activate_types_no_duplicate_warning() { let src = r#" fn fetch_all(x: String) -> String { let users: String = activate User where "users" let orders: String = activate Order where "orders" return users } "#; let warns = warnings(src); let graph002: Vec<_> = warns.iter().filter(|d| d.rule == "GRAPH-002").collect(); assert!(graph002.is_empty(), "different activate types should not trigger GRAPH-002"); } // ── 11. @swarm_agent calling @swarm_agent → SWARM-001 error ────────────────── #[test] fn test_swarm_agent_calls_swarm_agent_error() { let src = r#" @swarm_agent fn worker_b(x: String) -> String { return x } @swarm_agent fn worker_a(x: String) -> String { return worker_b(x) } "#; let errs = errors(src); assert!(!errs.is_empty(), "expected error when @swarm_agent calls @swarm_agent"); assert!(has_rule(&errs, "SWARM-001"), "expected SWARM-001 rule"); } // ── 12. @swarm_agent in isolation → no SWARM-001 error ─────────────────────── #[test] fn test_swarm_agent_isolation_no_error() { let src = r#" fn utility(x: String) -> String { return x } @swarm_agent fn worker(x: String) -> String { return utility(x) } "#; let errs = errors(src); let swarm001: Vec<_> = errs.iter().filter(|d| d.rule == "SWARM-001").collect(); assert!(swarm001.is_empty(), "isolated @swarm_agent should not trigger SWARM-001"); } // ── 13. Multiple rules fire on same function ────────────────────────────────── #[test] fn test_multiple_rules_fire_same_function() { // @swarm_agent calling a swarm_agent AND accessing shared state let src = r#" @swarm_agent fn peer(x: String) -> String { return x } fn get_shared_cache(x: String) -> String { return x } @swarm_agent fn violator(x: String) -> String { let a: String = peer(x) let b: String = get_shared_cache(x) return a } "#; let errs = errors(src); // Should fire both SWARM-001 (calls peer) and SWARM-003 (calls get_shared_cache) assert!(errs.len() >= 2, "expected multiple errors: {:?}", errs.iter().map(|e| &e.rule).collect::>()); } // ── 14. ArchChecker::has_errors() → true when errors present ───────────────── #[test] fn test_has_errors_true_when_errors_present() { let src = r#" @manager fn do_manage(x: String) -> String { return x } @accessor fn bad_fetch(x: String) -> String { return do_manage(x) } "#; let diags = check(src); assert!(ArchChecker::has_errors(&diags), "has_errors should be true"); } // ── 15. ArchChecker::has_errors() → false when only warnings ───────────────── #[test] fn test_has_errors_false_when_only_warnings() { let src = r#" @experience fn sign_up(email: String) -> String { return email } "#; // sign_up doesn't return Result so triggers VBD-003 warning let diags = check(src); let has_warn = diags.iter().any(|d| d.severity == Severity::Warning); assert!(has_warn || diags.is_empty(), "expected either warnings or empty"); assert!(!ArchChecker::has_errors(&diags.iter().filter(|d| d.severity == Severity::Warning).cloned().collect::>()), "has_errors should be false for warnings"); } // ── 16. Clean function → empty diagnostics ──────────────────────────────────── #[test] fn test_clean_function_no_diagnostics() { let src = r#" fn pure_add(a: String, b: String) -> String { return a } "#; let diags = check(src); assert!(diags.is_empty(), "clean function should produce no diagnostics, got: {:?}", diags.iter().map(|d| &d.rule).collect::>()); } // ── 17. @engine function → runs without panic ───────────────────────────────── #[test] fn test_engine_function_no_panic() { let src = r#" @engine fn compute(data: String) -> String { return data } "#; // Just verify no panic — engine rules only produce warnings in some impls let _diags = check(src); } // ── 18. sealed { } not in loop → no SEC-002 warning ────────────────────────── #[test] fn test_sealed_not_in_loop_no_warning() { let src = r#" fn encrypt_data(secret: String) -> String { sealed { let key: String = secret } return secret } "#; let warns = warnings(src); let sec002: Vec<_> = warns.iter().filter(|d| d.rule == "SEC-002").collect(); assert!(sec002.is_empty(), "sealed not in loop should not trigger SEC-002"); } // ── 19. Rule names are unique ───────────────────────────────────────────────── #[test] fn test_rule_names_are_unique() { let checker = ArchChecker::new(); let mut names = std::collections::HashSet::new(); for rule in checker.rules() { let inserted = names.insert(rule.name().to_string()); assert!(inserted, "duplicate rule name: {}", rule.name()); } } // ── 20. All rules implement ArchRule (compile-time check) ───────────────────── #[test] fn test_all_rules_implement_arch_rule() { use crate::rule::ArchRule; use crate::rules::{ vbd::{AccessorMustNotCallManager, ExperienceMustNotCallExperience, ExperienceShouldReturnResult}, security::{PublicFnWithActivate, SealedInLoop, AuthnWithoutAuthz}, graph::{N1Detection, DuplicateActivateType}, swarm::{SwarmAgentIsolation, SwarmAgentNoSpawn, SwarmAgentNoSharedState}, }; fn assert_arch_rule() {} assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); assert_arch_rule::(); } // ── 21. FnContext builds correctly for a decorated function ─────────────────── #[test] fn test_fn_context_from_decorated_function() { // Run checker and verify the location field is set to the function name let src = r#" @accessor fn fetch_item(id: String) -> String { return id } "#; let diags = check(src); // No violations — but verify the checker doesn't panic and processes it // (accessor with no calls should produce no errors) let _: Vec<_> = diags; } // ── 22. @experience without Result return type → VBD-003 warning ───────────── #[test] fn test_experience_non_result_return_warning() { let src = r#" @experience fn show_profile(user: String) -> String { return user } "#; let warns = warnings(src); assert!(!warns.is_empty(), "expected warning for @experience not returning Result"); assert!(has_rule(&warns, "VBD-003"), "expected VBD-003 rule"); } // ── 23. @authenticate without @authorize on mutation → SEC-003 warning ──────── #[test] fn test_authenticate_without_authorize_on_mutation_warning() { let src = r#" @authenticate fn create_account(email: String) -> String { return email } "#; let warns = warnings(src); assert!(!warns.is_empty(), "expected SEC-003 warning"); assert!(has_rule(&warns, "SEC-003"), "expected SEC-003 rule"); } // ── 24. Two @experience functions checked independently ─────────────────────── #[test] fn test_two_experience_functions_checked_independently() { let src = r#" @experience fn sign_up(email: String) -> Result { return email } @experience fn log_in(token: String) -> String { return token } "#; let warns = warnings(src); // sign_up returns Result — no VBD-003 for it // log_in returns String — VBD-003 fires for it let vbd003: Vec<_> = warns.iter().filter(|d| d.rule == "VBD-003").collect(); assert_eq!(vbd003.len(), 1, "only log_in should trigger VBD-003, got {:?}", vbd003.iter().map(|d| &d.location).collect::>()); assert!(vbd003[0].location.as_deref() == Some("log_in"), "VBD-003 should point to log_in"); } // ── 25. Mixed errors and warnings → has_errors() returns true ───────────────── #[test] fn test_mixed_errors_and_warnings_has_errors_true() { let src = r#" @manager fn manage_data(x: String) -> String { return x } @accessor fn bad_read(x: String) -> String { return manage_data(x) } @experience fn display(x: String) -> String { return x } "#; let diags = check(src); assert!(ArchChecker::has_errors(&diags), "should have errors (VBD-001)"); let has_warn = diags.iter().any(|d| d.severity == Severity::Warning); assert!(has_warn, "should also have warnings (VBD-003 for display)"); } // ── 26. @swarm_agent calling spawn → SWARM-002 error ───────────────────────── #[test] fn test_swarm_agent_no_spawn() { let src = r#" fn spawn(agent: String) -> String { return agent } @swarm_agent fn initiator(x: String) -> String { return spawn(x) } "#; let errs = errors(src); assert!(!errs.is_empty(), "expected SWARM-002 error for calling spawn"); assert!(has_rule(&errs, "SWARM-002"), "expected SWARM-002 rule"); } // ── 27. @swarm_agent accessing shared state → SWARM-003 error ──────────────── #[test] fn test_swarm_agent_no_shared_state() { let src = r#" fn get_shared_counter(x: String) -> String { return x } @swarm_agent fn agent(x: String) -> String { return get_shared_counter(x) } "#; let errs = errors(src); assert!(!errs.is_empty(), "expected SWARM-003 error for shared state access"); assert!(has_rule(&errs, "SWARM-003"), "expected SWARM-003 rule"); } // ── 28. sealed block inside for loop → SEC-002 warning ─────────────────────── #[test] fn test_sealed_in_for_loop_warning() { let src = r#" fn encrypt_many(items: String) -> String { for item in items { sealed { let x: String = item } } return items } "#; let warns = warnings(src); assert!(!warns.is_empty(), "expected SEC-002 warning for sealed in loop"); assert!(has_rule(&warns, "SEC-002"), "expected SEC-002 rule"); } // ── 29. @authenticate with @authorize → no SEC-003 warning ─────────────────── #[test] fn test_authenticate_with_authorize_no_warning() { let src = r#" @authenticate @authorize fn create_post(content: String) -> String { return content } "#; let warns = warnings(src); let sec003: Vec<_> = warns.iter().filter(|d| d.rule == "SEC-003").collect(); assert!(sec003.is_empty(), "@authenticate + @authorize should not trigger SEC-003"); } // ── 30. @experience returning Result → no VBD-003 warning ──────────────────── #[test] fn test_experience_returns_result_no_warning() { let src = r#" @experience fn register(email: String) -> Result { return email } "#; let warns = warnings(src); let vbd003: Vec<_> = warns.iter().filter(|d| d.rule == "VBD-003").collect(); assert!(vbd003.is_empty(), "@experience returning Result should not trigger VBD-003"); }