mockforge-core 0.3.124

Shared logic for MockForge - routing, validation, latency, proxy
Documentation
//! Tests for output control functionality

use mockforge_core::generate_config::{BarrelType, OutputConfig};
use mockforge_core::output_control::{
    apply_banner, apply_extension, apply_file_naming_template, build_file_naming_context,
    process_generated_file, BarrelGenerator, GeneratedFile,
};
use mockforge_openapi::OpenApiSpec;
use std::collections::HashMap;
use std::path::{Path, PathBuf};

#[test]
fn test_clean_option_in_config() {
    let config = OutputConfig {
        clean: true,
        ..Default::default()
    };
    assert!(config.clean);
}

#[test]
fn test_barrel_type_defaults_to_none() {
    let config = OutputConfig::default();
    assert_eq!(config.barrel_type, BarrelType::None);
}

#[test]
fn test_barrel_type_index() {
    let config = OutputConfig {
        barrel_type: BarrelType::Index,
        ..Default::default()
    };
    assert_eq!(config.barrel_type, BarrelType::Index);
}

#[test]
fn test_extension_override() {
    let config = OutputConfig {
        extension: Some("ts".to_string()),
        ..Default::default()
    };
    assert_eq!(config.extension, Some("ts".to_string()));
}

#[test]
fn test_banner_template() {
    let config = OutputConfig {
        banner: Some("Generated by {{generator}}\nSource: {{source}}".to_string()),
        ..Default::default()
    };
    assert!(config.banner.is_some());
}

#[test]
fn test_file_naming_template() {
    let config = OutputConfig {
        file_naming_template: Some("{{name}}_{{tag}}".to_string()),
        ..Default::default()
    };
    assert!(config.file_naming_template.is_some());
}

#[test]
fn test_apply_banner_with_placeholders() {
    let content = "export const test = 1;";
    let banner = "Generated by {{generator}}\nSource: {{source}}\nTimestamp: {{timestamp}}";
    let source = Some(Path::new("api.yaml"));

    let result = apply_banner(content, banner, source);
    assert!(result.contains("MockForge"));
    assert!(result.contains("api.yaml"));
    assert!(result.contains("export const test"));
    assert!(result.contains("//")); // Should have line comments for TypeScript
}

#[test]
fn test_apply_banner_without_source() {
    let content = "export const test = 1;";
    let banner = "Generated by {{generator}}";
    let result = apply_banner(content, banner, None);
    assert!(result.contains("MockForge"));
    // The banner is formatted with comments, so "unknown" appears in comment format
    // Check that the result contains the content and MockForge
    assert!(result.contains("export const test"));
}

#[test]
fn test_apply_extension() {
    let path = Path::new("output/file.js");
    let new_path = apply_extension(path, Some("ts"));
    assert_eq!(new_path, PathBuf::from("output/file.ts"));
}

#[test]
fn test_apply_extension_none() {
    let path = Path::new("output/file.js");
    let new_path = apply_extension(path, None);
    assert_eq!(new_path, path);
}

#[test]
fn test_apply_file_naming_template() {
    let template = "{{name}}_{{tag}}";
    let mut context = HashMap::new();
    context.insert("name", "user");
    context.insert("tag", "api");

    let result = apply_file_naming_template(template, &context);
    assert_eq!(result, "user_api");
}

#[test]
fn test_apply_file_naming_template_missing_placeholder() {
    let template = "{{name}}_{{tag}}";
    let mut context = HashMap::new();
    context.insert("name", "user");
    // tag is missing

    let result = apply_file_naming_template(template, &context);
    assert_eq!(result, "user_{{tag}}"); // Missing placeholder stays
}

#[test]
fn test_process_generated_file_with_extension() {
    let file = GeneratedFile {
        path: PathBuf::from("output.rs"),
        content: "pub struct Test {};".to_string(),
        extension: "rs".to_string(),
        exportable: false,
    };

    let config = OutputConfig {
        extension: Some("ts".to_string()),
        ..Default::default()
    };

    let processed = process_generated_file(file, &config, None, None);
    assert_eq!(processed.extension, "ts");
}

#[test]
fn test_process_generated_file_with_banner() {
    let file = GeneratedFile {
        path: PathBuf::from("output.ts"),
        content: "export const test = 1;".to_string(),
        extension: "ts".to_string(),
        exportable: true,
    };

    let config = OutputConfig {
        banner: Some("Generated by {{generator}}".to_string()),
        ..Default::default()
    };

    let processed = process_generated_file(file, &config, Some(Path::new("api.yaml")), None);
    assert!(processed.content.contains("MockForge"));
    assert!(processed.content.contains("export const test"));
}

#[test]
fn test_generate_index_file() {
    let output_dir = Path::new("/tmp/test");
    let files = vec![
        GeneratedFile {
            path: PathBuf::from("types.ts"),
            content: "export type User = {};".to_string(),
            extension: "ts".to_string(),
            exportable: true,
        },
        GeneratedFile {
            path: PathBuf::from("client.ts"),
            content: "export const client = {};".to_string(),
            extension: "ts".to_string(),
            exportable: true,
        },
        GeneratedFile {
            path: PathBuf::from("README.md"),
            content: "# Documentation".to_string(),
            extension: "md".to_string(),
            exportable: false, // Markdown is not exportable
        },
    ];

    let result =
        BarrelGenerator::generate_barrel_files(output_dir, &files, BarrelType::Index).unwrap();
    assert_eq!(result.len(), 1);
    assert!(result[0].0.ends_with("index.ts"));
    assert!(result[0].1.contains("export * from './types'"));
    assert!(result[0].1.contains("export * from './client'"));
    assert!(!result[0].1.contains("README")); // Should not export README
}

#[test]
fn test_generate_index_file_empty() {
    let output_dir = Path::new("/tmp/test");
    let files: Vec<GeneratedFile> = vec![];

    let result =
        BarrelGenerator::generate_barrel_files(output_dir, &files, BarrelType::Index).unwrap();
    assert_eq!(result.len(), 1);
    assert!(result[0].1.contains("No exportable files found"));
}

#[test]
fn test_generate_barrel_files_none() {
    let output_dir = Path::new("/tmp/test");
    let files = vec![GeneratedFile {
        path: PathBuf::from("test.ts"),
        content: "export const test = 1;".to_string(),
        extension: "ts".to_string(),
        exportable: true,
    }];

    let result =
        BarrelGenerator::generate_barrel_files(output_dir, &files, BarrelType::None).unwrap();
    assert_eq!(result.len(), 0);
}

#[test]
fn test_generate_barrel_files_index() {
    let output_dir = Path::new("/tmp/test");
    let files = vec![
        GeneratedFile {
            path: PathBuf::from("types.ts"),
            content: "export type User = {};".to_string(),
            extension: "ts".to_string(),
            exportable: true,
        },
        GeneratedFile {
            path: PathBuf::from("client.ts"),
            content: "export const client = {};".to_string(),
            extension: "ts".to_string(),
            exportable: true,
        },
    ];

    let result =
        BarrelGenerator::generate_barrel_files(output_dir, &files, BarrelType::Index).unwrap();
    assert_eq!(result.len(), 1);
    assert!(result[0].0.ends_with("index.ts"));
}

#[test]
fn test_generate_barrel_files_barrel() {
    let output_dir = Path::new("/tmp/test");
    let files = vec![
        GeneratedFile {
            path: PathBuf::from("api/types.ts"),
            content: "export type User = {};".to_string(),
            extension: "ts".to_string(),
            exportable: true,
        },
        GeneratedFile {
            path: PathBuf::from("api/client.ts"),
            content: "export const client = {};".to_string(),
            extension: "ts".to_string(),
            exportable: true,
        },
        GeneratedFile {
            path: PathBuf::from("utils/helpers.ts"),
            content: "export const helper = {};".to_string(),
            extension: "ts".to_string(),
            exportable: true,
        },
    ];

    let result =
        BarrelGenerator::generate_barrel_files(output_dir, &files, BarrelType::Barrel).unwrap();
    // Should generate index.ts files for each directory
    assert!(result.len() >= 2); // api/index.ts and utils/index.ts

    // Verify that import paths are relative to parent directory
    let api_barrel = result
        .iter()
        .find(|(path, _)| path.to_string_lossy().contains("api/index.ts"))
        .expect("Should have api/index.ts");

    // Should export './types' and './client', not './api/types'
    assert!(api_barrel.1.contains("export * from './types'"));
    assert!(api_barrel.1.contains("export * from './client'"));
    assert!(!api_barrel.1.contains("./api/types")); // Should not have full path
}

#[test]
fn test_process_generated_file_with_naming_template() {
    let file = GeneratedFile {
        path: PathBuf::from("user.ts"),
        content: "export const user = {};".to_string(),
        extension: "ts".to_string(),
        exportable: true,
    };

    let config = OutputConfig {
        file_naming_template: Some("{{name}}_{{tag}}".to_string()),
        ..Default::default()
    };

    let processed = process_generated_file(file, &config, None, None);
    // Should apply template (name="user", tag="api" as fallback)
    assert!(processed.path.to_string_lossy().contains("user_api"));
}

#[test]
fn test_build_file_naming_context() {
    let openapi_spec = r#"
openapi: 3.0.0
info:
  title: Test API
  version: 1.0.0
paths:
  /users:
    get:
      operationId: getUsers
      tags:
        - users
      summary: Get all users
      responses:
        '200':
          description: Success
  /users/{id}:
    get:
      operationId: getUserById
      tags:
        - users
      summary: Get user by ID
      responses:
        '200':
          description: Success
components:
  schemas:
    User:
      type: object
"#;

    let spec = OpenApiSpec::from_string(openapi_spec, Some("yaml")).unwrap();
    let context = build_file_naming_context(&spec);

    // Test context retrieval for operation
    let user_context = context.get_context_for_name("getUsers");
    assert_eq!(user_context.get("tag"), Some(&"users"));
    assert_eq!(user_context.get("operation"), Some(&"get"));

    // Test context retrieval for schema
    let schema_context = context.get_context_for_name("User");
    assert_eq!(schema_context.get("tag"), Some(&"schemas"));
}

#[test]
fn test_process_generated_file_with_openapi_context() {
    let openapi_spec = r#"
openapi: 3.0.0
info:
  title: Test API
  version: 1.0.0
paths:
  /users:
    get:
      operationId: getUsers
      tags:
        - users
      responses:
        '200':
          description: Success
"#;

    let spec = OpenApiSpec::from_string(openapi_spec, Some("yaml")).unwrap();
    let naming_context = build_file_naming_context(&spec);

    let file = GeneratedFile {
        path: PathBuf::from("getUsers.ts"),
        content: "export const getUsers = () => {};".to_string(),
        extension: "ts".to_string(),
        exportable: true,
    };

    let config = OutputConfig {
        file_naming_template: Some("{{tag}}_{{name}}".to_string()),
        ..Default::default()
    };

    let processed = process_generated_file(file, &config, None, Some(&naming_context));
    // Should use real tag from OpenAPI spec
    assert!(processed.path.to_string_lossy().contains("users_getUsers"));
}