leankg 0.16.7

Lightweight Knowledge Graph for AI-Assisted Development
Documentation
use leankg::db::models::{BusinessLogic, CodeElement, Relationship};
use leankg::db::schema::init_db;
use leankg::doc::{DocGenerator, DocSyncResult, DocTrackingInfo, TemplateEngine};
use leankg::graph::GraphEngine;
use std::path::PathBuf;
use tempfile::TempDir;

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_generator_comprehensive_agents_md() {
    let tmp = TempDir::new().unwrap();
    let db_path = tmp.path().join("leankg.db");
    let db = init_db(db_path.as_path()).unwrap();
    let graph = GraphEngine::new(db);
    let doc_gen = DocGenerator::new(graph, PathBuf::from("./docs"));

    let content = doc_gen.generate_agents_md().unwrap();

    assert!(content.contains("# Agent Guidelines for LeanKG"));
    assert!(content.contains("## Project Overview"));
    assert!(content.contains("**Tech Stack**: Rust 1.70+"));
    assert!(content.contains("## Build Commands"));
    assert!(content.contains("cargo build"));
    assert!(content.contains("cargo test"));
    assert!(content.contains("## Testing Guidelines"));
    assert!(content.contains("## Code Structure Overview"));
    assert!(content.contains("This codebase contains 0 elements"));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_generator_comprehensive_claude_md() {
    let tmp = TempDir::new().unwrap();
    let db_path = tmp.path().join("leankg.db");
    let db = init_db(db_path.as_path()).unwrap();
    let graph = GraphEngine::new(db);
    let doc_gen = DocGenerator::new(graph, PathBuf::from("./docs"));

    let content = doc_gen.generate_claude_md().unwrap();

    assert!(content.contains("# CLAUDE.md"));
    assert!(content.contains("## Project Overview"));
    assert!(content.contains("## Architecture Decisions"));
    assert!(content.contains("### Knowledge Graph Storage"));
    assert!(content.contains("### Code Indexing"));
    assert!(content.contains("### Documentation Generation"));
    assert!(content.contains("## Context Statistics"));
    assert!(content.contains("- **Total elements**: 0"));
    assert!(content.contains("- **Total relationships**: 0"));
    assert!(content.contains("## Context Guidelines for AI"));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_sync_result_structure() {
    let result = DocSyncResult {
        file_path: "src/main.rs".to_string(),
        elements_regenerated: 5,
        relationships_updated: 10,
        regenerated_elements: vec![
            "src/main.rs::main".to_string(),
            "src/main.rs::init".to_string(),
        ],
    };

    assert_eq!(result.file_path, "src/main.rs");
    assert_eq!(result.elements_regenerated, 5);
    assert_eq!(result.relationships_updated, 10);
    assert_eq!(result.regenerated_elements.len(), 2);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_tracking_info_structure() {
    let element = CodeElement {
        qualified_name: "src/main.rs::main".to_string(),
        element_type: "function".to_string(),
        name: "main".to_string(),
        file_path: "src/main.rs".to_string(),
        line_start: 1,
        line_end: 10,
        language: "rust".to_string(),
        parent_qualified: None,
        metadata: serde_json::json!({}),
        ..Default::default()
    };

    let relationship = Relationship {
        id: None,
        source_qualified: "src/main.rs::main".to_string(),
        target_qualified: "src/lib.rs::init".to_string(),
        rel_type: "imports".to_string(),
        confidence: 1.0,
        metadata: serde_json::json!({}),
    };

    let annotation = BusinessLogic {
        id: None,
        element_qualified: "src/main.rs::main".to_string(),
        description: "Main entry point for the application".to_string(),
        user_story_id: Some("US-001".to_string()),
        feature_id: Some("F-001".to_string()),
    };

    let tracking_info = DocTrackingInfo {
        element,
        relationships: vec![relationship],
        annotation: Some(annotation),
        generated_from: vec!["src/main.rs".to_string()],
    };

    assert_eq!(tracking_info.element.qualified_name, "src/main.rs::main");
    assert_eq!(tracking_info.relationships.len(), 1);
    assert!(tracking_info.annotation.is_some());
    assert_eq!(tracking_info.generated_from.len(), 1);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_template_engine_render_template() {
    use std::collections::HashMap;

    let template = "Hello {{name}}, you are a {{role}}.";
    let mut vars = HashMap::new();
    vars.insert("name".to_string(), "Alice".to_string());
    vars.insert("role".to_string(), "developer".to_string());

    let result = TemplateEngine::render_template(template, &vars);
    assert_eq!(result, "Hello Alice, you are a developer.");
}

#[tokio::test(flavor = "multi_thread")]
async fn test_template_engine_render_template_missing_var() {
    use std::collections::HashMap;

    let template = "Hello {{name}}, your email is {{email}}.";
    let mut vars = HashMap::new();
    vars.insert("name".to_string(), "Bob".to_string());

    let result = TemplateEngine::render_template(template, &vars);
    assert_eq!(result, "Hello Bob, your email is {{email}}.");
}

#[tokio::test(flavor = "multi_thread")]
async fn test_template_engine_default_agents_template() {
    let template = TemplateEngine::get_default_agents_template();
    assert!(template.contains("# Agent Guidelines for {{project_name}}"));
    assert!(template.contains("## Project Overview"));
    assert!(template.contains("{{project_description}}"));
    assert!(template.contains("## Build Commands"));
    assert!(template.contains("cargo build"));
    assert!(template.contains("## Code Structure Overview"));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_template_engine_default_claude_template() {
    let template = TemplateEngine::get_default_claude_template();
    assert!(template.contains("# CLAUDE.md"));
    assert!(template.contains("## Project Overview"));
    assert!(template.contains("{{project_description}}"));
    assert!(template.contains("## Architecture Decisions"));
    assert!(template.contains("{{architecture_decisions}}"));
    assert!(template.contains("## Context Statistics"));
    assert!(template.contains("{{element_count}}"));
    assert!(template.contains("{{relationship_count}}"));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_generator_with_elements() {
    let tmp = TempDir::new().unwrap();
    let db_path = tmp.path().join("leankg.db");
    let db = init_db(db_path.as_path()).unwrap();
    let graph = GraphEngine::new(db);

    let element = CodeElement {
        qualified_name: "src/main.rs::main".to_string(),
        element_type: "function".to_string(),
        name: "main".to_string(),
        file_path: "src/main.rs".to_string(),
        line_start: 1,
        line_end: 20,
        language: "rust".to_string(),
        parent_qualified: None,
        metadata: serde_json::json!({}),
        ..Default::default()
    };

    graph.insert_elements(&[element]).unwrap();

    let doc_gen = DocGenerator::new(graph, PathBuf::from("./docs"));
    let content = doc_gen.generate_agents_md().unwrap();

    assert!(content.contains("# Agent Guidelines for LeanKG"));
    assert!(content.contains("This codebase contains 1 elements"));
    assert!(content.contains("## Code Structure Overview"));
    assert!(content.contains("### Functions"));
    assert!(content.contains("src/main.rs::main"));
}

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_generator_regenerate_for_file() {
    let tmp = TempDir::new().unwrap();
    let db_path = tmp.path().join("leankg.db");
    let db = init_db(db_path.as_path()).unwrap();
    let graph = GraphEngine::new(db);

    let element = CodeElement {
        qualified_name: "src/main.rs::main".to_string(),
        element_type: "function".to_string(),
        name: "main".to_string(),
        file_path: "src/main.rs".to_string(),
        line_start: 1,
        line_end: 20,
        language: "rust".to_string(),
        parent_qualified: None,
        metadata: serde_json::json!({}),
        ..Default::default()
    };

    graph.insert_elements(&[element]).unwrap();

    let doc_gen = DocGenerator::new(graph, PathBuf::from("./docs"));
    let result = doc_gen.regenerate_for_file("src/main.rs").unwrap();

    assert_eq!(result.len(), 1);
    assert_eq!(result[0], "src/main.rs::main");
}

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_generator_tracking_info() {
    let tmp = TempDir::new().unwrap();
    let db_path = tmp.path().join("leankg.db");
    let db = init_db(db_path.as_path()).unwrap();
    let graph = GraphEngine::new(db);

    let element = CodeElement {
        qualified_name: "src/main.rs::main".to_string(),
        element_type: "function".to_string(),
        name: "main".to_string(),
        file_path: "src/main.rs".to_string(),
        line_start: 1,
        line_end: 20,
        language: "rust".to_string(),
        parent_qualified: None,
        metadata: serde_json::json!({}),
        ..Default::default()
    };

    graph.insert_elements(&[element]).unwrap();

    let doc_gen = DocGenerator::new(graph, PathBuf::from("./docs"));
    let tracking = doc_gen.get_doc_tracking_info("src/main.rs::main").unwrap();

    assert!(tracking.is_some());
    let info = tracking.unwrap();
    assert_eq!(info.element.qualified_name, "src/main.rs::main");
}

#[tokio::test(flavor = "multi_thread")]
async fn test_doc_generator_tracking_info_not_found() {
    let tmp = TempDir::new().unwrap();
    let db_path = tmp.path().join("leankg.db");
    let db = init_db(db_path.as_path()).unwrap();
    let graph = GraphEngine::new(db);
    let doc_gen = DocGenerator::new(graph, PathBuf::from("./docs"));

    let tracking = doc_gen.get_doc_tracking_info("nonexistent::foo").unwrap();
    assert!(tracking.is_none());
}