rprobe 0.9.0

A simple tool to probe a remote host http or https connection
Documentation
// File: tests.rs
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2023-2025
// - Volker Schwaberow <volker@schwaberow.de>

use super::*;
use crate::httpinner::HttpInner;
use reqwest::header::{HeaderMap, HeaderValue};
use rstest::*;

fn create_test_http_inner(body: &str, headers: Vec<(&'static str, &'static str)>) -> HttpInner {
    let mut header_map = HeaderMap::new();
    for (key, value) in headers {
        header_map.insert(key, HeaderValue::from_str(value).unwrap());
    }

    HttpInner::new_with_all(
        header_map,
        body.to_string(),
        200,
        "https://example.com".to_string(),
        true,
    )
}

fn create_test_http_inner_dynamic(body: &str, key: &str, value: &str) -> HttpInner {
    let mut header_map = HeaderMap::new();
    header_map.insert(key, HeaderValue::from_str(value).unwrap());

    HttpInner::new_with_all(
        header_map,
        body.to_string(),
        200,
        "https://example.com".to_string(),
        true,
    )
}

#[cfg(test)]
mod plugin_handler_tests {
    use super::*;

    #[test]
    fn test_plugin_handler_initialization() {
        let handler = PluginHandler::new();
        let plugins = handler.list();
        assert!(!plugins.is_empty());
        assert!(plugins.contains(&"Wordpress Basic".to_string()));
        assert!(plugins.contains(&"Apache Basic".to_string()));
        assert!(plugins.contains(&"Nginx Basic".to_string()));
        assert!(plugins.contains(&"Laravel".to_string()));
        assert!(plugins.contains(&"PHP Basic".to_string()));
        assert!(plugins.contains(&"Cloudflare Basic".to_string()));
    }

    #[test]
    fn test_plugin_handler_run_empty_response() {
        let handler = PluginHandler::new();
        let http_inner = create_test_http_inner("", vec![]);
        let results = handler.run(&http_inner);
        assert!(results.is_empty() || results.iter().all(|r| !r.contains("Detected")));
    }

    #[test]
    fn test_plugin_handler_run_wordpress_detection() {
        let handler = PluginHandler::new();
        let http_inner = create_test_http_inner(
            r#"<html><head><meta name="generator" content="WordPress 6.0"></head></html>",
            vec![],
        );
        let results = handler.run(&http_inner);
        let wordpress_result = results.iter().find(|r| r.contains("Wordpress Basic"));
        assert!(wordpress_result.is_some());
        assert!(wordpress_result.unwrap().contains("WordPress Detected"));
    }

    #[test]
    fn test_plugin_handler_multiple_detections() {
        let handler = PluginHandler::new();
        let http_inner = create_test_http_inner(
            r#"<html>
                <head><meta name="generator" content="WordPress 6.0"></head>
                <body>
                    <div class="wp-content"></div>
                    <!-- Laravel app -->
                </body>
            </html>",
            vec![("server", "Apache/2.4.41")],
        );
        let results = handler.run(&http_inner);
        assert!(results.len() >= 2); 
    }
}

#[cfg(test)]
mod wordpress_plugin_tests {
    use super::*;
    use wordpressbasic::WordpressBasicPlugin;

    #[rstest]
    #[case(r#"<meta name="generator" content="WordPress 6.0">", "Meta Generator")]
    #[case(
        r#"<link href="/wp-content/themes/twentytwenty/style.css">",
        "WP Content"
    )]
    #[case(r#"<script src="/wp-includes/js/jquery.js"></script>", "WP Includes")]
    #[case(
        r#"<link rel='https://api.w.org/' href='https://example.com/wp-json/' />",
        "WordPress API Link"
    )]
    fn test_wordpress_detection_patterns(#[case] html: &str, #[case] expected_detection: &str) {
        let plugin = WordpressBasicPlugin;
        let http_inner = create_test_http_inner(html, vec![]);
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains(expected_detection));
    }

    #[test]
    fn test_wordpress_multiple_patterns() {
        let plugin = WordpressBasicPlugin;
        let http_inner = create_test_http_inner(
            r#"<html>
                <meta name="generator" content="WordPress 6.0">
                <link href="/wp-content/themes/style.css">
                <script src="/wp-includes/js/jquery.js"></script>
            </html>",
            vec![],
        );
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        let detection = result.unwrap();
        assert!(detection.contains("Meta Generator"));
        assert!(detection.contains("WP Content"));
        assert!(detection.contains("WP Includes"));
    }

    #[test]
    fn test_wordpress_no_detection() {
        let plugin = WordpressBasicPlugin;
        let http_inner = create_test_http_inner("<html><body>Regular HTML</body></html>", vec![]);
        let result = plugin.run(&http_inner);
        assert!(result.is_none());
    }

    #[test]
    fn test_wordpress_case_insensitive() {
        let plugin = WordpressBasicPlugin;
        let http_inner =
            create_test_http_inner(r#"<META NAME="GENERATOR" CONTENT="WordPress 6.0">", vec![]);
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
    }
}

#[cfg(test)]
mod apache_plugin_tests {
    use super::*;
    use apachebasic::ApacheBasicPlugin;

    #[rstest]
    #[case("Apache", true)]
    #[case("Apache/2.4.41", true)]
    #[case("Apache/2.4.41 (Ubuntu)", true)]
    #[case("nginx", false)]
    #[case("Microsoft-IIS/10.0", false)]
    fn test_apache_server_header_detection(
        #[case] server_value: &str,
        #[case] should_detect: bool,
    ) {
        let plugin = ApacheBasicPlugin;
        let http_inner = create_test_http_inner_dynamic("", "server", server_value);
        let result = plugin.run(&http_inner);
        assert_eq!(result.is_some(), should_detect);
        if should_detect {
            assert!(result.unwrap().contains("Apache"));
        }
    }

    #[test]
    fn test_apache_body_detection() {
        let plugin = ApacheBasicPlugin;
        let http_inner = create_test_http_inner("<!-- Powered by Apache/2.4.41 -->", vec![]);
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains("Apache"));
    }
}

#[cfg(test)]
mod nginx_plugin_tests {
    use super::*;
    use nginxbasic::NginxBasicPlugin;

    #[rstest]
    #[case("nginx", true)]
    #[case("nginx/1.18.0", true)]
    #[case("nginx/1.18.0 (Ubuntu)", true)]
    #[case("Apache", false)]
    #[case("cloudflare", false)]
    fn test_nginx_server_header_detection(#[case] server_value: &str, #[case] should_detect: bool) {
        let plugin = NginxBasicPlugin;
        let http_inner = create_test_http_inner_dynamic("", "server", server_value);
        let result = plugin.run(&http_inner);
        assert_eq!(result.is_some(), should_detect);
        if should_detect {
            assert!(result.unwrap().contains("Nginx"));
        }
    }
}

#[cfg(test)]
mod laravel_plugin_tests {
    use super::*;
    use laravel::LaravelPlugin;

    #[test]
    fn test_laravel_cookie_detection() {
        let plugin = LaravelPlugin;
        let http_inner = create_test_http_inner(
            "",
            vec![("set-cookie", "laravel_session=abc123; path=/; httponly")],
        );
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains("Laravel"));
    }

    #[test]
    fn test_laravel_csrf_token_detection() {
        let plugin = LaravelPlugin;
        let http_inner =
            create_test_http_inner(r#"<meta name="csrf-token" content="abc123">", vec![]);
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains("Laravel"));
    }

    #[test]
    fn test_laravel_multiple_indicators() {
        let plugin = LaravelPlugin;
        let http_inner = create_test_http_inner(
            r#"<html>
                <meta name="csrf-token" content="abc123">
                <body class="laravel">
                    <!-- Laravel app content -->
                </body>
            </html>",
            vec![("set-cookie", "laravel_session=xyz; path=/")],
        );
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        let detection = result.unwrap();
        assert!(detection.contains("Laravel"));
        assert!(detection.contains("Multiple indicators"));
    }
}

#[cfg(test)]
mod php_plugin_tests {
    use super::*;
    use phpbasic::PHPBasicPlugin;

    #[rstest]
    #[case("PHP/7.4.3", true)]
    #[case("PHP/8.0.0", true)]
    #[case("Python/3.8", false)]
    fn test_php_x_powered_by_detection(#[case] powered_by: &str, #[case] should_detect: bool) {
        let plugin = PHPBasicPlugin;
        let http_inner = create_test_http_inner_dynamic("", "x-powered-by", powered_by);
        let result = plugin.run(&http_inner);
        assert_eq!(result.is_some(), should_detect);
        if should_detect {
            let result_str = result.unwrap();
            assert!(result_str.contains("PHP"));
            assert!(result_str.contains(powered_by));
        }
    }

    #[test]
    fn test_php_session_cookie_detection() {
        let plugin = PHPBasicPlugin;
        let http_inner =
            create_test_http_inner("", vec![("set-cookie", "PHPSESSID=abc123; path=/")]);
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains("PHP"));
    }
}

#[cfg(test)]
mod cloudflare_plugin_tests {
    use super::*;
    use cloudflarebasic::CloudflareBasicPlugin;

    #[test]
    fn test_cloudflare_server_detection() {
        let plugin = CloudflareBasicPlugin;
        let http_inner = create_test_http_inner("", vec![("server", "cloudflare")]);
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains("Cloudflare"));
    }

    #[test]
    fn test_cloudflare_ray_id_detection() {
        let plugin = CloudflareBasicPlugin;
        let http_inner = create_test_http_inner("", vec![("cf-ray", "7a1b2c3d4e5f6789-SJC")]);
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains("Cloudflare"));
    }

    #[test]
    fn test_cloudflare_multiple_headers() {
        let plugin = CloudflareBasicPlugin;
        let http_inner = create_test_http_inner(
            "",
            vec![
                ("server", "cloudflare"),
                ("cf-ray", "7a1b2c3d4e5f6789-SJC"),
                ("cf-cache-status", "HIT"),
            ],
        );
        let result = plugin.run(&http_inner);
        assert!(result.is_some());
        assert!(result.unwrap().contains("Cloudflare"));
    }
}

struct MockPlugin {
    name: &'static str,
    should_detect: bool,
}

impl Plugin for MockPlugin {
    fn name(&self) -> &'static str {
        self.name
    }

    fn run(&self, _http_inner: &HttpInner) -> Option<String> {
        if self.should_detect {
            Some(format!("{} detected", self.name))
        } else {
            None
        }
    }
}

#[test]
fn test_custom_plugin_implementation() {
    let plugin = MockPlugin {
        name: "Custom Plugin",
        should_detect: true,
    };
    let http_inner = create_test_http_inner("", vec![]);
    let result = plugin.run(&http_inner);
    assert!(result.is_some());
    assert_eq!(result.unwrap(), "Custom Plugin detected");
}

#[test]
fn test_plugin_trait_object() {
    let plugins: Vec<Box<dyn Plugin + Send + Sync>> = vec![
        Box::new(MockPlugin {
            name: "Plugin1",
            should_detect: true,
        }),
        Box::new(MockPlugin {
            name: "Plugin2",
            should_detect: false,
        }),
    ];

    let http_inner = create_test_http_inner("", vec![]);
    let results: Vec<_> = plugins.iter().filter_map(|p| p.run(&http_inner)).collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0], "Plugin1 detected");
}