use colored::Colorize;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct WizardConfig {
pub project_name: String,
pub project_dir: PathBuf,
pub protocols: Vec<Protocol>,
pub enable_admin: bool,
pub enable_examples: bool,
pub template: Option<Template>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Protocol {
Http,
WebSocket,
Grpc,
GraphQL,
Kafka,
Mqtt,
Amqp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Template {
RestApi,
Grpc,
WebSocket,
GraphQL,
Microservices,
}
impl Protocol {
fn all() -> Vec<Protocol> {
vec![
Protocol::Http,
Protocol::WebSocket,
Protocol::Grpc,
Protocol::GraphQL,
Protocol::Kafka,
Protocol::Mqtt,
Protocol::Amqp,
]
}
fn name(&self) -> &'static str {
match self {
Protocol::Http => "HTTP/REST",
Protocol::WebSocket => "WebSocket",
Protocol::Grpc => "gRPC",
Protocol::GraphQL => "GraphQL",
Protocol::Kafka => "Kafka",
Protocol::Mqtt => "MQTT",
Protocol::Amqp => "AMQP/RabbitMQ",
}
}
fn port(&self) -> u16 {
match self {
Protocol::Http => 3000,
Protocol::WebSocket => 3001,
Protocol::Grpc => 50051,
Protocol::GraphQL => 4000,
Protocol::Kafka => 9092,
Protocol::Mqtt => 1883,
Protocol::Amqp => 5672,
}
}
}
impl Template {
fn all() -> Vec<Template> {
vec![
Template::RestApi,
Template::Grpc,
Template::WebSocket,
Template::GraphQL,
Template::Microservices,
]
}
fn name(&self) -> &'static str {
match self {
Template::RestApi => "REST API",
Template::Grpc => "gRPC Service",
Template::WebSocket => "WebSocket Server",
Template::GraphQL => "GraphQL API",
Template::Microservices => "Microservices (Multi-Protocol)",
}
}
fn description(&self) -> &'static str {
match self {
Template::RestApi => "Standard REST API with CRUD operations",
Template::Grpc => "gRPC service with protobuf definitions",
Template::WebSocket => "Real-time WebSocket server",
Template::GraphQL => "GraphQL API with queries and mutations",
Template::Microservices => "Multiple protocols for microservices architecture",
}
}
}
pub async fn run_wizard() -> Result<WizardConfig, Box<dyn std::error::Error + Send + Sync>> {
let theme = ColorfulTheme::default();
println!("\n{}", "🎯 MockForge Getting Started Wizard".bright_cyan().bold());
println!("{}", "=".repeat(50).bright_cyan());
println!(
"\n{}",
"This wizard will help you set up your first MockForge project.\n".bright_white()
);
let suggestions = detect_environment();
if !suggestions.is_empty() {
println!("{}", "🔍 Environment Detection:".bright_yellow().bold());
for suggestion in &suggestions {
println!(" • {}", suggestion.bright_white());
}
println!();
}
let project_name: String = Input::with_theme(&theme)
.with_prompt("Project name")
.default("my-mock-api".to_string())
.interact()?;
let current_dir = std::env::current_dir()?;
let project_dir = if project_name == "." {
current_dir.clone()
} else {
current_dir.join(&project_name)
};
let template_selection = Select::with_theme(&theme)
.with_prompt("Choose a template to get started")
.items(&[
"REST API - Standard REST API with CRUD operations",
"gRPC Service - gRPC service with protobuf definitions",
"WebSocket Server - Real-time WebSocket server",
"GraphQL API - GraphQL API with queries and mutations",
"Microservices - Multiple protocols for microservices",
"Custom - Start from scratch",
])
.default(0)
.interact()?;
let template = match template_selection {
0 => Some(Template::RestApi),
1 => Some(Template::Grpc),
2 => Some(Template::WebSocket),
3 => Some(Template::GraphQL),
4 => Some(Template::Microservices),
_ => None,
};
let protocols = if let Some(tmpl) = template {
match tmpl {
Template::RestApi => vec![Protocol::Http],
Template::Grpc => vec![Protocol::Http, Protocol::Grpc],
Template::WebSocket => vec![Protocol::Http, Protocol::WebSocket],
Template::GraphQL => vec![Protocol::Http, Protocol::GraphQL],
Template::Microservices => vec![
Protocol::Http,
Protocol::Grpc,
Protocol::WebSocket,
Protocol::Kafka,
],
}
} else {
let protocol_names: Vec<&str> = Protocol::all().iter().map(|p| p.name()).collect();
let selections = MultiSelect::with_theme(&theme)
.with_prompt("Select protocols to enable")
.items(&protocol_names)
.defaults(&[true, false, false, false, false, false, false]) .interact()?;
selections.into_iter().map(|i| Protocol::all()[i]).collect()
};
let enable_admin = Confirm::with_theme(&theme)
.with_prompt("Enable Admin UI?")
.default(true)
.interact()?;
let enable_examples = Confirm::with_theme(&theme)
.with_prompt("Include example files?")
.default(true)
.interact()?;
Ok(WizardConfig {
project_name,
project_dir,
protocols,
enable_admin,
enable_examples,
template,
})
}
pub async fn generate_project(
config: &WizardConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !config.project_dir.exists() {
fs::create_dir_all(&config.project_dir)?;
println!("\n{} Created directory: {}", "✅".green(), config.project_dir.display());
}
let config_path = config.project_dir.join("mockforge.yaml");
let config_content = generate_config_yaml(config);
fs::write(&config_path, config_content)?;
println!("{} Created {}", "✅".green(), config_path.display());
if let Some(template) = config.template {
generate_template_files(config, template).await?;
} else if config.enable_examples {
generate_example_files(config).await?;
}
let readme_path = config.project_dir.join("README.md");
let readme_content = generate_readme(config);
fs::write(&readme_path, readme_content)?;
println!("{} Created {}", "✅".green(), readme_path.display());
println!("\n{}", "🎉 Project created successfully!".bright_green().bold());
println!("\n{}", "Next steps:".bright_cyan().bold());
println!(" 1. cd {}", config.project_dir.display());
println!(" 2. Review mockforge.yaml configuration");
if config.enable_examples {
println!(" 3. Check examples/ directory for sample files");
}
println!(" 4. Run: {}", "mockforge serve".bright_yellow());
if config.enable_admin {
println!(" 5. Open Admin UI: {}", "http://localhost:9080".bright_yellow());
}
Ok(())
}
fn generate_config_yaml(config: &WizardConfig) -> String {
let mut yaml = String::from("# MockForge Configuration\n");
yaml.push_str("# Generated by MockForge Wizard\n");
yaml.push_str("# Full configuration reference: https://docs.mockforge.dev/config\n\n");
if config.protocols.contains(&Protocol::Http) {
yaml.push_str("# HTTP Server\n");
yaml.push_str("http:\n");
yaml.push_str(" port: 3000\n");
yaml.push_str(" host: \"0.0.0.0\"\n");
yaml.push_str(" cors_enabled: true\n");
yaml.push_str(" request_validation: \"enforce\"\n");
yaml.push_str(" response_template_expand: true\n");
yaml.push('\n');
}
if config.protocols.contains(&Protocol::WebSocket) {
yaml.push_str("# WebSocket Server\n");
yaml.push_str("websocket:\n");
yaml.push_str(" port: 3001\n");
yaml.push_str(" host: \"0.0.0.0\"\n");
yaml.push('\n');
}
if config.protocols.contains(&Protocol::Grpc) {
yaml.push_str("# gRPC Server\n");
yaml.push_str("grpc:\n");
yaml.push_str(" port: 50051\n");
yaml.push_str(" host: \"0.0.0.0\"\n");
yaml.push('\n');
}
if config.protocols.contains(&Protocol::GraphQL) {
yaml.push_str("# GraphQL Server\n");
yaml.push_str("graphql:\n");
yaml.push_str(" port: 4000\n");
yaml.push_str(" host: \"0.0.0.0\"\n");
yaml.push('\n');
}
if config.protocols.contains(&Protocol::Kafka) {
yaml.push_str("# Kafka Broker\n");
yaml.push_str("kafka:\n");
yaml.push_str(" enabled: true\n");
yaml.push_str(" port: 9092\n");
yaml.push('\n');
}
if config.protocols.contains(&Protocol::Mqtt) {
yaml.push_str("# MQTT Broker\n");
yaml.push_str("mqtt:\n");
yaml.push_str(" enabled: true\n");
yaml.push_str(" port: 1883\n");
yaml.push('\n');
}
if config.protocols.contains(&Protocol::Amqp) {
yaml.push_str("# AMQP Broker\n");
yaml.push_str("amqp:\n");
yaml.push_str(" enabled: true\n");
yaml.push_str(" port: 5672\n");
yaml.push('\n');
}
if config.enable_admin {
yaml.push_str("# Admin UI\n");
yaml.push_str("admin:\n");
yaml.push_str(" enabled: true\n");
yaml.push_str(" port: 9080\n");
yaml.push_str(" host: \"127.0.0.1\"\n");
yaml.push('\n');
}
yaml.push_str("# Observability\n");
yaml.push_str("observability:\n");
yaml.push_str(" prometheus:\n");
yaml.push_str(" enabled: true\n");
yaml.push_str(" port: 9090\n");
yaml.push('\n');
yaml.push_str("# Logging\n");
yaml.push_str("logging:\n");
yaml.push_str(" level: \"info\"\n");
yaml
}
async fn generate_template_files(
config: &WizardConfig,
template: Template,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match template {
Template::RestApi => generate_rest_api_template(config).await?,
Template::Grpc => generate_grpc_template(config).await?,
Template::WebSocket => generate_websocket_template(config).await?,
Template::GraphQL => generate_graphql_template(config).await?,
Template::Microservices => generate_microservices_template(config).await?,
}
Ok(())
}
async fn generate_rest_api_template(
config: &WizardConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let examples_dir = config.project_dir.join("examples");
fs::create_dir_all(&examples_dir)?;
let openapi_path = examples_dir.join("openapi.json");
let openapi_content = r##"{
"openapi": "3.0.0",
"info": {
"title": "User Management API",
"version": "1.0.0",
"description": "Example REST API for user management"
},
"servers": [
{
"url": "http://localhost:3000",
"description": "Local development server"
}
],
"paths": {
"/users": {
"get": {
"summary": "List all users",
"operationId": "listUsers",
"responses": {
"200": {
"description": "List of users",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
},
"example": [
{
"id": 1,
"name": "Alice Johnson",
"email": "alice@example.com",
"createdAt": "2024-01-01T00:00:00Z"
}
]
}
}
}
}
},
"post": {
"summary": "Create a new user",
"operationId": "createUser",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateUserRequest"
}
}
}
},
"responses": {
"201": {
"description": "User created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
}
},
"/users/{id}": {
"get": {
"summary": "Get user by ID",
"operationId": "getUser",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "User details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"404": {
"description": "User not found"
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": ["id", "name", "email"]
},
"CreateUserRequest": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
}
},
"required": ["name", "email"]
}
}
}
}"##;
fs::write(&openapi_path, openapi_content)?;
println!("{} Created {}", "✅".green(), openapi_path.display());
let config_path = config.project_dir.join("mockforge.yaml");
let mut config_content = fs::read_to_string(&config_path)?;
config_content.push_str("\n# OpenAPI Specification\n");
config_content.push_str("http:\n");
config_content.push_str(" openapi_spec: \"./examples/openapi.json\"\n");
fs::write(&config_path, config_content)?;
Ok(())
}
async fn generate_grpc_template(
config: &WizardConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let proto_dir = config.project_dir.join("proto");
fs::create_dir_all(&proto_dir)?;
let proto_path = proto_dir.join("example.proto");
let proto_content = r#"syntax = "proto3";
package example;
// Example gRPC service
service ExampleService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}
message GetUserRequest {
int64 user_id = 1;
}
message GetUserResponse {
int64 id = 1;
string name = 2;
string email = 3;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message CreateUserResponse {
int64 id = 1;
string name = 2;
string email = 3;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}
message ListUsersResponse {
repeated GetUserResponse users = 1;
int32 total = 2;
}
"#;
fs::write(&proto_path, proto_content)?;
println!("{} Created {}", "✅".green(), proto_path.display());
let config_path = config.project_dir.join("mockforge.yaml");
let mut config_content = fs::read_to_string(&config_path)?;
config_content.push_str("\n# gRPC Configuration\n");
config_content.push_str("grpc:\n");
config_content.push_str(" dynamic:\n");
config_content.push_str(" enabled: true\n");
config_content.push_str(" proto_dir: \"./proto\"\n");
fs::write(&config_path, config_content)?;
Ok(())
}
async fn generate_websocket_template(
config: &WizardConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let examples_dir = config.project_dir.join("examples");
fs::create_dir_all(&examples_dir)?;
let ws_replay_path = examples_dir.join("ws-replay.jsonl");
let ws_content = r#"{"ts":0,"dir":"out","text":"Welcome! Type 'hello' to get started.","waitFor":"^CLIENT_READY$"}
{"ts":1000,"dir":"out","text":"{\"type\":\"message\",\"content\":\"Hello from server!\"}","waitFor":"^hello$"}
{"ts":2000,"dir":"out","text":"{\"type\":\"data\",\"value\":\"{{randInt 1 100}}\"}"}
"#;
fs::write(&ws_replay_path, ws_content)?;
println!("{} Created {}", "✅".green(), ws_replay_path.display());
let config_path = config.project_dir.join("mockforge.yaml");
let mut config_content = fs::read_to_string(&config_path)?;
config_content.push_str("\n# WebSocket Configuration\n");
config_content.push_str("websocket:\n");
config_content.push_str(" replay_file: \"./examples/ws-replay.jsonl\"\n");
fs::write(&config_path, config_content)?;
Ok(())
}
async fn generate_graphql_template(
config: &WizardConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let examples_dir = config.project_dir.join("examples");
fs::create_dir_all(&examples_dir)?;
let schema_path = examples_dir.join("schema.graphql");
let schema_content = r#"type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
}
type User {
id: ID!
name: String!
email: String!
createdAt: String!
}
input CreateUserInput {
name: String!
email: String!
}
input UpdateUserInput {
name: String
email: String
}
"#;
fs::write(&schema_path, schema_content)?;
println!("{} Created {}", "✅".green(), schema_path.display());
let config_path = config.project_dir.join("mockforge.yaml");
let mut config_content = fs::read_to_string(&config_path)?;
config_content.push_str("\n# GraphQL Configuration\n");
config_content.push_str("graphql:\n");
config_content.push_str(" schema_file: \"./examples/schema.graphql\"\n");
fs::write(&config_path, config_content)?;
Ok(())
}
async fn generate_microservices_template(
config: &WizardConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
generate_rest_api_template(config).await?;
generate_grpc_template(config).await?;
generate_websocket_template(config).await?;
let examples_dir = config.project_dir.join("examples");
let kafka_dir = examples_dir.join("kafka");
fs::create_dir_all(&kafka_dir)?;
let kafka_fixture = kafka_dir.join("orders.yaml");
let kafka_content = r#"# Kafka fixture example
- identifier: "order-created"
topic: "orders.created"
key_pattern: "order-{{uuid}}"
value_template:
order_id: "{{uuid}}"
customer_id: "customer-{{faker.int 1000 9999}}"
total: "{{faker.float 10.0 1000.0 | round 2}}"
status: "pending"
created_at: "{{now}}"
"#;
fs::write(&kafka_fixture, kafka_content)?;
println!("{} Created {}", "✅".green(), kafka_fixture.display());
Ok(())
}
async fn generate_example_files(
config: &WizardConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let examples_dir = config.project_dir.join("examples");
fs::create_dir_all(&examples_dir)?;
let openapi_path = examples_dir.join("openapi.json");
let openapi_content = r##"{
"openapi": "3.0.0",
"info": {
"title": "Example API",
"version": "1.0.0"
},
"paths": {
"/health": {
"get": {
"summary": "Health check",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}"##;
fs::write(&openapi_path, openapi_content)?;
println!("{} Created {}", "✅".green(), openapi_path.display());
Ok(())
}
fn generate_readme(config: &WizardConfig) -> String {
let mut readme = String::from("# ");
readme.push_str(&config.project_name);
readme.push_str("\n\n");
readme.push_str("MockForge project generated by wizard.\n\n");
readme.push_str("## Quick Start\n\n");
readme.push_str("```bash\n");
readme.push_str("# Start the mock server\n");
readme.push_str("mockforge serve\n");
readme.push_str("```\n\n");
if config.enable_admin {
readme.push_str("## Admin UI\n\n");
readme.push_str("Access the Admin UI at: http://localhost:9080\n\n");
}
readme.push_str("## Protocols Enabled\n\n");
for protocol in &config.protocols {
readme.push_str("- ");
readme.push_str(protocol.name());
readme.push_str(" (port ");
readme.push_str(&protocol.port().to_string());
readme.push_str(")\n");
}
readme.push_str("\n## Documentation\n\n");
readme.push_str("For more information, visit: https://docs.mockforge.dev\n");
readme
}
pub fn detect_environment() -> Vec<String> {
let mut suggestions = Vec::new();
if Path::new("package.json").exists() {
suggestions.push("Node.js project detected - consider REST API template".to_string());
}
if Path::new("Cargo.toml").exists() {
suggestions.push("Rust project detected - consider gRPC template".to_string());
}
if Path::new("go.mod").exists() {
suggestions.push("Go project detected - consider gRPC template".to_string());
}
if Path::new("openapi.yaml").exists() || Path::new("openapi.json").exists() {
suggestions.push("OpenAPI specification found - will be used automatically".to_string());
}
if Path::new("schema.graphql").exists() {
suggestions.push("GraphQL schema found - GraphQL template recommended".to_string());
}
suggestions
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wizard_config_creation() {
let config = WizardConfig {
project_name: "test-project".to_string(),
project_dir: PathBuf::from("/tmp/test-project"),
protocols: vec![Protocol::Http, Protocol::WebSocket],
enable_admin: true,
enable_examples: true,
template: Some(Template::RestApi),
};
assert_eq!(config.project_name, "test-project");
assert_eq!(config.protocols.len(), 2);
assert!(config.enable_admin);
assert!(config.enable_examples);
}
#[test]
fn test_protocol_all() {
let protocols = Protocol::all();
assert_eq!(protocols.len(), 7);
assert!(protocols.contains(&Protocol::Http));
assert!(protocols.contains(&Protocol::WebSocket));
assert!(protocols.contains(&Protocol::Grpc));
assert!(protocols.contains(&Protocol::GraphQL));
assert!(protocols.contains(&Protocol::Kafka));
assert!(protocols.contains(&Protocol::Mqtt));
assert!(protocols.contains(&Protocol::Amqp));
}
#[test]
fn test_protocol_name() {
assert_eq!(Protocol::Http.name(), "HTTP/REST");
assert_eq!(Protocol::WebSocket.name(), "WebSocket");
assert_eq!(Protocol::Grpc.name(), "gRPC");
assert_eq!(Protocol::GraphQL.name(), "GraphQL");
assert_eq!(Protocol::Kafka.name(), "Kafka");
assert_eq!(Protocol::Mqtt.name(), "MQTT");
assert_eq!(Protocol::Amqp.name(), "AMQP/RabbitMQ");
}
#[test]
fn test_protocol_port() {
assert_eq!(Protocol::Http.port(), 3000);
assert_eq!(Protocol::WebSocket.port(), 3001);
assert_eq!(Protocol::Grpc.port(), 50051);
assert_eq!(Protocol::GraphQL.port(), 4000);
assert_eq!(Protocol::Kafka.port(), 9092);
assert_eq!(Protocol::Mqtt.port(), 1883);
assert_eq!(Protocol::Amqp.port(), 5672);
}
#[test]
fn test_template_all() {
let templates = Template::all();
assert_eq!(templates.len(), 5);
assert!(templates.contains(&Template::RestApi));
assert!(templates.contains(&Template::Grpc));
assert!(templates.contains(&Template::WebSocket));
assert!(templates.contains(&Template::GraphQL));
assert!(templates.contains(&Template::Microservices));
}
#[test]
fn test_template_name() {
assert_eq!(Template::RestApi.name(), "REST API");
assert_eq!(Template::Grpc.name(), "gRPC Service");
assert_eq!(Template::WebSocket.name(), "WebSocket Server");
assert_eq!(Template::GraphQL.name(), "GraphQL API");
assert_eq!(Template::Microservices.name(), "Microservices (Multi-Protocol)");
}
#[test]
fn test_template_description() {
assert_eq!(Template::RestApi.description(), "Standard REST API with CRUD operations");
assert_eq!(Template::Grpc.description(), "gRPC service with protobuf definitions");
assert_eq!(Template::WebSocket.description(), "Real-time WebSocket server");
assert_eq!(Template::GraphQL.description(), "GraphQL API with queries and mutations");
assert_eq!(
Template::Microservices.description(),
"Multiple protocols for microservices architecture"
);
}
#[test]
fn test_generate_config_yaml_http() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Http],
enable_admin: false,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# HTTP Server"));
assert!(yaml.contains("port: 3000"));
assert!(yaml.contains("cors_enabled: true"));
}
#[test]
fn test_generate_config_yaml_websocket() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::WebSocket],
enable_admin: false,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# WebSocket Server"));
assert!(yaml.contains("port: 3001"));
}
#[test]
fn test_generate_config_yaml_grpc() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Grpc],
enable_admin: false,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# gRPC Server"));
assert!(yaml.contains("port: 50051"));
}
#[test]
fn test_generate_config_yaml_graphql() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::GraphQL],
enable_admin: false,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# GraphQL Server"));
assert!(yaml.contains("port: 4000"));
}
#[test]
fn test_generate_config_yaml_kafka() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Kafka],
enable_admin: false,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# Kafka Broker"));
assert!(yaml.contains("port: 9092"));
assert!(yaml.contains("enabled: true"));
}
#[test]
fn test_generate_config_yaml_mqtt() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Mqtt],
enable_admin: false,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# MQTT Broker"));
assert!(yaml.contains("port: 1883"));
}
#[test]
fn test_generate_config_yaml_amqp() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Amqp],
enable_admin: false,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# AMQP Broker"));
assert!(yaml.contains("port: 5672"));
}
#[test]
fn test_generate_config_yaml_with_admin() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Http],
enable_admin: true,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# Admin UI"));
assert!(yaml.contains("enabled: true"));
assert!(yaml.contains("port: 9080"));
}
#[test]
fn test_generate_config_yaml_multiple_protocols() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Http, Protocol::Grpc, Protocol::Kafka],
enable_admin: true,
enable_examples: false,
template: None,
};
let yaml = generate_config_yaml(&config);
assert!(yaml.contains("# HTTP Server"));
assert!(yaml.contains("# gRPC Server"));
assert!(yaml.contains("# Kafka Broker"));
assert!(yaml.contains("# Admin UI"));
}
#[test]
fn test_generate_readme() {
let config = WizardConfig {
project_name: "my-api".to_string(),
project_dir: PathBuf::from("/tmp/my-api"),
protocols: vec![Protocol::Http, Protocol::WebSocket],
enable_admin: true,
enable_examples: true,
template: Some(Template::RestApi),
};
let readme = generate_readme(&config);
assert!(readme.contains("# my-api"));
assert!(readme.contains("MockForge project generated by wizard"));
assert!(readme.contains("mockforge serve"));
assert!(readme.contains("http://localhost:9080"));
assert!(readme.contains("HTTP/REST (port 3000)"));
assert!(readme.contains("WebSocket (port 3001)"));
}
#[test]
fn test_generate_readme_without_admin() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Http],
enable_admin: false,
enable_examples: false,
template: None,
};
let readme = generate_readme(&config);
assert!(!readme.contains("Admin UI"));
}
#[test]
fn test_detect_environment_nodejs() {
let suggestions = detect_environment();
assert!(suggestions.is_empty() || !suggestions.is_empty());
}
#[test]
fn test_protocol_equality() {
assert_eq!(Protocol::Http, Protocol::Http);
assert_ne!(Protocol::Http, Protocol::WebSocket);
assert_eq!(Protocol::Grpc, Protocol::Grpc);
}
#[test]
fn test_template_equality() {
assert_eq!(Template::RestApi, Template::RestApi);
assert_ne!(Template::RestApi, Template::Grpc);
assert_eq!(Template::GraphQL, Template::GraphQL);
}
#[test]
fn test_wizard_config_clone() {
let config1 = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Http],
enable_admin: true,
enable_examples: false,
template: Some(Template::RestApi),
};
let config2 = config1.clone();
assert_eq!(config1.project_name, config2.project_name);
assert_eq!(config1.project_dir, config2.project_dir);
assert_eq!(config1.enable_admin, config2.enable_admin);
}
#[test]
fn test_wizard_config_debug() {
let config = WizardConfig {
project_name: "test".to_string(),
project_dir: PathBuf::from("/tmp/test"),
protocols: vec![Protocol::Http],
enable_admin: true,
enable_examples: true,
template: None,
};
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("test"));
assert!(debug_str.contains("Http"));
}
#[test]
fn test_protocol_copy() {
let p1 = Protocol::Http;
let p2 = p1;
assert_eq!(p1, p2);
}
#[test]
fn test_template_copy() {
let t1 = Template::RestApi;
let t2 = t1;
assert_eq!(t1, t2);
}
}