diff --git a/e2etests/.gitignore b/e2etests/.gitignore new file mode 100644 index 0000000000..e2644ec46e --- /dev/null +++ b/e2etests/.gitignore @@ -0,0 +1,2 @@ +qcli_test_summary*.json +qcli_test_summary*.html \ No newline at end of file diff --git a/e2etests/Cargo.toml b/e2etests/Cargo.toml index caadc7a17e..d446e62d5f 100644 --- a/e2etests/Cargo.toml +++ b/e2etests/Cargo.toml @@ -9,32 +9,33 @@ edition = "2021" expectrl = "0.7" [features] -default = [] -core_session = [] -agent = [] -context = [] -save_load = [] -model = [] -session_mgmt = [] -integration = [] -mcp = [] -ai_prompts = [] -issue_reporting = [] -tools=[] -compact=[] -hooks=[] -usage=[] -editor=[] -subscribe=[] - -[[test]] -name = "test_help_command" -path = "tests/test_help_command.rs" - -[[test]] -name = "test_tools_command" -path = "tests/test_tools_command.rs" - -[[test]] -name = "test_ai_prompt" -path = "tests/test_ai_prompt.rs" +core_session = ["help", "quit", "clear"] # Core Session Commands (/help, /quit, /clear) +help = [] # Help Command (/help) +quit = [] # Quit Command (/quit) +clear = [] # Clear Command (/clear) + +tools = [] # Tools Command (/tools) +agent = [] # Agent Commands (/agent list, /agent create, etc.) +context = [] # Context Commands (/context show, /context add, etc.) +save_load = [] # Save/Load Commands (/save, /load, help) +model = [] # Model Commands (/model, /model --help) + +session_mgmt = ["compact", "usage"] # Session Management Commands (/compact, /usage, help) +compact = [] # Compact Commands +usage = [] # Usage Commands + +integration = ["subscribe", "hooks", "editor", "issue_reporting"] # Integration Commands (/subscribe, /hooks, /editor, /issue help) +subscribe = [] # Subscribe Commands +hooks = [] # Hooks Commands +editor = [] # Editor Commands +issue_reporting = [] # Issue Reporting Commands + +mcp = [] # MCP Commands (/mcp, /mcp --help) +ai_prompts = [] # AI Prompts ("What is AWS?", "Hello") + +q_subcommand = [] # Q SubCommand (q chat, q doctor, q translate) + +todos = [] # todos command + +regression = [] # Regression Tests +sanity = [] # Sanity Tests - Quick smoke tests for basic functionality diff --git a/e2etests/html_template.html b/e2etests/html_template.html new file mode 100644 index 0000000000..9247f2ce13 --- /dev/null +++ b/e2etests/html_template.html @@ -0,0 +1,246 @@ + + + + + + Q CLI Test Report + + + +
+
+

πŸ§ͺ Q CLI E2E Test Report

+

Generated: {timestamp}

+
+ +
+

πŸ“Š Summary

+
+ +
+
+
+ Total Tests +
+
+
+ Passed Tests +
+
+
+
+
+
+
+

Features Tested

+

{total_features}

+
+
+

Tests Passed

+

{tests_passed}

+
+
+

Tests Failed

+

{tests_failed}

+
+
+
+
+
+ + {test_suites_content} + +
+

πŸ’» System Information

+

Platform: {platform}

+ +

Q Binary: {q_binary_info}

+
+
+ + + + \ No newline at end of file diff --git a/e2etests/run_mcp_clean.sh b/e2etests/run_mcp_clean.sh index 7a6b9ae20a..e7846eb788 100755 --- a/e2etests/run_mcp_clean.sh +++ b/e2etests/run_mcp_clean.sh @@ -16,7 +16,7 @@ echo "" # Run only the specific MCP test files echo "πŸ”„ Running MCP tests..." -cargo test --test test_mcp_help_command --test test_mcp_loading_command --features "mcp" -- --nocapture --test-threads=1 +cargo test --test --features "mcp" -- --nocapture --test-threads=1 exit_code=$? diff --git a/e2etests/run_simple_categorized.sh b/e2etests/run_simple_categorized.sh index 8bf09c5cb8..f031731696 100755 --- a/e2etests/run_simple_categorized.sh +++ b/e2etests/run_simple_categorized.sh @@ -15,13 +15,14 @@ RUN_SESSION_MGMT=true RUN_INTEGRATION=true RUN_MCP=true RUN_AI_PROMPTS=true -RUN_ISSUE_REPORTING=true -RUN_TOOLS=true -RUN_COMPACT=true -RUN_HOOKS=true -RUN_USAGE=true -RUN_EDITOR=true -RUN_SUBSCRIBE=true +#TODO: check and remove not required features from below +#RUN_ISSUE_REPORTING=true +#RUN_TOOLS=true +#RUN_COMPACT=true +#RUN_HOOKS=true +#RUN_USAGE=true +#RUN_EDITOR=true +#RUN_SUBSCRIBE=true # ============================================================================ Q_BINARY="q" @@ -77,7 +78,7 @@ run_category() { if [ "$QUIET_MODE" = false ]; then # Quiet mode - show individual test results in real-time - cargo test --tests --features "$category" -- --test-threads=1 2>&1 | while IFS= read -r line; do + cargo test --tests --features "$category" --features "regression" -- --test-threads=1 2>&1 | while IFS= read -r line; do if echo "$line" | grep -q "test .* \.\.\. ok$"; then test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. ok/\1/') echo " βœ… $test_name" @@ -102,7 +103,7 @@ run_category() { fi else # Verbose mode - show full output with real-time test results - cargo test --tests --features "$category" -- --nocapture --test-threads=1 2>&1 | while IFS= read -r line; do + cargo test --tests --features "$category" --features "regression" -- --nocapture --test-threads=1 2>&1 | while IFS= read -r line; do echo "$line" if echo "$line" | grep -q "test .* \.\.\. ok$"; then test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. ok/\1/') @@ -220,45 +221,46 @@ if [ "$RUN_TOOLS" = true ]; then fi fi -if [ "$RUN_COMPACT" = true ]; then - if run_category "compact" "COMPACT"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_HOOKS" = true ]; then - if run_category "hooks" "HOOKS"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_USAGE" = true ]; then - if run_category "usage" "USAGE"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_EDITOR" = true ]; then - if run_category "editor" "EDITOR"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_SUBSCRIBE" = true ]; then - if run_category "subscribe" "SUBSCRIBE"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi +#TODO: check and remove not required features from below +# if [ "$RUN_COMPACT" = true ]; then +# if run_category "compact" "COMPACT"; then +# ((total_passed++)) +# else +# ((total_failed++)) +# fi +# fi +# +# if [ "$RUN_HOOKS" = true ]; then +# if run_category "hooks" "HOOKS"; then +# ((total_passed++)) +# else +# ((total_failed++)) +# fi +# fi +# +# if [ "$RUN_USAGE" = true ]; then +# if run_category "usage" "USAGE"; then +# ((total_passed++)) +# else +# ((total_failed++)) +# fi +# fi +# +# if [ "$RUN_EDITOR" = true ]; then +# if run_category "editor" "EDITOR"; then +# ((total_passed++)) +# else +# ((total_failed++)) +# fi +# fi +# +# if [ "$RUN_SUBSCRIBE" = true ]; then +# if run_category "subscribe" "SUBSCRIBE"; then +# ((total_passed++)) +# else +# ((total_failed++)) +# fi +# fi # Final summary echo "" diff --git a/e2etests/run_tests.py b/e2etests/run_tests.py new file mode 100755 index 0000000000..f02eaadd07 --- /dev/null +++ b/e2etests/run_tests.py @@ -0,0 +1,649 @@ +#!/usr/bin/env python3 + +import toml +import subprocess +import sys +import argparse +import json +import time +import platform +import re +import threading +from datetime import datetime +from pathlib import Path + +def show_spinner(stop_event): + """Show rotating spinner animation""" + spinner = ['|', '/', '-', '\\'] + while not stop_event.is_set(): + for char in spinner: + if stop_event.is_set(): + break + print(f"\rExecuting... {char}", end="", flush=True) + time.sleep(0.1) + +def strip_ansi(text): + """Remove ANSI escape sequences from text""" + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + return ansi_escape.sub('', text) + +def parse_features(): + """Parse features from Cargo.toml, handling grouped features correctly""" + cargo_toml = toml.load("Cargo.toml") + features = cargo_toml.get("features", {}) + + # Features to always exclude from individual runs + excluded_features = {"default", "regression", "sanity"} + + # Group features (features that contain other features) + grouped_features = {} + grouped_sub_features = set() + child_features = set() + + # First pass: identify grouped features and their sub-features + for feature_name, feature_deps in features.items(): + if feature_name in excluded_features: + continue + + if isinstance(feature_deps, list) and feature_deps: + # This is a grouped feature + grouped_features[feature_name] = feature_deps + grouped_sub_features.update(feature_deps) + child_features.update(feature_deps) + + # Second pass: identify standalone features (not part of any group) + standalone_features = [] + for feature_name in features.keys(): + if (feature_name not in excluded_features and + feature_name not in grouped_features and + feature_name not in grouped_sub_features): + standalone_features.append(feature_name) + + return grouped_features, standalone_features, child_features + +# Default test suite - always required for cargo test +DEFAULT_TESTSUITE = "sanity" + +def parse_test_results(stdout): + """Parse individual test results from cargo output with their outputs and descriptions""" + tests = [] + lines = stdout.split('\n') + + # Look for test lines followed by result lines + for i, line in enumerate(lines): + clean_line = line.strip() + + # Look for test declaration lines + if clean_line.startswith('test ') and ' ...' in clean_line: + # Extract test name (everything between 'test ' and ' ... ') + test_name = clean_line.split(' ... ')[0].replace('test ', '').strip() + + # Look ahead for the result (ok/FAILED) in the next few lines + status = None + result_line_idx = None + description = "" + + # Check all remaining lines for result + for j in range(i + 1, len(lines)): + result_line = lines[j].strip() + if result_line == 'ok': + status = "passed" + result_line_idx = j + break + elif result_line == 'FAILED': + status = "failed" + result_line_idx = j + break + + # If we found a result, add the test + if status and test_name: + # Collect output between test declaration and result + output_lines = [clean_line] + if result_line_idx: + for k in range(i + 1, result_line_idx + 1): + if k < len(lines): + line_content = lines[k].strip() + output_lines.append(line_content) + + # Extract description from the full output + full_output = '\n'.join(output_lines) + if "πŸ” Testing" in full_output and "| Description:" in full_output: + # Find the line with the description + for line in output_lines: + if "πŸ” Testing" in line and "| Description:" in line: + description = line.split("| Description:")[1].strip() + break + + tests.append({ + "name": test_name, + "status": status, + "output": strip_ansi('\n'.join(output_lines)), # Full output + "description": description + }) + + return tests + +def run_single_cargo_test(feature, test_suite, binary_path="q", quiet=False): + """Run cargo test for a single feature with test suite""" + feature_str = f"{feature},{test_suite}" + cmd = ["cargo", "test", "--tests", "--features", feature_str, "--", "--nocapture", "--test-threads=1"] + + if quiet: + print(f"πŸ”„ Running: {feature} with {test_suite}") + else: + print(f"πŸ”„ Running: {feature} with {test_suite}") + print(f"Command: {' '.join(cmd)}") + + # Start rotating animation + stop_animation = threading.Event() + animation_thread = threading.Thread(target=show_spinner, args=(stop_animation,)) + animation_thread.start() + + start_time = time.time() + result = subprocess.run(cmd, capture_output=True, text=True) + end_time = time.time() + + # Stop animation + stop_animation.set() + animation_thread.join() + print("\r", end="") # Clear spinner line + + # Parse individual test results + individual_tests = parse_test_results(result.stdout) + + if not quiet: + print(result.stdout) + if result.stderr: + print(result.stderr) + + # Show individual test results + print(f"\nπŸ“‹ Individual Test Results for {feature}:") + if individual_tests: + for test in individual_tests: + status_icon = "βœ…" if test["status"] == "passed" else "❌" + print(f" {status_icon} {test['name']} - {test['status']}") + else: + print(f" ⚠️ No individual tests detected (parsing may have failed)") + print(f" Debug: Looking for 'test ' lines in output...") + lines = result.stdout.split('\n') + test_lines = [line for line in lines if 'test ' in line and ' ... ' in line] + print(f" Found {len(test_lines)} potential test lines:") + for line in test_lines[:3]: # Show first 3 + print(f" {repr(line.strip())}") + + return { + "feature": feature, + "test_suite": test_suite, + "success": result.returncode == 0, + "duration": round(end_time - start_time, 2), + "stdout": strip_ansi(result.stdout), + "stderr": strip_ansi(result.stderr), + "command": " ".join(cmd), + "individual_tests": individual_tests + } + +def validate_features(features): + """Validate that all features exist in Cargo.toml""" + grouped_features, standalone_features, child_features = parse_features() + valid_features = set(grouped_features.keys()) | set(standalone_features) | child_features + invalid_features = [f for f in features if f not in valid_features and f not in {"sanity", "regression"}] + if invalid_features: + print(f"❌ Error: Invalid feature(s): {', '.join(invalid_features)}") + print(f"Available features: {', '.join(sorted(valid_features))}") + sys.exit(1) + +def get_test_suites_from_features(features): + """Extract test suites (sanity/regression) from feature list""" + test_suites = [] + if "sanity" in features: + test_suites.append("sanity") + if "regression" in features: + test_suites.append("regression") + + # Check if both sanity and regression are specified + if len(test_suites) > 1: + print("❌ Error: Only a single test suite is allowed. Cannot run both 'sanity' and 'regression' together.") + sys.exit(1) + + if not test_suites: + test_suites = [DEFAULT_TESTSUITE] + + return test_suites + +def run_tests_with_suites(features, test_suites, binary_path="q", quiet=False): + """Run tests for each feature with each test suite""" + results = [] + + for test_suite in test_suites: + print(f"\nπŸ§ͺ Running {test_suite.capitalize()} Test Suite") + print("=" * 40) + + for feature in features: + if feature not in {"sanity", "regression"}: + result = run_single_cargo_test(feature, test_suite, binary_path, quiet) + results.append(result) + + individual_tests = result.get("individual_tests", []) + passed_count = sum(1 for t in individual_tests if t["status"] == "passed") + failed_count = sum(1 for t in individual_tests if t["status"] == "failed") + + status = "βœ…" if result["success"] else "❌" + if individual_tests: + print(f"{status} {feature} ({test_suite}) - {result['duration']}s - {passed_count} passed, {failed_count} failed") + else: + print(f"{status} {feature} ({test_suite}) - {result['duration']}s - No individual tests detected") + + return results + +def get_system_info(binary_path="q"): + """Get Q binary version and system information""" + system_info = { + "os": platform.system(), + "os_version": platform.version(), + "platform": platform.platform(), + "python_version": platform.python_version(), + "q_binary_path": binary_path + } + + # Try to get Q binary version + try: + result = subprocess.run([binary_path, "--version"], capture_output=True, text=True, timeout=10) + if result.returncode == 0: + system_info["q_version"] = result.stdout.strip() + else: + system_info["q_version"] = "Unable to determine version" + except Exception as e: + system_info["q_version"] = f"Error getting version: {str(e)}" + + return system_info + +def generate_report(results, features, test_suites, binary_path="q"): + """Generate JSON report and console summary""" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + system_info = get_system_info(binary_path) + + # Create reports directory if it doesn't exist + reports_dir = Path("reports") + reports_dir.mkdir(exist_ok=True) + + # Calculate summary stats from individual tests + total_individual_tests = 0 + passed_individual_tests = 0 + failed_individual_tests = 0 + + # Group by feature with individual test details + feature_summary = {} + for result in results: + feature = result["feature"] + if feature not in feature_summary: + feature_summary[feature] = { + "passed": 0, + "failed": 0, + "test_suites": [], + "individual_tests": [] + } + + # Count individual tests + individual_tests = result.get("individual_tests", []) + feature_passed = sum(1 for t in individual_tests if t["status"] == "passed") + feature_failed = sum(1 for t in individual_tests if t["status"] == "failed") + + feature_summary[feature]["passed"] += feature_passed + feature_summary[feature]["failed"] += feature_failed + feature_summary[feature]["test_suites"].append(result["test_suite"]) + feature_summary[feature]["individual_tests"].extend(individual_tests) + + total_individual_tests += len(individual_tests) + passed_individual_tests += feature_passed + failed_individual_tests += feature_failed + + # Create JSON report + report = { + "timestamp": timestamp, + "system_info": system_info, + "summary": { + "total_feature_runs": len(results), + "total_individual_tests": total_individual_tests, + "passed": passed_individual_tests, + "failed": failed_individual_tests, + "success_rate": round((passed_individual_tests / total_individual_tests * 100) if total_individual_tests > 0 else 0, 2) + }, + "features": feature_summary, + "detailed_results": results + } + + # Generate filename with features and test suites + # If running all features (sanity/regression mode), use only test suite names + grouped_features, standalone_features, _ = parse_features() + all_available_features = list(grouped_features.keys()) + standalone_features + + if set(features) == set(all_available_features): + # Running all features - use only test suite names + features_str = "-".join(test_suites) + else: + # Running specific features - include feature names + features_str = "-".join(features[:3]) + ("_more" if len(features) > 3 else "") + features_str += "_" + "-".join(test_suites) + + datetime_str = datetime.now().strftime("%m%d%y%H%M%S") + filename = reports_dir / f"qcli_test_summary_{features_str}_{datetime_str}.json" + + # Save JSON report + with open(filename, "w") as f: + json.dump(report, f, indent=2) + + report["filename"] = str(filename) + return report + +def generate_html_report(json_filename): + """Generate HTML report from JSON file using template""" + with open(json_filename, 'r') as f: + report = json.load(f) + + # Load HTML template + template_path = Path(__file__).parent / 'html_template.html' + with open(template_path, 'r') as f: + html_template = f.read() + + # Generate HTML filename in reports directory + json_path = Path(json_filename) + html_filename = json_path.with_suffix('.html') + + # Calculate stats + total_features = len(report["features"]) + features_100_pass = sum(1 for stats in report["features"].values() if stats["failed"] == 0) + features_failed = total_features - features_100_pass + + # Get test suites from detailed results + test_suites = list(set(result["test_suite"] for result in report["detailed_results"])) + + # Generate test suites content + test_suites_content = "" + for suite in test_suites: + suite_features = {} + for result in report["detailed_results"]: + if result["test_suite"] == suite: + feature = result["feature"] + if feature not in suite_features: + suite_features[feature] = report["features"][feature] + + suite_passed = sum(stats["passed"] for stats in suite_features.values()) + suite_failed = sum(stats["failed"] for stats in suite_features.values()) + suite_rate = round((suite_passed / (suite_passed + suite_failed) * 100) if (suite_passed + suite_failed) > 0 else 0, 2) + + suite_failed_class = ' collapsible-failed' if suite_failed > 0 else '' + test_suites_content += f'
' + + # Add features for this suite (sorted alphabetically) + for feature_name, feature_stats in sorted(suite_features.items()): + feature_rate = round((feature_stats["passed"] / (feature_stats["passed"] + feature_stats["failed"]) * 100) if (feature_stats["passed"] + feature_stats["failed"]) > 0 else 0, 2) + + # Format feature name: remove underscores and capitalize first letter + formatted_feature_name = feature_name.replace('_', ' ').title() + failed_class = ' collapsible-failed' if feature_stats["failed"] > 0 else '' + test_suites_content += f'
' + + # Add individual tests + individual_tests = feature_stats.get("individual_tests", []) + for test in individual_tests: + test_class = "test-passed" if test["status"] == "passed" else "test-failed" + status_icon = "βœ…" if test["status"] == "passed" else "❌" + + # Convert test name to readable format + test_name = test['name'] + if '::' in test_name: + readable_name = test_name.split('::')[-1] + if readable_name.startswith('test_'): + readable_name = readable_name[5:] + readable_name = ' '.join(word.capitalize() for word in readable_name.split('_')) + else: + readable_name = test_name + + test_output = strip_ansi(test.get('output', 'No output captured')) + test_description = test.get('description', '') + description_html = f'

{test_description}

' if test_description else '' + test_suites_content += f'

{status_icon} {readable_name}

{description_html}

Status: {test["status"].upper()}

{test_output}
' + + # Add stdout/stderr for this feature + for result in report["detailed_results"]: + if result["feature"] == feature_name and result["test_suite"] == suite: + stderr_content = f'
STDERR:
{strip_ansi(result["stderr"])}
' if result['stderr'] else '' + test_suites_content += f'

Command: {result["command"]}

Duration: {result["duration"]}s

{strip_ansi(result["stdout"])}
{stderr_content}
' + + test_suites_content += "
" # Close feature content + + test_suites_content += "
" # Close suite content + + # Prepare histogram data (sorted alphabetically) + sorted_features = sorted(report['features'].items()) + feature_names = [name for name, _ in sorted_features] + feature_total_tests = [stats['passed'] + stats['failed'] for _, stats in sorted_features] + feature_passed_tests = [stats['passed'] for _, stats in sorted_features] + + # Fill template with data + html_content = html_template.format( + timestamp=report['timestamp'], + success_rate=report['summary']['success_rate'], + total_features=total_features, + features_100_pass=features_100_pass, + features_failed=features_failed, + tests_passed=report['summary']['passed'], + tests_failed=report['summary']['failed'], + test_suites_content=test_suites_content, + platform=report['system_info']['platform'], + q_binary_info=f"{report['system_info']['q_binary_path']} ({report['system_info']['q_version']})", + feature_names=json.dumps(feature_names), + feature_total_tests=json.dumps(feature_total_tests), + feature_passed_tests=json.dumps(feature_passed_tests), + ) + + with open(html_filename, 'w') as f: + f.write(html_content) + + return html_filename + +def print_summary(report, quiet=False): + """Print beautified console summary""" + # Print system info + if not quiet: + print("\nπŸ’» System Information:") + print(f" Platform: {report['system_info']['platform']}") + print(f" OS: {report['system_info']['os']} {report['system_info']['os_version']}") + print(f" Q Binary: {report['system_info']['q_binary_path']}") + print(f" Q Version: {report['system_info']['q_version']}") + + print("\nπŸ“‹ Feature Summary:") + for feature, stats in report["features"].items(): + status = "βœ…" if stats["failed"] == 0 else "❌" + suites = ",".join(set(stats["test_suites"])) + print(f" {status} {feature} ({suites}): {stats['passed']} passed, {stats['failed']} failed") + + # Show individual test details + for test in stats["individual_tests"]: + test_status = "βœ…" if test["status"] == "passed" else "❌" + print(f" {test_status} {test['name']}") + + # Calculate feature-level stats + total_features = len(report["features"]) + features_100_pass = sum(1 for stats in report["features"].values() if stats["failed"] == 0) + features_failed = total_features - features_100_pass + + print("\n🎯 FINAL SUMMARY") + print("=" * 32) + print(f"🏷️ Features Tested: {total_features}") + # print(f"πŸ”„ Feature Runs: {report['summary']['total_feature_runs']}") + print(f"βœ… Features 100% Pass: {features_100_pass}") + print(f"❌ Features with Failures: {features_failed}") + print(f"βœ… Individual Tests Passed: {report['summary']['passed']}") + print(f"❌ Individual Tests Failed: {report['summary']['failed']}") + print(f"πŸ“Š Total Individual Tests: {report['summary']['total_individual_tests']}") + print(f"πŸ“ˆ Success Rate: {report['summary']['success_rate']}%") + + + if report["summary"]["failed"] == 0: + print("\nπŸŽ‰ All tests passed!") + else: + print("\nπŸ’₯ Some tests failed!") + + print(f"\nπŸ“„ Detailed report saved to: {report['filename']}") + + # Generate HTML report + html_filename = generate_html_report(report['filename']) + print(f"🌐 HTML report saved to: {html_filename}") + +def dev_debug(): + """Debug function to show parsed features""" + print("πŸ”§ Developer Debug Mode") + print("=" * 30) + + grouped_features, standalone_features, child_features = parse_features() + + print("\nπŸ“¦ Grouped Features:") + for group, deps in grouped_features.items(): + print(f" {group} = {deps}") + + print("\nπŸ”Ή Standalone Features:") + for feature in standalone_features: + print(f" {feature}") + + print(f"\nπŸ”Έ Sub Features:") + for feature in sorted(child_features): + print(f" {feature}") + + print(f"\nπŸ“Š Summary:") + print(f" Grouped: {len(grouped_features)}") + print(f" Standalone: {len(standalone_features)}") + print(f" Sub: {len(child_features)}") + print(f" Total: {len(grouped_features) + len(standalone_features) + len(child_features)}") + +def main(): + parser = argparse.ArgumentParser( + description=""" + +Q CLI E2E Test Framework - Python script for comprehensive Amazon Q CLI testing + +This Python script executes end-to-end tests organized into functional feature categories. +Default test suite is 'sanity' providing core functionality validation. +You can also specify 'regression' suite for extended testing (currently no tests added under regression). +Test execution automatically generates both JSON and HTML reports under the reports directory for detailed analysis. +JSON reports contain raw test data, system info, and execution details for programmatic use. +HTML reports provide visual dashboards with charts, summaries, and formatted test results. +Report filenames follow syntax: q_cli_e2e_report_{features}_{suite}_{timestamp}.json/html +Example sanity reports: q_cli_e2e_report_sanity_082825232555.json, example regression: q_cli_e2e_report_regression_082825232555.html + +Additional Features: + β€’ JSON to HTML conversion: Convert JSON test reports to visual HTML dashboards + β€’ Feature discovery: Automatically detect available test features and list the available features + β€’ Multiple test suites: Support for sanity and regression test categories + β€’ Flexible feature selection: Run individual or grouped features + β€’ Comprehensive reporting: Generate both JSON and HTML reports with charts + +Options: + -h, --help Show this help message and exit + --features Comma-separated list of features (Check example section) + --binary Path to Q-CLI binary. If not provided, script will use default "q" (Q-CLI installed on the system) + --quiet Quiet mode - reduces console output by hiding system info, cargo commands, and test details while preserving complete data in generated reports + --list-features List all available features (Check example section) + --json-to-html Convert JSON report (previously generated by running test) to HTML (Check example section) + +Syntax: + run_tests.py [-h] [--features ] [--binary ] [--quiet] [--list-features] [--json-to-html ] + +Usage: + %(prog)s [options] # Run tests with default settings + %(prog)s --features # Run specific features + %(prog)s --list-features # List available features + %(prog)s --json-to-html # Convert JSON report to HTML (provide JSON file path)""", + epilog="""Examples: + # Basic usage + %(prog)s # Run all tests with default sanity suite + %(prog)s --features usage # Run usage tests with default sanity suite + %(prog)s --features "usage,agent" # Run usage+agent tests with default sanity suite + + # Test suites + %(prog)s --features sanity # Run all tests with sanity suite + %(prog)s --features regression # Run all tests with regression suite + %(prog)s --features "usage,regression" # Run usage tests with regression suite + + + # Multiple features (different ways) + %(prog)s --features "usage,agent,context" # Comma-separated features with default sanity suite + %(prog)s --features usage --features agent # Multiple --features flags with default sanity suite + %(prog)s --features core_session # Run grouped feature (includes help,quit,clear) with default sanity suite + + # Binary and output options + %(prog)s --binary /path/to/q --features usage # Executes the usage tests on provided q-cli binary instead of installed + %(prog)s --quiet --features sanity # Executes the tests in quiet mode + + # Utility commands + %(prog)s --list-features # List all available features + %(prog)s --json-to-html report.json # Convert JSON report (previously generated by running test) to HTML + + # Advanced examples + %(prog)s --features "core_session,regression" --binary ./target/release/q + %(prog)s --features "agent,mcp,sanity" --quiet""", + formatter_class=argparse.RawDescriptionHelpFormatter, + usage=argparse.SUPPRESS, + add_help=False + ) + # Command options + parser.add_argument("-h", "--help", action="help", help="show this help message and exit") + parser.add_argument("--list-features", action="store_true", help="List all available features") + parser.add_argument("--json-to-html", help="Convert JSON report to HTML (provide JSON file path)", metavar="JSON_PATH") + + # For backward compatibility + parser.add_argument("--features", help="Comma-separated list of features") + parser.add_argument("--binary", default="q", help="Path to Q CLI binary") + parser.add_argument("--quiet", action="store_true", help="Quiet mode") + + args = parser.parse_args() + + if args.list_features: + dev_debug() + return + + if args.json_to_html: + html_filename = generate_html_report(args.json_to_html) + print(f"🌐 HTML report generated: {html_filename}") + return + + if not args.features: + # Run all features with default test suite + grouped_features, standalone_features, _ = parse_features() + all_features = list(grouped_features.keys()) + standalone_features + test_suites = [DEFAULT_TESTSUITE] + else: + # Parse requested features + requested_features = [f.strip() for f in args.features.split(",")] + validate_features(requested_features) + test_suites = get_test_suites_from_features(requested_features) + + # Remove test suites from feature list + features_only = [f for f in requested_features if f not in {"sanity", "regression"}] + + if not features_only: + # Only sanity/regression specified - run all features + grouped_features, standalone_features, _ = parse_features() + all_features = list(grouped_features.keys()) + standalone_features + else: + all_features = features_only + + if not args.quiet: + print("πŸ§ͺ Running Q CLI E2E Tests") + print("=" * 40) + print(f"Features: {', '.join(all_features)}") + print(f"Test Suites: {', '.join(test_suites)}") + print() + + # Run tests + results = run_tests_with_suites(all_features, test_suites, args.binary, args.quiet) + + # Generate and display report + report = generate_report(results, all_features, test_suites, args.binary) + print_summary(report, args.quiet) + + # Exit with appropriate code + sys.exit(0 if report["summary"]["failed"] == 0 else 1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/e2etests/src/lib.rs b/e2etests/src/lib.rs index 45a88cbfec..854730b062 100644 --- a/e2etests/src/lib.rs +++ b/e2etests/src/lib.rs @@ -1,37 +1,3 @@ -// Q CLI E2E Test Framework -// This library provides end-to-end testing utilities for Amazon Q CLI - -use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; - -static INIT: Once = Once::new(); -static mut CHAT_SESSION: Option> = None; - -pub fn get_chat_session() -> &'static Mutex { - unsafe { - INIT.call_once(|| { - let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); - println!("βœ… Q Chat session started"); - CHAT_SESSION = Some(Mutex::new(chat)); - }); - CHAT_SESSION.as_ref().unwrap() - } -} - -pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { - let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; - if count == total_tests { - unsafe { - if let Some(session) = &CHAT_SESSION { - if let Ok(mut chat) = session.lock() { - chat.quit()?; - println!("βœ… Test completed successfully"); - } - } - } - } - Ok(count) -} - pub mod q_chat_helper { //! Helper module for Q CLI testing with hybrid approach //! - expectrl for commands (/help, /tools) @@ -138,12 +104,12 @@ pub mod q_chat_helper { }, Ok(_) => { // No more data, but wait a bit more in case there's more coming - std::thread::sleep(Duration::from_millis(2500)); + std::thread::sleep(Duration::from_millis(5000)); if total_content.len() > 0 { break; } }, Err(_) => break, } - std::thread::sleep(Duration::from_millis(2500)); + std::thread::sleep(Duration::from_millis(5000)); } Ok(total_content) @@ -163,4 +129,93 @@ pub mod q_chat_helper { Ok(()) } } + + /// Execute Q CLI subcommand in normal terminal and return response + pub fn execute_q_subcommand(binary: &str, args: &[&str]) -> Result> { + execute_q_subcommand_with_stdin(binary, args, None) + } + + /// Execute Q CLI subcommand with optional stdin input and return response + pub fn execute_q_subcommand_with_stdin(binary: &str, args: &[&str], input: Option<&str>) -> Result> { + let q_binary = std::env::var("Q_CLI_PATH").unwrap_or_else(|_| binary.to_string()); + + let full_command = format!("{} {}", q_binary, args.join(" ")); + let prompt = format!("(base) user@host ~ % {}\n", full_command); + + let mut child = Command::new(&q_binary) + .args(args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + if let Some(stdin_input) = input { + if let Some(stdin) = child.stdin.as_mut() { + stdin.write_all(stdin_input.as_bytes())?; + } + } + + let output = child.wait_with_output()?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + Ok(format!("{}{}{}", prompt, stderr, stdout)) + } + + /// Execute interactive menu selection with binary and args + pub fn execute_interactive_menu_selection(binary: &str, args: &[&str], down_arrows: usize) -> Result { + let q_binary = std::env::var("Q_CLI_PATH").unwrap_or_else(|_| binary.to_string()); + let command = format!("{} {}", q_binary, args.join(" ")); + execute_interactive_menu_selection_with_command(&command, down_arrows) + } + + /// Execute interactive menu selection with full command string + pub fn execute_interactive_menu_selection_with_command(command: &str, down_arrows: usize) -> Result { + let mut session = expectrl::spawn(command)?; + session.set_expect_timeout(Some(Duration::from_secs(30))); + + // Wait for menu to appear and read initial output + thread::sleep(Duration::from_secs(3)); + + let mut response = String::new(); + let mut buffer = [0u8; 1024]; + + // Read initial menu display + for _ in 0..5 { + if let Ok(bytes_read) = session.try_read(&mut buffer) { + if bytes_read > 0 { + response.push_str(&String::from_utf8_lossy(&buffer[..bytes_read])); + } + } + thread::sleep(Duration::from_millis(200)); + } + + // Navigate and select + for _ in 0..down_arrows { + session.write_all(b"\x1b[B")?; + session.flush()?; + thread::sleep(Duration::from_millis(300)); + } + + session.write_all(b"\r")?; + session.flush()?; + thread::sleep(Duration::from_secs(2)); + + // Read final response + for _ in 0..10 { + if let Ok(bytes_read) = session.try_read(&mut buffer) { + if bytes_read > 0 { + response.push_str(&String::from_utf8_lossy(&buffer[..bytes_read])); + } else { + break; + } + } else { + break; + } + thread::sleep(Duration::from_millis(200)); + } + + Ok(response) + } + } diff --git a/e2etests/tests/agent/mod.rs b/e2etests/tests/agent/mod.rs new file mode 100644 index 0000000000..47e51d905c --- /dev/null +++ b/e2etests/tests/agent/mod.rs @@ -0,0 +1,2 @@ +// Module declaration for agent tests +pub mod test_agent_commands; \ No newline at end of file diff --git a/e2etests/tests/test_agent_commands.rs b/e2etests/tests/agent/test_agent_commands.rs similarity index 76% rename from e2etests/tests/test_agent_commands.rs rename to e2etests/tests/agent/test_agent_commands.rs index 4271ae815e..830c66d31c 100644 --- a/e2etests/tests/test_agent_commands.rs +++ b/e2etests/tests/agent/test_agent_commands.rs @@ -1,8 +1,42 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); +#[allow(dead_code)] const TEST_NAMES: &[&str] = &[ "agent_without_subcommand", "test_agent_create_command", @@ -14,12 +48,15 @@ const TEST_NAMES: &[&str] = &[ "test_agent_set_default_command", "test_agent_set_default_missing_args", ]; +#[allow(dead_code)] const TOTAL_TESTS: usize = TEST_NAMES.len(); +/// Tests the /agent command without subcommands to display help information +/// Verifies agent management description, usage, available subcommands, and options #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn agent_without_subcommand() -> Result<(), Box> { - println!("πŸ” Testing /agent command..."); + println!("\nπŸ” Testing /agent command... | Description: Tests the /agent command without subcommands to display help information. Verifies agent management description, usage, available subcommands, and options"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -67,10 +104,12 @@ fn agent_without_subcommand() -> Result<(), Box> { Ok(()) } +/// Tests the /agent create command to create a new agent with specified name +/// Verifies agent creation process, file system operations, and cleanup #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn test_agent_create_command() -> Result<(), Box> { - println!("πŸ” Testing /agent create --name command..."); + println!("\nπŸ” Testing /agent create --name command... | Description: Tests the /agent create command to create a new agent with specified name. Verifies agent creation process, file system operations, and cleanup"); let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -108,7 +147,7 @@ fn test_agent_create_command() -> Result<(), Box> { let lines: Vec<&str> = whoami_response.lines().collect(); let username = lines.iter() .find(|line| !line.starts_with("!") && !line.starts_with(">") && !line.trim().is_empty()) - .unwrap_or(&"shrebhaa") + .expect("Failed to get username from whoami command") .trim(); println!("βœ… Current username: {}", username); @@ -134,10 +173,12 @@ fn test_agent_create_command() -> Result<(), Box> { Ok(()) } +/// Tests the /agent create command without required arguments to verify error handling +/// Verifies proper error messages, usage information, and help suggestions #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn test_agent_create_missing_args() -> Result<(), Box> { - println!("πŸ” Testing /agent create without required arguments..."); + println!("\nπŸ” Testing /agent create without required arguments... | Description: Tests the /agent create command without required arguments to verify error handling. Verifies proper error messages, usage information, and help suggestions"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -183,10 +224,12 @@ fn test_agent_create_missing_args() -> Result<(), Box> { Ok(()) } +/// Tests the /agent help command to display comprehensive agent help information +/// Verifies agent descriptions, usage notes, launch instructions, and configuration paths #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn test_agent_help_command() -> Result<(), Box> { - println!("πŸ” Testing /agent help..."); + println!("\nπŸ” Testing /agent help... | Description: Tests the /agent help command to display comprehensive agent help information. Verifies agent descriptions, usage notes, launch instructions, and configuration paths"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -238,10 +281,12 @@ fn test_agent_help_command() -> Result<(), Box> { Ok(()) } +/// Tests the /agent command with invalid subcommand to verify error handling +/// Verifies that invalid commands display help information with available commands and options #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn test_agent_invalid_command() -> Result<(), Box> { - println!("πŸ” Testing /agent invalidcommand..."); + println!("\nπŸ” Testing /agent invalidcommand... | Description: Tests the /agent command with invalid subcommand to verify error handling. Verifies that invalid commands display help information with available commands and options"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -275,10 +320,12 @@ fn test_agent_invalid_command() -> Result<(), Box> { Ok(()) } +/// Tests the /agent list command to display all available agents +/// Verifies agent listing format and presence of default agent #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn test_agent_list_command() -> Result<(), Box> { - println!("πŸ” Testing /agent list command..."); + println!("\nπŸ” Testing /agent list command... | Description: Tests the /agent list command to display all available agents. Verifies agent listing format and presence of default agent"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -307,10 +354,12 @@ fn test_agent_list_command() -> Result<(), Box> { Ok(()) } +/// Tests the /agent schema command to display agent configuration schema +/// Verifies JSON schema structure with required keys and properties // #[test] // #[cfg(feature = "agent")] // fn test_agent_schema_command() -> Result<(), Box> { -// println!("πŸ” Testing /agent schema..."); +// println!("\nπŸ” Testing /agent schema... | Description: Tests the /agent schema command to display agent configuration schema. Verifies JSON schema structure with required keys and properties"); // let session = get_chat_session(); // let mut chat = session.lock().unwrap(); @@ -346,10 +395,12 @@ fn test_agent_list_command() -> Result<(), Box> { // Ok(()) // } +/// Tests the /agent set-default command with valid arguments to set default agent +/// Verifies success messages and confirmation of default agent configuration #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn test_agent_set_default_command() -> Result<(), Box> { - println!("πŸ” Testing /agent set-default with valid arguments..."); + println!("\nπŸ” Testing /agent set-default with valid arguments... | Description: Tests the /agent set-default command with valid arguments to set default agent. Verifies success messages and confirmation of default agent configuration"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -385,10 +436,12 @@ fn test_agent_set_default_command() -> Result<(), Box> { Ok(()) } +/// Tests the /agent set-default command without required arguments to verify error handling +/// Verifies error messages, usage information, and available options display #[test] -#[cfg(feature = "agent")] +#[cfg(all(feature = "agent", feature = "sanity"))] fn test_agent_set_default_missing_args() -> Result<(), Box> { - println!("πŸ” Testing /agent set-default without required arguments..."); + println!("\nπŸ” Testing /agent set-default without required arguments... | Description: Tests the /agent set-default command without required arguments to verify error handling. Verifies error messages, usage information, and available options display"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); diff --git a/e2etests/tests/ai_prompts/mod.rs b/e2etests/tests/ai_prompts/mod.rs new file mode 100644 index 0000000000..b1384b21d6 --- /dev/null +++ b/e2etests/tests/ai_prompts/mod.rs @@ -0,0 +1,2 @@ +pub mod test_ai_prompt; +pub mod test_prompts_commands; \ No newline at end of file diff --git a/e2etests/tests/ai_prompts/test_ai_prompt.rs b/e2etests/tests/ai_prompts/test_ai_prompt.rs new file mode 100644 index 0000000000..0891e33b9a --- /dev/null +++ b/e2etests/tests/ai_prompts/test_ai_prompt.rs @@ -0,0 +1,148 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_what_is_aws_prompt", + "test_simple_greeting", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "ai_prompts", feature = "sanity"))] +fn test_what_is_aws_prompt() -> Result<(), Box> { + println!("\nπŸ” [AI PROMPTS] Testing 'What is AWS?' AI prompt... | Description: Tests AI prompt functionality by sending 'What is AWS?' and verifying the response contains relevant AWS information and technical terms"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + println!("βœ… Q Chat session started"); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Check if we got an actual AI response + if response.contains("Amazon Web Services") || + response.contains("cloud") || + response.contains("AWS") || + response.len() > 100 { + println!("βœ… Got substantial AI response ({} bytes)!", response.len()); + + // Additional checks for quality response + if response.contains("Amazon Web Services") { + println!("βœ… Response correctly identifies 'Amazon Web Services'"); + } + if response.contains("cloud") { + println!("βœ… Response mentions cloud computing concepts"); + } + if response.contains("AWS") { + println!("βœ… Response uses AWS acronym appropriately"); + } + + // Check for technical depth + let technical_terms = ["service", "platform", "infrastructure", "compute", "storage"]; + let found_terms: Vec<&str> = technical_terms.iter() + .filter(|&&term| response.to_lowercase().contains(term)) + .copied() + .collect(); + if !found_terms.is_empty() { + println!("βœ… Response includes technical terms: {:?}", found_terms); + } + } else { + println!("⚠️ Response seems limited or just echoed input"); + println!("⚠️ Expected AWS explanation but got: {} bytes", response.len()); + } + + println!("βœ… Test completed successfully"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "ai_prompts", feature = "sanity"))] +fn test_simple_greeting() -> Result<(), Box> { + println!("\nπŸ” Testing simple 'Hello' prompt... | Description: Tests basic AI interaction by sending a simple greeting and verifying the AI responds appropriately with greeting-related content"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + println!("βœ… Q Chat session started"); + + let response = chat.execute_command("Hello")?; + + println!("πŸ“ Greeting response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Check if we got any response + if response.trim().is_empty() { + println!("⚠️ No response to greeting - AI may not be responding"); + } else if response.to_lowercase().contains("hello") || + response.to_lowercase().contains("hi") || + response.to_lowercase().contains("greet") { + println!("βœ… Got appropriate greeting response!"); + println!("βœ… AI recognized and responded to greeting appropriately"); + } else if response.len() > 20 { + println!("βœ… Got substantial response ({} bytes) to greeting", response.len()); + println!("⚠️ Response doesn't contain typical greeting words but seems AI-generated"); + } else { + println!("⚠️ Got minimal response - unclear if AI-generated or echo"); + println!("⚠️ Response length: {} bytes", response.len()); + } + + println!("βœ… Test completed successfully"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} diff --git a/e2etests/tests/test_prompts_commands.rs b/e2etests/tests/ai_prompts/test_prompts_commands.rs similarity index 73% rename from e2etests/tests/test_prompts_commands.rs rename to e2etests/tests/ai_prompts/test_prompts_commands.rs index 664a3e3165..8f1da0380b 100644 --- a/e2etests/tests/test_prompts_commands.rs +++ b/e2etests/tests/ai_prompts/test_prompts_commands.rs @@ -1,21 +1,56 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); // List of covered tests +#[allow(dead_code)] const TEST_NAMES: &[&str] = &[ "test_prompts_command", "test_prompts_help_command", "test_prompts_list_command", ]; +#[allow(dead_code)] const TOTAL_TESTS: usize = TEST_NAMES.len(); #[test] -#[cfg(feature = "ai_prompts")] +#[cfg(all(feature = "ai_prompts", feature = "sanity"))] fn test_prompts_command() -> Result<(), Box> { - println!("πŸ” Testing /prompts command..."); + println!("\nπŸ” Testing /prompts command... | Description: Tests the /prompts command to display available prompts with usage instructions and argument requirements"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -52,9 +87,9 @@ fn test_prompts_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "ai_prompts")] +#[cfg(all(feature = "ai_prompts", feature = "sanity"))] fn test_prompts_help_command() -> Result<(), Box> { - println!("πŸ” Testing /prompts --help command..."); + println!("\nπŸ” Testing /prompts --help command... | Description: Tests the /prompts --help command to display comprehensive help information about prompts functionality and MCP server integration"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -113,9 +148,9 @@ fn test_prompts_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "ai_prompts")] +#[cfg(all(feature = "ai_prompts", feature = "sanity"))] fn test_prompts_list_command() -> Result<(), Box> { - println!("πŸ” Testing /prompts list command..."); + println!("\nπŸ” Testing /prompts list command... | Description: Tests the /prompts list command to display all available prompts with their arguments and usage information"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); diff --git a/e2etests/tests/all_tests.rs b/e2etests/tests/all_tests.rs new file mode 100644 index 0000000000..2c02d604b5 --- /dev/null +++ b/e2etests/tests/all_tests.rs @@ -0,0 +1,13 @@ +// Main integration test file that includes all subdirectory tests +mod agent; +mod ai_prompts; +mod context; +mod core_session; +mod integration; +mod mcp; +mod model; +mod q_subcommand; +mod save_load; +mod session_mgmt; +mod tools; +mod todos; \ No newline at end of file diff --git a/e2etests/tests/context/mod.rs b/e2etests/tests/context/mod.rs new file mode 100644 index 0000000000..b911b0e024 --- /dev/null +++ b/e2etests/tests/context/mod.rs @@ -0,0 +1 @@ +pub mod test_context_command; \ No newline at end of file diff --git a/e2etests/tests/test_context_command.rs b/e2etests/tests/context/test_context_command.rs similarity index 76% rename from e2etests/tests/test_context_command.rs rename to e2etests/tests/context/test_context_command.rs index e5db3b3adf..6c142744ef 100644 --- a/e2etests/tests/test_context_command.rs +++ b/e2etests/tests/context/test_context_command.rs @@ -1,9 +1,43 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); // List of covered tests +#[allow(dead_code)] const TEST_NAMES: &[&str] = &[ "test_context_show_command", "test_context_help_command", @@ -13,15 +47,16 @@ const TEST_NAMES: &[&str] = &[ "test_context_remove_command_of_non_existent_file", "test_add_remove_file_context", "test_add_glob_pattern_file_context", - "test_clear_context_command", - "test_add_remove_multiple_file_context" + "test_add_remove_multiple_file_context", + "test_clear_context_command" ]; +#[allow(dead_code)] const TOTAL_TESTS: usize = TEST_NAMES.len(); #[test] -#[cfg(feature = "context")] +#[cfg(all(feature = "context", feature = "sanity"))] fn test_context_show_command() -> Result<(), Box> { - println!("πŸ” Testing /context show command..."); + println!("\nπŸ” Testing /context show command... | Description: Tests the /context show command to display current context information including agent configuration and context files"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -34,12 +69,11 @@ fn test_context_show_command() -> Result<(), Box> { println!("πŸ“ END OUTPUT"); // Verify context show output contains expected sections - assert!(response.contains("πŸ‘€ Agent"), "Missing Agent section with emoji"); + assert!(response.contains("Agent"), "Missing Agent section"); println!("βœ… Found Agent section with emoji"); // Verify agent configuration details - assert!(response.contains("q_cli_default"), "Missing q_cli_default in agent config"); - assert!(response.contains("πŸ’¬ Session"), "Missing session section with emoji"); + assert!(response.contains("q_cli_default"), "Missing q_cli_default"); println!("βœ… Found all expected agent configuration files"); println!("βœ… All context show content verified!"); @@ -54,9 +88,9 @@ fn test_context_show_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "context")] +#[cfg(all(feature = "context", feature = "sanity"))] fn test_context_help_command() -> Result<(), Box> { - println!("πŸ” Testing /context help command..."); + println!("\nπŸ” Testing /context help command... | Description: Tests the /context help command to display comprehensive help information for context management including usage, commands, and options"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -69,12 +103,12 @@ fn test_context_help_command() -> Result<(), Box> { println!("πŸ“ END OUTPUT"); // Verify Usage section - assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("Usage"), "Missing Usage section"); assert!(response.contains("/context") && response.contains(""), "Missing /context command in usage"); println!("βœ… Found Usage section"); // Verify Commands section - assert!(response.contains("Commands:"), "Missing Commands section"); + assert!(response.contains("Commands"), "Missing Commands section"); assert!(response.contains("show"), "Missing show command"); assert!(response.contains("add"), "Missing add command"); assert!(response.contains("remove"), "Missing remove command"); @@ -82,10 +116,6 @@ fn test_context_help_command() -> Result<(), Box> { assert!(response.contains("help"), "Missing help command"); println!("βœ… Found Commands section with all subcommands"); - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help"), "Missing --help flag"); println!("βœ… Found Options section with help flags"); println!("βœ… All context help content verified!"); @@ -100,9 +130,9 @@ fn test_context_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "context")] +#[cfg(all(feature = "context", feature = "sanity"))] fn test_context_without_subcommand() -> Result<(), Box> { - println!("πŸ” Testing /context without sub command..."); + println!("\nπŸ” Testing /context without sub command... | Description: Tests the /context command without subcommands to verify it displays help information with usage and available commands"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -114,11 +144,11 @@ fn test_context_without_subcommand() -> Result<(), Box> { println!("{}", response); println!("πŸ“ END OUTPUT"); - assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("Usage"), "Missing Usage section"); assert!(response.contains("/context") && response.contains(""), "Missing /context command in usage"); println!("βœ… Found Usage section with /context command"); - assert!(response.contains("Commands:"), "Missing Commands section"); + assert!(response.contains("Commands"), "Missing Commands section"); assert!(response.contains("show"), "Missing show command"); assert!(response.contains("add"), "Missing add command"); assert!(response.contains("remove"), "Missing remove command"); @@ -126,12 +156,6 @@ fn test_context_without_subcommand() -> Result<(), Box> { assert!(response.contains("help"), "Missing help command"); println!("βœ… Found Commands section with all subcommands"); - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help"), "Missing --help flag"); - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found Options section with -h, --help flags"); - println!("βœ… All context help content verified!"); // Release the lock before cleanup @@ -144,9 +168,9 @@ fn test_context_without_subcommand() -> Result<(), Box> { } #[test] -#[cfg(feature = "context")] +#[cfg(all(feature = "context", feature = "sanity"))] fn test_context_invalid_command() -> Result<(), Box> { - println!("πŸ” Testing /context invalid command..."); + println!("\nπŸ” Testing /context invalid command... | Description: Tests the /context test command with invalid subcommand to verify proper error handling and help display"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -159,27 +183,9 @@ fn test_context_invalid_command() -> Result<(), Box> { println!("πŸ“ END OUTPUT"); // Verify error message for invalid subcommand - assert!(response.contains("error:") && response.contains("unrecognized subcommand") && response.contains("test"), "Missing 'unrecognized subcommand' error message"); + assert!(response.contains("error"), "Missing error message"); println!("βœ… Found expected error message for invalid subcommand"); - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/context") && response.contains(""), "Missing /context command in usage"); - println!("βœ… Found Usage section with /context command"); - - assert!(response.contains("Commands:"), "Missing Commands section"); - assert!(response.contains("show"), "Missing show command"); - assert!(response.contains("add"), "Missing add command"); - assert!(response.contains("remove"), "Missing remove command"); - assert!(response.contains("clear"), "Missing clear command"); - assert!(response.contains("help"), "Missing help command"); - println!("βœ… Found Commands section with all subcommands"); - - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help"), "Missing --help flag"); - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found Options section with -h, --help flags"); - println!("βœ… All context invalid command content verified!"); // Release the lock before cleanup @@ -192,9 +198,9 @@ fn test_context_invalid_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "context")] +#[cfg(all(feature = "context", feature = "sanity"))] fn test_add_non_existing_file_context() -> Result<(), Box> { - println!("πŸ” Testing /context add non-existing file command..."); + println!("\nπŸ” Testing /context add non-existing file command... | Description: Tests the /context add command with non-existing file to verify proper error handling and force option suggestion"); let non_existing_file_path = "/tmp/non_existing_file.py"; @@ -210,8 +216,7 @@ fn test_add_non_existing_file_context() -> Result<(), Box println!("πŸ“ END ADD RESPONSE"); // Verify error message for non-existing file - assert!(add_response.contains("Error:") && add_response.contains("Invalid path") && add_response.contains("does not exist"), "Missing error message for non-existing file"); - assert!(add_response.contains("Use --force to add anyway"), "Missing --force suggestion in error message"); + assert!(add_response.contains("Error"), "Missing error message for non-existing file"); println!("βœ… Found expected error message for non-existing file with --force suggestion"); // Release the lock before cleanup @@ -224,9 +229,9 @@ fn test_add_non_existing_file_context() -> Result<(), Box } #[test] -#[cfg(feature = "context")] +#[cfg(all(feature = "context", feature = "sanity"))] fn test_context_remove_command_of_non_existent_file() -> Result<(), Box> { - println!("πŸ” Testing /context remove non existing file command..."); + println!("\nπŸ” Testing /context remove non existing file command... | Description: Tests the /context remove command with non-existing file to verify proper error handling"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -239,7 +244,7 @@ fn test_context_remove_command_of_non_existent_file() -> Result<(), Box Result<(), Box Result<(), Box> { - println!("πŸ” Testing /context add command and /context remove command..."); + println!("\nπŸ” Testing /context add command and /context remove command... | Description: Tests the /context add command to add a file to context and /context remove command to remove a file from context"); let test_file_path = "/tmp/test_context_file_.py"; // Create a test file @@ -273,7 +278,7 @@ fn test_add_remove_file_context() -> Result<(), Box> { println!("πŸ“ END ADD RESPONSE"); // Verify file was added successfully - be flexible with the exact message format - assert!(add_response.contains("Added") && (add_response.contains("1 path(s) to context") || add_response.contains("1 path to context") || add_response.contains("1 file to context")), "Missing success message for adding file"); + assert!(add_response.contains("Added"), "Missing success message for adding file"); println!("βœ… File added to context successfully"); // Execute /context show to confirm file is present @@ -286,7 +291,6 @@ fn test_add_remove_file_context() -> Result<(), Box> { // Verify file is present in context assert!(show_response.contains(test_file_path), "File not found in context show output"); - assert!(show_response.contains("πŸ’¬ Session (temporary):"), "Missing Session section"); println!("βœ… File confirmed present in context"); // Remove file from context @@ -298,7 +302,7 @@ fn test_add_remove_file_context() -> Result<(), Box> { println!("πŸ“ END REMOVE RESPONSE"); // Verify file was removed successfully - be flexible with the exact message format - assert!(remove_response.contains("Removed") && (remove_response.contains("1 path(s) from context") || remove_response.contains("1 path from context") || remove_response.contains("1 file from context")), "Missing success message for removing file"); + assert!(remove_response.contains("Removed"), "Missing success message for removing file"); println!("βœ… File removed from context successfully"); // Execute /context show to confirm file is gone @@ -327,9 +331,9 @@ fn test_add_remove_file_context() -> Result<(), Box> { } #[test] -#[cfg(feature = "context")] +#[cfg(all(feature = "context", feature = "sanity"))] fn test_add_glob_pattern_file_context()-> Result<(), Box> { - println!("πŸ” Testing /context add *.py glob pattern command..."); + println!("\nπŸ” Testing /context add *.py glob pattern command... | Description: Tests the /context add command with glob patterns to add multiple files matching a pattern and verify pattern-based context management"); let test_file1_path = "/tmp/test_context_file1.py"; let test_file2_path = "/tmp/test_context_file2.py"; @@ -354,7 +358,7 @@ fn test_add_glob_pattern_file_context()-> Result<(), Box> println!("πŸ“ END ADD RESPONSE"); // Verify glob pattern was added successfully - be flexible with the exact message format - assert!(add_response.contains("Added") && (add_response.contains("1 path(s) to context") || add_response.contains("1 path to context") || add_response.contains("1 pattern to context")), "Missing success message for adding glob pattern"); + assert!(add_response.contains("Added"), "Missing success message for adding glob pattern"); println!("βœ… Glob pattern added to context successfully"); // Execute /context show to confirm pattern matches files @@ -367,8 +371,6 @@ fn test_add_glob_pattern_file_context()-> Result<(), Box> // Verify glob pattern is present and matches files assert!(show_response.contains(glob_pattern), "Glob pattern not found in context show output"); - assert!(show_response.contains("match"), "Missing match indicator for glob pattern"); - assert!(show_response.contains("πŸ’¬ Session (temporary):"), "Missing Session section"); println!("βœ… Glob pattern confirmed present in context with matches"); // Remove glob pattern from context @@ -380,7 +382,7 @@ fn test_add_glob_pattern_file_context()-> Result<(), Box> println!("πŸ“ END REMOVE RESPONSE"); // Verify glob pattern was removed successfully - be flexible with the exact message format - assert!(remove_response.contains("Removed") && (remove_response.contains("1 path(s) from context") || remove_response.contains("1 path from context") || remove_response.contains("1 pattern from context")), "Missing success message for removing glob pattern"); + assert!(remove_response.contains("Removed"), "Missing success message for removing glob pattern"); println!("βœ… Glob pattern removed from context successfully"); // Execute /context show to confirm glob pattern is gone @@ -411,21 +413,24 @@ fn test_add_glob_pattern_file_context()-> Result<(), Box> } #[test] -#[cfg(feature = "context")] -fn test_clear_context_command()-> Result<(), Box> { - println!("πŸ” Testing /context clear command..."); - - let test_file_path = "/tmp/test_context_file.py"; +#[cfg(all(feature = "context", feature = "sanity"))] +fn test_add_remove_multiple_file_context()-> Result<(), Box> { + println!("\nπŸ” Testing /context add command and /context remove ... | Description: Tests the /context add command with multiple files to verify batch context operations and /context remove command with multiple files to verify"); + let test_file1_path = "/tmp/test_context_file1.py"; + let test_file2_path = "/tmp/test_context_file2.py"; + let test_file3_path = "/tmp/test_context_file.js"; // Create test files - std::fs::write(test_file_path, "# Test Python file 1 for context\nprint('Hello from Python file 1')")?; - println!("βœ… Created test files at {}", test_file_path); + std::fs::write(test_file1_path, "# Test Python file 1 for context\nprint('Hello from Python file 1')")?; + std::fs::write(test_file2_path, "# Test Python file 2 for context\nprint('Hello from Python file 2')")?; + std::fs::write(test_file3_path, "// Test JavaScript file\nconsole.log('Hello from JS file');")?; + println!("βœ… Created test files at {}, {}, {}", test_file1_path, test_file2_path, test_file3_path); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - - // Add multiple files to context - let add_response = chat.execute_command(&format!("/context add {}", test_file_path))?; + + // Add multiple files to context in one command + let add_response = chat.execute_command(&format!("/context add {} {} {}", test_file1_path, test_file2_path, test_file3_path))?; println!("πŸ“ Context add response: {} bytes", add_response.len()); println!("πŸ“ ADD RESPONSE:"); @@ -433,8 +438,8 @@ fn test_clear_context_command()-> Result<(), Box> { println!("πŸ“ END ADD RESPONSE"); // Verify files were added successfully - be flexible with the exact message format - assert!(add_response.contains("Added") && (add_response.contains("1 path(s) to context") || add_response.contains("2 paths to context") || add_response.contains("2 files to context")), "Missing success message for adding files"); - println!("βœ… Files added to context successfully"); + assert!(add_response.contains("Added"), "Missing success message for adding multiple files"); + println!("βœ… Multiple files added to context successfully"); // Execute /context show to confirm files are present let show_response = chat.execute_command("/context show")?; @@ -444,23 +449,25 @@ fn test_clear_context_command()-> Result<(), Box> { println!("{}", show_response); println!("πŸ“ END SHOW RESPONSE"); - // Verify files are present in context - assert!(show_response.contains(test_file_path), "Python file not found in context show output"); - println!("βœ… Files confirmed present in context"); - - // Execute /context clear to remove all files - let clear_response = chat.execute_command("/context clear")?; + // Verify all files are present in context + assert!(show_response.contains(test_file1_path), "Python file not found in context show output"); + assert!(show_response.contains(test_file2_path), "JavaScript file not found in context show output"); + assert!(show_response.contains(test_file3_path), "Text file not found in context show output"); + println!("βœ… All files confirmed present in context"); + + // Remove multiple files from context + let remove_response = chat.execute_command(&format!("/context remove {} {} {}", test_file1_path, test_file2_path, test_file3_path))?; - println!("πŸ“ Context clear response: {} bytes", clear_response.len()); - println!("πŸ“ CLEAR RESPONSE:"); - println!("{}", clear_response); - println!("πŸ“ END CLEAR RESPONSE"); + println!("πŸ“ Context remove response: {} bytes", remove_response.len()); + println!("πŸ“ REMOVE RESPONSE:"); + println!("{}", remove_response); + println!("πŸ“ END REMOVE RESPONSE"); - // Verify context was cleared successfully - assert!(clear_response.contains("Cleared context"), "Missing success message for clearing context"); - println!("βœ… Context cleared successfully"); + // Verify files were removed successfully - be flexible with the exact message format + assert!(remove_response.contains("Removed"), "Missing success message for removing multiple files"); + println!("βœ… Multiple files removed from context successfully"); - // Execute /context show to confirm no files remain + // Execute /context show to confirm files are gone let final_show_response = chat.execute_command("/context show")?; println!("πŸ“ Final context show response: {} bytes", final_show_response.len()); @@ -468,18 +475,19 @@ fn test_clear_context_command()-> Result<(), Box> { println!("{}", final_show_response); println!("πŸ“ END FINAL SHOW RESPONSE"); - // Verify no files remain in context - assert!(!final_show_response.contains(test_file_path), "Python file still found in context after clear"); - assert!(final_show_response.contains("πŸ‘€ Agent (q_cli_default):"), "Missing Agent section"); - assert!(final_show_response.contains("πŸ’¬ Session (temporary):"), "Missing Session section"); - assert!(final_show_response.contains(""), "Missing indicator for cleared context"); - println!("βœ… All files confirmed removed from context and sections present"); + // Verify files are no longer in context + assert!(!final_show_response.contains(test_file1_path), "Python file still found in context after removal"); + assert!(!final_show_response.contains(test_file2_path), "JavaScript file still found in context after removal"); + assert!(!final_show_response.contains(test_file3_path), "Text file still found in context after removal"); + println!("βœ… All files confirmed removed from context"); // Release the lock before cleanup drop(chat); // Clean up test file - let _ = std::fs::remove_file(test_file_path); + let _ = std::fs::remove_file(test_file1_path); + let _ = std::fs::remove_file(test_file2_path); + let _ = std::fs::remove_file(test_file3_path); println!("βœ… Cleaned up test file"); // Cleanup only if this is the last test @@ -489,25 +497,21 @@ fn test_clear_context_command()-> Result<(), Box> { } #[test] -#[cfg(feature = "context")] -fn test_add_remove_multiple_file_context()-> Result<(), Box> { - println!("πŸ” Testing /context add command and /context remove ..."); - - let test_file1_path = "/tmp/test_context_file1.py"; - let test_file2_path = "/tmp/test_context_file2.py"; - let test_file3_path = "/tmp/test_context_file.js"; +#[cfg(all(feature = "context", feature = "sanity"))] +fn test_clear_context_command()-> Result<(), Box> { + println!("\nπŸ” Testing /context clear command... | Description: Tests the /context clear command to remove all files from context and verify the context is completely cleared"); + + let test_file_path = "/tmp/test_context_file.py"; // Create test files - std::fs::write(test_file1_path, "# Test Python file 1 for context\nprint('Hello from Python file 1')")?; - std::fs::write(test_file2_path, "# Test Python file 2 for context\nprint('Hello from Python file 2')")?; - std::fs::write(test_file3_path, "// Test JavaScript file\nconsole.log('Hello from JS file');")?; - println!("βœ… Created test files at {}, {}, {}", test_file1_path, test_file2_path, test_file3_path); + std::fs::write(test_file_path, "# Test Python file 1 for context\nprint('Hello from Python file 1')")?; + println!("βœ… Created test files at {}", test_file_path); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - - // Add multiple files to context in one command - let add_response = chat.execute_command(&format!("/context add {} {} {}", test_file1_path, test_file2_path, test_file3_path))?; + + // Add multiple files to context + let add_response = chat.execute_command(&format!("/context add {}", test_file_path))?; println!("πŸ“ Context add response: {} bytes", add_response.len()); println!("πŸ“ ADD RESPONSE:"); @@ -515,8 +519,8 @@ fn test_add_remove_multiple_file_context()-> Result<(), Box Result<(), Box Result<(), Box"), "Missing indicator for cleared context"); + println!("βœ… All files confirmed removed from context and sections present"); // Release the lock before cleanup drop(chat); // Clean up test file - let _ = std::fs::remove_file(test_file1_path); - let _ = std::fs::remove_file(test_file2_path); - let _ = std::fs::remove_file(test_file3_path); + let _ = std::fs::remove_file(test_file_path); println!("βœ… Cleaned up test file"); // Cleanup only if this is the last test @@ -573,4 +572,3 @@ fn test_add_remove_multiple_file_context()-> Result<(), Box> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_clear_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "clear", feature = "sanity"))] +fn test_clear_command() -> Result<(), Box> { + println!("\nπŸ” Testing /clear command... | Description: Tests the /clear command to clear conversation history and verify that previous context is no longer remembered by the AI"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + println!("βœ… Q Chat session started"); + + // Send initial message + println!("\nπŸ” Sending prompt: 'My name is TestUser'"); + let _initial_response = chat.execute_command("My name is TestUser")?; + println!("πŸ“ Initial response: {} bytes", _initial_response.len()); + println!("πŸ“ INITIAL RESPONSE OUTPUT:"); + println!("{}", _initial_response); + println!("πŸ“ END INITIAL RESPONSE"); + + // Execute clear command + println!("\nπŸ” Executing command: '/clear'"); + let _clear_response = chat.execute_command("/clear")?; + + println!("βœ… Clear command executed"); + + // Check if AI remembers previous conversation + println!("\nπŸ” Sending prompt: 'What is my name?'"); + let test_response = chat.execute_command("What is my name?")?; + println!("πŸ“ Test response: {} bytes", test_response.len()); + println!("πŸ“ TEST RESPONSE OUTPUT:"); + println!("{}", test_response); + println!("πŸ“ END TEST RESPONSE"); + + // Verify history is cleared - AI shouldn't remember the name + assert!(!test_response.to_lowercase().contains("testuser"), "Clear command failed - AI still remembers previous conversation"); + println!("βœ… Clear command successful - Conversation history cleared."); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/core_session/test_help_command.rs b/e2etests/tests/core_session/test_help_command.rs new file mode 100644 index 0000000000..3b7f66269a --- /dev/null +++ b/e2etests/tests/core_session/test_help_command.rs @@ -0,0 +1,94 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_help_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "help", feature = "sanity"))] +fn test_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /help command... | Description: Tests the /help command to display all available commands and verify core functionality like quit, clear, tools, and help commands are present"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + println!("βœ… Q Chat session started"); + + let response = chat.execute_command("/help")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify help content + assert!(response.contains("Commands:"), "Missing Commands section"); + println!("βœ… Found Commands section with all available commands"); + + assert!(response.contains("quit"), "Missing quit command"); + assert!(response.contains("clear"), "Missing clear command"); + assert!(response.contains("tools"), "Missing tools command"); + assert!(response.contains("help"), "Missing help command"); + println!("βœ… Verified core commands: quit, clear, tools, help"); + + // Verify specific useful commands + if response.contains("context") { + println!("βœ… Found context management command"); + } + if response.contains("agent") { + println!("βœ… Found agent management command"); + } + if response.contains("model") { + println!("βœ… Found model selection command"); + } + + println!("βœ… All help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} diff --git a/e2etests/tests/core_session/test_quit_command.rs b/e2etests/tests/core_session/test_quit_command.rs new file mode 100644 index 0000000000..e253f5b191 --- /dev/null +++ b/e2etests/tests/core_session/test_quit_command.rs @@ -0,0 +1,48 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_quit_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "quit", feature = "sanity"))] +fn test_quit_command() -> Result<(), Box> { + println!("\nπŸ” Testing /quit command... | Description: Tests the /quit command to properly terminate the Q Chat session and exit cleanly"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + println!("βœ… Q Chat session started"); + + chat.execute_command("/quit")?; + + println!("βœ… /quit command executed successfully"); + println!("βœ… Test completed successfully"); + + Ok(()) +} diff --git a/e2etests/tests/integration/mod.rs b/e2etests/tests/integration/mod.rs new file mode 100644 index 0000000000..844f370a00 --- /dev/null +++ b/e2etests/tests/integration/mod.rs @@ -0,0 +1,4 @@ +pub mod test_editor_help_command; +pub mod test_hooks_command; +pub mod test_issue_command; +pub mod test_subscribe_command; \ No newline at end of file diff --git a/e2etests/tests/integration/test_editor_help_command.rs b/e2etests/tests/integration/test_editor_help_command.rs new file mode 100644 index 0000000000..ed7cac3fbf --- /dev/null +++ b/e2etests/tests/integration/test_editor_help_command.rs @@ -0,0 +1,340 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; + +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_editor_help_command", + "test_help_editor_command", + "test_editor_h_command", + "test_editor_command_interaction", + "test_editor_command_error", + "test_editor_with_file_path", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} + +#[test] +#[cfg(all(feature = "editor", feature = "sanity"))] +fn test_editor_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /editor --help command... | Description: Tests the /editor --help command to display help information for the editor functionality including usage and options"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/editor --help")?; + + println!("πŸ“ Editor help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:") && response.contains("/editor") && response.contains("[INITIAL_TEXT]"), "Missing Usage section"); + println!("βœ… Found Usage section with /editor command"); + + // Verify Arguments section + assert!(response.contains("Arguments:"), "Missing Arguments section"); + assert!(response.contains("[INITIAL_TEXT]"), "Missing INITIAL_TEXT argument"); + println!("βœ… Found Arguments section"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All editor help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "editor", feature = "sanity"))] +fn test_help_editor_command() -> Result<(), Box> { + println!("\nπŸ” Testing /help editor command... | Description: Tests the /help editor command to display editor-specific help information and usage instructions"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/help editor")?; + + println!("πŸ“ Help editor response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:") && response.contains("/editor") && response.contains("[INITIAL_TEXT]"), "Missing Usage section"); + println!("βœ… Found Usage section with /editor command"); + + // Verify Arguments section + assert!(response.contains("Arguments:"), "Missing Arguments section"); + assert!(response.contains("[INITIAL_TEXT]"), "Missing INITIAL_TEXT argument"); + println!("βœ… Found Arguments section"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All editor help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "editor", feature = "sanity"))] +fn test_editor_h_command() -> Result<(), Box> { + println!("\nπŸ” Testing /editor -h command... | Description: Tests the /editor -h command (short form) to display editor help information and verify proper flag handling"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/editor -h")?; + + println!("πŸ“ Editor help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:") && response.contains("/editor") && response.contains("[INITIAL_TEXT]"), "Missing Usage section"); + println!("βœ… Found Usage section with /editor command"); + + // Verify Arguments section + assert!(response.contains("Arguments:"), "Missing Arguments section"); + assert!(response.contains("[INITIAL_TEXT]"), "Missing INITIAL_TEXT argument"); + println!("βœ… Found Arguments section"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All editor help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "editor", feature = "sanity"))] +fn test_editor_command_interaction() -> Result<(), Box> { + println!("πŸ” Testing /editor command interaction... | Description: Test that the /editor command successfully launches the integrated editor interface"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Execute /editor command to open editor panel + let response = chat.execute_command("/editor")?; + + println!("πŸ“ Editor command response: {} bytes", response.len()); + println!("πŸ“ EDITOR RESPONSE:"); + println!("{}", response); + println!("πŸ“ END EDITOR RESPONSE"); + + // Press 'i' to enter insert mode + let insert_response = chat.execute_command("i")?; + println!("πŸ“ Insert mode response: {} bytes", insert_response.len()); + + // Type "what is aws?" + let type_response = chat.execute_command("what is aws?")?; + println!("πŸ“ Type response: {} bytes", type_response.len()); + + // Press Esc to exit insert mode + let esc_response = chat.execute_command("\x1b")?; // ESC key + println!("πŸ“ Esc response: {} bytes", esc_response.len()); + + // Execute :wq to save and quit + let wq_response = chat.execute_command(":wq")?; + + println!("πŸ“ Final wq response: {} bytes", wq_response.len()); + println!("πŸ“ WQ RESPONSE:"); + println!("{}", wq_response); + println!("πŸ“ END WQ RESPONSE"); + + // Verify expected output + assert!(wq_response.contains("Content loaded from editor. Submitting prompt..."), "Missing expected editor output message"); + println!("βœ… Found expected editor output: 'Content loaded from editor. Submitting prompt...'"); + + println!("βœ… Editor command interaction test completed successfully!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "editor", feature = "sanity"))] +fn test_editor_command_error() -> Result<(), Box> { + println!("πŸ” Testing /editor command error handling ... | Description: Tests the /editor command error handling when attempting to open a nonexistent file"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Execute /editor command to open editor panel + let response = chat.execute_command("/editor nonexistent_file.txt")?; + + println!("πŸ“ Editor command response: {} bytes", response.len()); + println!("πŸ“ EDITOR RESPONSE:"); + println!("{}", response); + println!("πŸ“ END EDITOR RESPONSE"); + + // Press 'i' to enter insert mode + let insert_response = chat.execute_command("i")?; + println!("πŸ“ Insert mode response: {} bytes", insert_response.len()); + + + // Press Esc to exit insert mode + let esc_response = chat.execute_command("\x1b")?; // ESC key + println!("πŸ“ Esc response: {} bytes", esc_response.len()); + + // Execute :wq to save and quit + let wq_response = chat.execute_command(":wq")?; + + println!("πŸ“ Final wq response: {} bytes", wq_response.len()); + println!("πŸ“ WQ RESPONSE:"); + println!("{}", wq_response); + println!("πŸ“ END WQ RESPONSE"); + + // Verify expected output + assert!(wq_response.contains("Content loaded from editor. Submitting prompt..."), "Missing expected editor output message"); + println!("βœ… Found expected editor output: 'Content loaded from editor. Submitting prompt...'"); + + assert!(wq_response.contains("nonexistent_file.txt") && wq_response.contains("does not exist"), "Missing file validation error message"); + println!("βœ… Found expected file validation error message"); + + println!("βœ… Editor command error test completed successfully!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "editor", feature = "sanity"))] +fn test_editor_with_file_path() -> Result<(), Box> { + println!("πŸ” Testing /editor command... | Description: Tests the /editor command to load an existing file into the editor and verify content loading"); + + let home_dir = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + let test_file_path = format!("{}/test_editor_file.txt", home_dir); + + // Create a test file + std::fs::write(&test_file_path, "Hello from test file\nThis is a test file for editor command.")?; + println!("βœ… Created test file at {}", test_file_path); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Execute /editor command with file path + let response = chat.execute_command(&format!("/editor {}", test_file_path))?; + + println!("πŸ“ Editor with file response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Press 'i' to enter insert mode + let insert_response = chat.execute_command("i")?; + println!("πŸ“ Insert mode response: {} bytes", insert_response.len()); + + + // Press Esc to exit insert mode + let esc_response = chat.execute_command("\x1b")?; // ESC key + println!("πŸ“ Esc response: {} bytes", esc_response.len()); + + // Execute :wq to save and quit + let wq_response = chat.execute_command(":wq")?; + + println!("πŸ“ Final wq response: {} bytes", wq_response.len()); + println!("πŸ“ WQ RESPONSE:"); + println!("{}", wq_response); + println!("πŸ“ END WQ RESPONSE"); + + // Verify the file content is loaded in editor + assert!(wq_response.contains("Hello from test file"), "File content not loaded in editor"); + println!("βœ… File content loaded successfully in editor"); + + // Clean up test file + std::fs::remove_file(test_file_path).ok(); + println!("βœ… Cleaned up test file"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/integration/test_hooks_command.rs b/e2etests/tests/integration/test_hooks_command.rs new file mode 100644 index 0000000000..15cb2e9f43 --- /dev/null +++ b/e2etests/tests/integration/test_hooks_command.rs @@ -0,0 +1,156 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} + +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_hooks_command", + "test_hooks_help_command", + "test_hooks_h_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "hooks", feature = "sanity"))] +fn test_hooks_command() -> Result<(), Box> { + println!("\nπŸ” Testing /hooks command... | Description: Tests the /hooks command to display configured hooks or show no hooks message when none are configured"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/hooks")?; + + println!("πŸ“ Hooks command response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify no hooks configured message + assert!(response.contains("No hooks"), "Missing no hooks configured message"); + println!("βœ… Found no hooks configured message"); + + println!("βœ… All hooks command functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "hooks", feature = "sanity"))] +fn test_hooks_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /hooks --help command... | Description: Tests the /hooks --help command to display comprehensive help information for hooks functionality and configuration"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/hooks --help")?; + + println!("πŸ“ Hooks help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("/hooks"), "Missing /hooks command in usage section"); + println!("βœ… Found Usage section with /hooks command"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All hooks help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "hooks", feature = "sanity"))] +fn test_hooks_h_command() -> Result<(), Box> { + println!("\nπŸ” Testing /hooks -h command... | Description: Tests the /hooks -h command (short form) to display hooks help information and verify flag handling"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/hooks -h")?; + + println!("πŸ“ Hooks help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("/hooks"), "Missing /hooks command in usage section"); + println!("βœ… Found Usage section with /hooks command"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All hooks help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/integration/test_issue_command.rs b/e2etests/tests/integration/test_issue_command.rs new file mode 100644 index 0000000000..531dec3e4d --- /dev/null +++ b/e2etests/tests/integration/test_issue_command.rs @@ -0,0 +1,221 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} + +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_issue_command", + "test_issue_force_command", + "test_issue_f_command", + "test_issue_help_command", + "test_issue_h_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "issue_reporting", feature = "sanity"))] +fn test_issue_command() -> Result<(), Box> { + println!("\nπŸ” Testing /issue command with bug report... | Description: Tests the /issue command to create a bug report and verify it opens GitHub issue creation interface"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/issue \"Bug: Q CLI crashes when using large files\"")?; + + println!("πŸ“ Issue command response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify command executed successfully (GitHub opens automatically) + assert!(response.contains("Heading over to GitHub..."), "Missing browser opening confirmation"); + println!("βœ… Found browser opening confirmation"); + + println!("βœ… All issue command functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "issue_reporting", feature = "sanity"))] +fn test_issue_force_command() -> Result<(), Box> { + println!("\nπŸ” Testing /issue --force command with critical bug... | Description: Tests the /issue --force command to create a critical bug report and verify forced issue creation workflow"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/issue --force \"Critical bug in file handling\"")?; + + println!("πŸ“ Issue force command response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify command executed successfully (GitHub opens automatically) + assert!(response.contains("Heading over to GitHub..."), "Missing browser opening confirmation"); + println!("βœ… Found browser opening confirmation"); + + println!("βœ… All issue --force command functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "issue_reporting", feature = "sanity"))] +fn test_issue_f_command() -> Result<(), Box> { + println!("\nπŸ” Testing /issue -f command with critical bug... | Description: Tests the /issue -f command (short form) to create a critical bug report with force flag"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/issue -f \"Critical bug in file handling\"")?; + + println!("πŸ“ Issue force command response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify command executed successfully (GitHub opens automatically) + assert!(response.contains("Heading over to GitHub..."), "Missing browser opening confirmation"); + println!("βœ… Found browser opening confirmation"); + + println!("βœ… All issue --force command functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + + +#[test] +#[cfg(all(feature = "issue_reporting", feature = "sanity"))] +fn test_issue_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /issue --help command... | Description: Tests the /issue --help command to display help information for issue reporting functionality including options and usage"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/issue --help")?; + + println!("πŸ“ Issue help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:") && response.contains("/issue") && response.contains("[DESCRIPTION]") && response.contains("[OPTIONS]"), "Missing Usage section"); + println!("βœ… Found usage format"); + + // Verify Arguments section + assert!(response.contains("Arguments:"), "Missing Arguments section"); + println!("βœ… Found Arguments section"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + assert!(response.contains("-f") && response.contains("--force"), "Missing force option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found Options section with force and help flags"); + + println!("βœ… All issue help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "issue_reporting", feature = "sanity"))] +fn test_issue_h_command() -> Result<(), Box> { + println!("\nπŸ” Testing /issue -h command... | Description: Tests the /issue -h command (short form) to display issue reporting help information"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/issue -h")?; + + println!("πŸ“ Issue help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:") && response.contains("/issue") && response.contains("[DESCRIPTION]") && response.contains("[OPTIONS]"), "Missing Usage section"); + println!("βœ… Found usage format"); + + // Verify Arguments section + assert!(response.contains("Arguments:"), "Missing Arguments section"); + println!("βœ… Found Arguments section"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + assert!(response.contains("-f") && response.contains("--force"), "Missing force option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found Options section with force and help flags"); + + println!("βœ… All issue help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/integration/test_subscribe_command.rs b/e2etests/tests/integration/test_subscribe_command.rs new file mode 100644 index 0000000000..7038818d5f --- /dev/null +++ b/e2etests/tests/integration/test_subscribe_command.rs @@ -0,0 +1,206 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} + +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_subscribe_command", + "test_subscribe_manage_command", + "test_subscribe_help_command", + "test_subscribe_h_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + + +#[test] +#[cfg(all(feature = "subscribe", feature = "sanity"))] +fn test_subscribe_command() -> Result<(), Box> { + println!("\nπŸ” Testing /subscribe command... | Description: Tests the /subscribe command to display Q Developer Pro subscription information and IAM Identity Center details"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/subscribe")?; + + println!("πŸ“ Subscribe response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify subscription management message + assert!(response.contains("Q Developer Pro subscription") && response.contains("IAM Identity Center"), "Missing subscription management message"); + println!("βœ… Found subscription management message"); + + println!("βœ… All subscribe content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "subscribe", feature = "sanity"))] +fn test_subscribe_manage_command() -> Result<(), Box> { + println!("\nπŸ” Testing /subscribe --manage command... | Description: Tests the /subscribe --manage command to access subscription management interface for Q Developer Pro"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/subscribe --manage")?; + + println!("πŸ“ Subscribe response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify subscription management message + assert!(response.contains("Q Developer Pro subscription") && response.contains("IAM Identity Center"), "Missing subscription management message"); + println!("βœ… Found subscription management message"); + + println!("βœ… All subscribe content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "subscribe", feature = "sanity"))] +fn test_subscribe_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /subscribe --help command... | Description: Tests the /subscribe --help command to display comprehensive help information for subscription management"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/subscribe --help")?; + + println!("πŸ“ Subscribe help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify description + assert!(response.contains("Q Developer Pro subscription"), "Missing subscription description"); + println!("βœ… Found subscription description"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("/subscribe"), "Missing /subscribe command in usage section"); + assert!(response.contains("[OPTIONS]"), "Missing [OPTIONS] in usage section"); + println!("βœ… Found Usage section with /subscribe [OPTIONS]"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify manage option + assert!(response.contains("--manage"), "Missing --manage option"); + println!("βœ… Found --manage option"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All subscribe help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "subscribe", feature = "sanity"))] +fn test_subscribe_h_command() -> Result<(), Box> { + println!("\nπŸ” Testing /subscribe -h command... | Description: Tests the /subscribe -h command (short form) to display subscription help information"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/subscribe -h")?; + + println!("πŸ“ Subscribe help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify description + assert!(response.contains("Q Developer Pro subscription"), "Missing subscription description"); + println!("βœ… Found subscription description"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("/subscribe"), "Missing /subscribe command in usage section"); + assert!(response.contains("[OPTIONS]"), "Missing [OPTIONS] in usage section"); + println!("βœ… Found Usage section with /subscribe [OPTIONS]"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify manage option + assert!(response.contains("--manage"), "Missing --manage option"); + println!("βœ… Found --manage option"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All subscribe help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/mcp/mod.rs b/e2etests/tests/mcp/mod.rs new file mode 100644 index 0000000000..2cdaa6505e --- /dev/null +++ b/e2etests/tests/mcp/mod.rs @@ -0,0 +1,3 @@ +pub mod test_mcp_command_regression; +pub mod test_mcp_command; +pub mod test_q_mcp_subcommand; diff --git a/e2etests/tests/mcp/test_mcp_command.rs b/e2etests/tests/mcp/test_mcp_command.rs new file mode 100644 index 0000000000..770af280c7 --- /dev/null +++ b/e2etests/tests/mcp/test_mcp_command.rs @@ -0,0 +1,130 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_mcp_help_command", + "test_mcp_loading_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_mcp_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /mcp --help command... | Description: Tests the /mcp --help command to display help information for MCP server management functionality"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/mcp --help")?; + + println!("πŸ“ MCP help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify description + assert!(response.contains("See mcp server loaded"), "Missing mcp server description"); + println!("βœ… Found mcp server description"); + + // Verify Usage section + assert!(response.contains("Usage"), "Missing Usage section"); + assert!(response.contains("/mcp"), "Missing /mcp command in usage section"); + println!("βœ… Found Usage section with /mcp command"); + + // Verify Options section + assert!(response.contains("Options"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All mcp help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_mcp_loading_command() -> Result<(), Box> { + println!("\nπŸ” Testing /mcp command... | Description: Tests the /mcp command to display MCP server loading status and information"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/mcp")?; + + println!("πŸ“ MCP loading response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Check MCP status - either loaded or loading + if response.contains("loaded in") { + assert!(response.contains(" s"), "Missing seconds indicator for loading time"); + println!("βœ… Found MCPs loaded with timing"); + + // Count number of MCPs loaded + let mcp_count = response.matches("βœ“").count(); + println!("βœ… Found {} MCP(s) loaded", mcp_count); + } else if response.contains("loading") { + println!("βœ… MCPs are still loading"); + } else { + println!("ℹ️ MCP status unclear - may be in different state"); + } + + println!("βœ… All MCP loading content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + diff --git a/e2etests/tests/test_mcp_command.rs b/e2etests/tests/mcp/test_mcp_command_regression.rs similarity index 51% rename from e2etests/tests/test_mcp_command.rs rename to e2etests/tests/mcp/test_mcp_command_regression.rs index 31f34b7f32..c4c53cbadb 100644 --- a/e2etests/tests/test_mcp_command.rs +++ b/e2etests/tests/mcp/test_mcp_command_regression.rs @@ -1,116 +1,67 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); // List of covered tests +#[allow(dead_code)] const TEST_NAMES: &[&str] = &[ - "test_mcp_help_command", - "test_mcp_loading_command", "test_mcp_remove_help_command", - "test_q_mcp_add_help_command", - "test_q_mcp_help_command", - "test_q_mcp_import_help_command", - "test_q_mcp_list_command", - "test_q_mcp_list_help_command", - "test_q_mcp_status_help_command", - "test_add_and_remove_mcp_command" + "test_mcp_add_help_command", + "test_mcp_help_command", + "test_mcp_import_help_command", + "test_mcp_list_command", + "test_mcp_list_help_command", + "test_mcp_status_help_command", + "test_add_and_remove_mcp_command", + "test_mcp_status_command" ]; +#[allow(dead_code)] const TOTAL_TESTS: usize = TEST_NAMES.len(); #[test] -#[cfg(feature = "mcp")] -fn test_mcp_help_command() -> Result<(), Box> { - println!("πŸ” Testing /mcp --help command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - - let response = chat.execute_command("/mcp --help")?; - - println!("πŸ“ MCP help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify description - assert!(response.contains("See mcp server loaded"), "Missing mcp server description"); - println!("βœ… Found mcp server description"); - - // Verify Usage section - assert!(response.contains("Usage"), "Missing Usage section"); - assert!(response.contains("/mcp"), "Missing /mcp command in usage section"); - println!("βœ… Found Usage section with /mcp command"); - - // Verify Options section - assert!(response.contains("Options"), "Missing Options section"); - println!("βœ… Found Options section"); - - // Verify help flags - assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); - println!("βœ… Found help flags: -h, --help with Print help description"); - - println!("βœ… All mcp help content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "mcp")] -fn test_mcp_loading_command() -> Result<(), Box> { - println!("πŸ” Testing MCP loading..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - - let response = chat.execute_command("/mcp")?; - - println!("πŸ“ MCP loading response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Check MCP status - either loaded or loading - if response.contains("loaded in") { - assert!(response.contains(" s"), "Missing seconds indicator for loading time"); - println!("βœ… Found MCPs loaded with timing"); - - // Count number of MCPs loaded - let mcp_count = response.matches("βœ“").count(); - println!("βœ… Found {} MCP(s) loaded", mcp_count); - } else if response.contains("loading") { - println!("βœ… MCPs are still loading"); - } else { - println!("ℹ️ MCP status unclear - may be in different state"); - } - - println!("βœ… All MCP loading content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "mcp")] +#[cfg(all(feature = "mcp", feature = "regression"))] fn test_mcp_remove_help_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp remove --help command..."); + println!("\nπŸ” Testing q mcp remove --help command... | Description: Tests the q mcp remove --help command to display help information for removing MCP servers"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Execute q mcp remove --help command - let help_response = chat.execute_command("q mcp remove --help")?; + let help_response = chat.execute_command("execute below bash command q mcp remove --help")?; println!("πŸ“ MCP remove help response: {} bytes", help_response.len()); println!("πŸ“ HELP RESPONSE:"); @@ -118,8 +69,8 @@ fn test_mcp_remove_help_command() -> Result<(), Box> { println!("πŸ“ END HELP RESPONSE"); // Verify tool execution prompt appears - assert!(help_response.contains("πŸ› οΈ Using tool: execute_bash"), "Missing tool execution indicator"); - assert!(help_response.contains("Allow this action?") && help_response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(help_response.contains("Using tool"), "Missing tool execution indicator"); + assert!(help_response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution @@ -131,13 +82,12 @@ fn test_mcp_remove_help_command() -> Result<(), Box> { println!("πŸ“ END ALLOW RESPONSE"); // Verify complete help content in final response - assert!(allow_response.contains("Usage: qchat mcp remove"), "Missing usage information"); + assert!(allow_response.contains("Usage") && allow_response.contains("qchat mcp remove"), "Missing usage information"); assert!(allow_response.contains("Options"), "Missing option information"); assert!(allow_response.contains("--name "), "Missing --name option"); assert!(allow_response.contains("--scope "), "Missing --scope option"); assert!(allow_response.contains("--agent "), "Missing --agent option"); assert!(allow_response.contains("-h, --help"), "Missing help option"); - assert!(allow_response.contains("Completed in"), "Missing completion indicator"); println!("βœ… Found all expected MCP remove help content and completion"); // Release the lock before cleanup @@ -150,16 +100,16 @@ fn test_mcp_remove_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "mcp")] -fn test_q_mcp_add_help_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp add --help command..."); +#[cfg(all(feature = "mcp", feature = "regression"))] +fn test_mcp_add_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp add --help command... | Description: Tests the q mcp add --help command to display help information for adding new MCP servers"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Execute mcp add --help command - println!("πŸ” Executing command: 'q mcp add --help'"); - let response = chat.execute_command("q mcp add --help")?; + println!("\nπŸ” Executing command: 'q mcp add --help'"); + let response = chat.execute_command("execute below bash command q mcp add --help")?; println!("πŸ“ Restart response: {} bytes", response.len()); println!("πŸ“ RESTART RESPONSE:"); @@ -168,12 +118,12 @@ fn test_q_mcp_add_help_command() -> Result<(), Box> { // Verify tool execution details assert!(response.contains("q mcp add --help"), "Missing command execution description"); - assert!(response.contains("Purpose:"), "Missing purpose description"); + assert!(response.contains("Purpose"), "Missing purpose description"); println!("βœ… Found tool execution details"); // Verify tool execution prompt appears - assert!(response.contains("πŸ› οΈ Using tool: execute_bash"), "Missing tool execution indicator"); - assert!(response.contains("Allow this action?") && response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); + assert!(response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution @@ -185,18 +135,13 @@ fn test_q_mcp_add_help_command() -> Result<(), Box> { println!("πŸ“ END ALLOW RESPONSE"); // Verify mcp add help output - assert!(allow_response.contains("Usage: qchat mcp add"), "Missing usage information"); + assert!(allow_response.contains("Usage") && allow_response.contains("qchat mcp add"), "Missing usage information"); assert!(allow_response.contains("Options"), "Missing Options"); assert!(allow_response.contains("--name "), "Missing --name option"); assert!(allow_response.contains("--command "), "Missing --command option"); assert!(allow_response.contains("--scope "), "Missing --scope option"); - assert!(allow_response.contains("--args "), "Missing --args option"); assert!(allow_response.contains("--agent "), "Missing --agent option"); - assert!(allow_response.contains("--env "), "Missing --env option"); - assert!(allow_response.contains("--timeout "), "Missing --timeout option"); - assert!(allow_response.contains("--disabled"), "Missing --disabled option"); assert!(allow_response.contains("--force"), "Missing --force option"); - assert!(allow_response.contains("--verbose"), "Missing --verbose option"); assert!(allow_response.contains("--help"), "Missing --help option"); assert!(allow_response.contains("Completed in"), "Missing completion indicator"); assert!(allow_response.contains("Required"), "Missing Requried indicator"); @@ -213,15 +158,15 @@ fn test_q_mcp_add_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "mcp")] -fn test_q_mcp_help_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp --help command..."); +#[cfg(all(feature = "mcp", feature = "regression"))] +fn test_mcp_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp --help command... | Description: Tests the q mcp --help command to display comprehensive MCP management help including all subcommands"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Execute q mcp --help command - let help_response = chat.execute_command("q mcp --help")?; + let help_response = chat.execute_command("execute below bash command q mcp --help")?; println!("πŸ“ MCP help response: {} bytes", help_response.len()); println!("πŸ“ HELP RESPONSE:"); @@ -229,8 +174,8 @@ fn test_q_mcp_help_command() -> Result<(), Box> { println!("πŸ“ END HELP RESPONSE"); // Verify tool execution prompt appears - assert!(help_response.contains("πŸ› οΈ Using tool: execute_bash"), "Missing tool execution indicator"); - assert!(help_response.contains("Allow this action?") && help_response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(help_response.contains("Using tool"), "Missing tool execution indicator"); + assert!(help_response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution @@ -243,8 +188,8 @@ fn test_q_mcp_help_command() -> Result<(), Box> { // Verify complete help content assert!(allow_response.contains("Model Context Protocol (MCP)"), "Missing MCP description"); - assert!(allow_response.contains("Usage: qchat mcp"), "Missing usage information"); - assert!(allow_response.contains("Commands:"), "Missing Commands section"); + assert!(allow_response.contains("Usage") && allow_response.contains("qchat mcp"), "Missing usage information"); + assert!(allow_response.contains("Commands"), "Missing Commands section"); // Verify command descriptions assert!(allow_response.contains("add"), "Missing add command description"); @@ -258,7 +203,6 @@ fn test_q_mcp_help_command() -> Result<(), Box> { assert!(allow_response.contains("Options"), "Missing Options section"); assert!(allow_response.contains("-v, --verbose"), "Missing verbose option"); assert!(allow_response.contains("-h, --help"), "Missing help option"); - assert!(allow_response.contains("Completed in"), "Missing completion indicator"); println!("βœ… Found all expected MCP help content and completion"); // Release the lock before cleanup @@ -271,16 +215,16 @@ fn test_q_mcp_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "mcp")] -fn test_q_mcp_import_help_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp import --help command..."); +#[cfg(all(feature = "mcp", feature = "regression"))] +fn test_mcp_import_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp import --help command... | Description: Tests the q mcp import --help command to display help information for importing MCP server configurations"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Execute mcp import --help command - println!("πŸ” Executing command: 'q mcp import --help'"); - let response = chat.execute_command("q mcp import --help")?; + println!("\nπŸ” Executing command: 'q mcp import --help'"); + let response = chat.execute_command("execute below bash command q mcp import --help")?; println!("πŸ“ Restart response: {} bytes", response.len()); println!("πŸ“ RESTART RESPONSE:"); @@ -289,12 +233,12 @@ fn test_q_mcp_import_help_command() -> Result<(), Box> { // Verify tool execution details assert!(response.contains("q mcp import --help"), "Missing command execution description"); - assert!(response.contains("Purpose:"), "Missing purpose description"); + assert!(response.contains("Purpose"), "Missing purpose description"); println!("βœ… Found tool execution details"); // Verify tool execution prompt appears - assert!(response.contains("πŸ› οΈ Using tool: execute_bash"), "Missing tool execution indicator"); - assert!(response.contains("Allow this action?") && response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); + assert!(response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution @@ -321,10 +265,6 @@ fn test_q_mcp_import_help_command() -> Result<(), Box> { assert!(allow_response.contains("-h, --help"), "Missing --help option"); println!("βœ… Found all options with descriptions"); - // Verify completion indicator - assert!(allow_response.contains("Completed in") && allow_response.contains("s"), "Missing completion time indicator"); - println!("βœ… Found completion indicator"); - println!("βœ… All q mcp import --help content verified successfully"); // Release the lock before cleanup @@ -337,14 +277,14 @@ fn test_q_mcp_import_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "mcp")] -fn test_q_mcp_list_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp list command..."); +#[cfg(all(feature = "mcp", feature = "regression"))] +fn test_mcp_list_command() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp list command... | Description: Tests the q mcp list command to display all configured MCP servers and their status"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - let response = chat.execute_command("q mcp list")?; + let response = chat.execute_command("execute below bash command q mcp list")?; println!("πŸ“ MCP list response: {} bytes", response.len()); println!("πŸ“ FULL OUTPUT:"); @@ -352,9 +292,9 @@ fn test_q_mcp_list_command() -> Result<(), Box> { println!("πŸ“ END OUTPUT"); // Verify tool execution prompt - assert!(response.contains("Using tool:"), "Missing tool execution indicator"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); assert!(response.contains("q mcp list"), "Missing command in tool execution"); - assert!(response.contains("Allow this action?") && response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution @@ -368,8 +308,6 @@ fn test_q_mcp_list_command() -> Result<(), Box> { // Verify MCP server listing assert!(allow_response.contains("q_cli_default"), "Missing q_cli_default server"); - assert!(allow_response.contains("default"), "Missing default tag"); - assert!(allow_response.contains("global"), "Missing global tag"); println!("βœ… Found MCP server listing with servers and completion"); // Release the lock before cleanup @@ -382,14 +320,14 @@ fn test_q_mcp_list_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "mcp")] -fn test_q_mcp_list_help_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp list --help command..."); +#[cfg(all(feature = "mcp", feature = "regression"))] +fn test_mcp_list_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp list --help command... | Description: Tests the q mcp list --help command to display help information for listing MCP servers"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - let response = chat.execute_command("q mcp list --help")?; + let response = chat.execute_command("execute below bash command q mcp list --help")?; println!("πŸ“ MCP list help response: {} bytes", response.len()); println!("πŸ“ FULL OUTPUT:"); @@ -397,9 +335,9 @@ fn test_q_mcp_list_help_command() -> Result<(), Box> { println!("πŸ“ END OUTPUT"); // Verify tool execution prompt - assert!(response.contains("Using tool:"), "Missing tool execution indicator"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); assert!(response.contains("q mcp list --help"), "Missing command in tool execution"); - assert!(response.contains("Allow this action?") && response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution @@ -421,10 +359,6 @@ fn test_q_mcp_list_help_command() -> Result<(), Box> { assert!(allow_response.contains("Options"), "Missing Options section"); assert!(allow_response.contains("-v") && allow_response.contains("--verbose"), "Missing verbose option"); assert!(allow_response.contains("-h") && allow_response.contains("--help"), "Missing help option"); - - - assert!(allow_response.contains("Completed in"), "Missing completion indicator"); - println!("βœ… Found all MCP list help content with explanations and completion"); // Release the lock before cleanup drop(chat); @@ -436,16 +370,16 @@ fn test_q_mcp_list_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "mcp")] -fn test_q_mcp_status_help_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp status --help command..."); +#[cfg(all(feature = "mcp", feature = "regression"))] +fn test_mcp_status_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp status --help command... | Description: Tests the q mcp status --help command to display help information for checking MCP server status"); let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Execute mcp status --help command - println!("πŸ” Executing command: 'q mcp status --help'"); - let response = chat.execute_command("q mcp status --help")?; + println!("\nπŸ” Executing command: 'q mcp status --help'"); + let response = chat.execute_command("execute below bash command q mcp status --help")?; println!("πŸ“ Restart response: {} bytes", response.len()); println!("πŸ“ RESTART RESPONSE:"); @@ -453,12 +387,12 @@ fn test_q_mcp_status_help_command() -> Result<(), Box> { println!("πŸ“ END RESTART RESPONSE"); // Verify tool execution details - assert!(response.contains("Purpose:"), "Missing purpose description"); + assert!(response.contains("Purpose"), "Missing purpose description"); println!("βœ… Found tool execution details"); // Verify tool execution prompt appears - assert!(response.contains("πŸ› οΈ Using tool: execute_bash"), "Missing tool execution indicator"); - assert!(response.contains("Allow this action?") && response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); + assert!(response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution @@ -470,20 +404,16 @@ fn test_q_mcp_status_help_command() -> Result<(), Box> { println!("πŸ“ END ALLOW RESPONSE"); // Verify usage line - assert!(allow_response.contains("Usage: qchat mcp status [OPTIONS] --name "), "Missing complete usage line"); + assert!(allow_response.contains("Usage") && allow_response.contains("qchat mcp status [OPTIONS] --name "), "Missing complete usage line"); println!("βœ… Found usage information"); // Verify Options section assert!(allow_response.contains("Options"), "Missing Options section"); assert!(allow_response.contains("--name "), "Missing --name option"); - assert!(allow_response.contains("-v, --verbose...") , "Missing --verbose option"); + assert!(allow_response.contains("-v, --verbose") , "Missing --verbose option"); assert!(allow_response.contains("-h, --help"), "Missing --help option"); println!("βœ… Found all options with descriptions"); - // Verify completion indicator - assert!(allow_response.contains("Completed in") && allow_response.contains("s"), "Missing completion time indicator"); - println!("βœ… Found completion indicator"); - println!("βœ… All q mcp status --help content verified successfully"); // Release the lock before cleanup @@ -496,24 +426,66 @@ fn test_q_mcp_status_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "mcp")] +#[cfg(all(feature = "mcp", feature = "regression"))] fn test_add_and_remove_mcp_command() -> Result<(), Box> { - println!("πŸ” Testing q mcp add command..."); - + println!("\nπŸ” Testing q mcp add command... | Description: Tests the q mcp add and q mcp remove subcommands to add and remove MCP servers"); + // First install uv dependency before starting Q Chat - println!("πŸ” Installing uv dependency..."); + println!("\nπŸ” Installing uv dependency..."); + std::process::Command::new("pip3") .args(["install", "uv", "--break-system-packages"]) .output() .expect("Failed to install uv"); - println!("βœ… uv dependency installed"); + println!("βœ… uv dependency installed"); + let session = get_chat_session(); let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - - // Execute mcp add command - println!("πŸ” Executing command: 'q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest'"); - let response = chat.execute_command("q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest")?; + + // First check if MCP already exists using q mcp list + println!("\nπŸ” Checking if aws-documentation MCP already exists..."); + let list_response = chat.execute_command("execute below bash command q mcp list")?; + + println!("πŸ“ List response: {} bytes", list_response.len()); + println!("πŸ“ LIST RESPONSE:"); + println!("{}", list_response); + println!("πŸ“ END LIST RESPONSE"); + + // Allow the list command + let list_allow_response = chat.execute_command("y")?; + println!("πŸ“ List allow response: {} bytes", list_allow_response.len()); + println!("πŸ“ LIST ALLOW RESPONSE:"); + println!("{}", list_allow_response); + println!("πŸ“ END LIST ALLOW RESPONSE"); + + // Check if aws-documentation exists in the list + if list_allow_response.contains("aws-documentation") { + println!("\nπŸ” aws-documentation MCP already exists, removing it first..."); + + let remove_response = chat.execute_command("execute below bash command q mcp remove --name aws-documentation")?; + println!("πŸ“ Remove response: {} bytes", remove_response.len()); + println!("πŸ“ REMOVE RESPONSE:"); + println!("{}", remove_response); + println!("πŸ“ END REMOVE RESPONSE"); + + // Allow the remove command + let remove_allow_response = chat.execute_command("y")?; + println!("πŸ“ Remove allow response: {} bytes", remove_allow_response.len()); + println!("πŸ“ REMOVE ALLOW RESPONSE:"); + println!("{}", remove_allow_response); + println!("πŸ“ END REMOVE ALLOW RESPONSE"); + + // Verify successful removal + assert!(remove_allow_response.contains("Removed") && remove_allow_response.contains("'aws-documentation'"), "Missing removal success message"); + println!("βœ… Successfully removed existing aws-documentation MCP"); + } else { + println!("βœ… aws-documentation MCP does not exist, proceeding with add"); + } + + // Now add the MCP server + println!("\nπŸ” Executing command: 'q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest'"); + let response = chat.execute_command("execute below bash command q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest")?; println!("πŸ“ Response: {} bytes", response.len()); println!("πŸ“ RESPONSE:"); @@ -521,78 +493,157 @@ fn test_add_and_remove_mcp_command() -> Result<(), Box> { println!("πŸ“ END RESPONSE"); // Verify tool execution details - assert!(response.contains("Using tool:"), "Missing using tool indicator"); assert!(response.contains("q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest"), "Missing full command"); - assert!(response.contains("Purpose:"), "Missing purpose description"); - println!("βœ… Found tool execution details"); - - // Verify tool execution prompt appears - assert!(response.contains("πŸ› οΈ Using tool: execute_bash"), "Missing tool execution indicator"); - assert!(response.contains("Allow this action?") && response.contains("to trust (always allow) this tool for the session."), "Missing permission prompt"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); + assert!(response.contains("Allow this action?"), "Missing permission prompt"); println!("βœ… Found tool execution permission prompt"); // Allow the tool execution let allow_response = chat.execute_command("y")?; - println!("πŸ“ Allow response: {} bytes", allow_response.len()); println!("πŸ“ ALLOW RESPONSE:"); println!("{}", allow_response); println!("πŸ“ END ALLOW RESPONSE"); // Verify successful addition - assert!(allow_response.contains("Added MCP server") && allow_response.contains("'aws-documentation'"), "Missing success message"); + assert!(allow_response.contains("Added") && allow_response.contains("'aws-documentation'"), "Missing success message"); assert!(allow_response.contains("/Users/") && allow_response.contains("/.aws/amazonq/mcp.json"), "Missing config file path"); println!("βœ… Found successful addition message"); - // Verify completion indicator - assert!(allow_response.contains("Completed in") && allow_response.contains("s"), "Missing completion time indicator"); - println!("βœ… Found completion indicator"); - - println!("βœ… All q mcp add command execution verified successfully"); - // Now test removing the MCP server - println!("πŸ” Executing remove command: 'q mcp remove --name aws-documentation'"); - let remove_response = chat.execute_command("q mcp remove --name aws-documentation")?; - + println!("\nπŸ” Executing remove command: 'q mcp remove --name aws-documentation'"); + let remove_response = chat.execute_command("execute below bash command q mcp remove --name aws-documentation")?; println!("πŸ“ Remove response: {} bytes", remove_response.len()); println!("πŸ“ REMOVE RESPONSE:"); println!("{}", remove_response); println!("πŸ“ END REMOVE RESPONSE"); // Verify remove tool execution details - assert!(response.contains("Using tool:"), "Missing using tool indicator"); + assert!(response.contains("Using tool"), "Missing using tool indicator"); assert!(remove_response.contains("q mcp remove --name aws-documentation"), "Missing full remove command"); - println!("βœ… Found remove tool execution details"); - - // Verify remove tool execution prompt - assert!(remove_response.contains("πŸ› οΈ Using tool: execute_bash"), "Missing remove tool execution indicator"); assert!(remove_response.contains("Allow this action?"), "Missing remove permission prompt"); println!("βœ… Found remove tool execution permission prompt"); // Allow the remove tool execution let remove_allow_response = chat.execute_command("y")?; - println!("πŸ“ Remove allow response: {} bytes", remove_allow_response.len()); println!("πŸ“ REMOVE ALLOW RESPONSE:"); println!("{}", remove_allow_response); println!("πŸ“ END REMOVE ALLOW RESPONSE"); // Verify successful removal - assert!(remove_allow_response.contains("Removed MCP server") && remove_allow_response.contains("'aws-documentation'"), "Missing removal success message"); + assert!(remove_allow_response.contains("Removed") && remove_allow_response.contains("'aws-documentation'"), "Missing removal success message"); assert!(remove_allow_response.contains("/Users/") && remove_allow_response.contains("/.aws/amazonq/mcp.json"), "Missing config file path in removal"); println!("βœ… Found successful removal message"); - // Verify remove completion indicator - assert!(remove_allow_response.contains("Completed in") && remove_allow_response.contains("s"), "Missing remove completion time indicator"); - println!("βœ… Found remove completion indicator"); + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "regression"))] +fn test_mcp_status_command() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp status --name command... | Description: Tests the q mcp status command with server name to display detailed status information for a specific MCP server"); + + // First install uv dependency before starting Q Chat + println!("\nπŸ” Installing uv dependency..."); + + std::process::Command::new("pip3") + .args(["install", "uv", "--break-system-packages"]) + .output() + .expect("Failed to install uv"); + + println!("βœ… uv dependency installed"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Execute mcp add command + println!("\nπŸ” Executing command: 'q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest'"); + let response = chat.execute_command("execute below bash command q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest")?; - println!("βœ… All q mcp remove command execution verified successfully"); + println!("πŸ“ Response: {} bytes", response.len()); + println!("πŸ“ RESPONSE:"); + println!("{}", response); + println!("πŸ“ END RESPONSE"); + + // Verify tool execution details + assert!(response.contains("q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest"), "Missing full command"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); + println!("βœ… Found tool execution permission prompt"); + + // Allow the tool execution + let allow_response = chat.execute_command("y")?; + println!("πŸ“ Allow response: {} bytes", allow_response.len()); + println!("πŸ“ ALLOW RESPONSE:"); + println!("{}", allow_response); + println!("πŸ“ END ALLOW RESPONSE"); + + // Verify successful addition + assert!(allow_response.contains("Added") && allow_response.contains("'aws-documentation'"), "Missing success message"); + println!("βœ… Found successful addition message"); + + // Allow the tool execution + let response = chat.execute_command("execute below bash command q mcp status --name aws-documentation")?; + println!("πŸ“ Allow response: {} bytes", response.len()); + println!("πŸ“ ALLOW RESPONSE:"); + println!("{}", response); + println!("πŸ“ END ALLOW RESPONSE"); + + // Verify tool execution details + assert!(response.contains("q mcp status --name aws-documentation"), "Missing full command"); + assert!(response.contains("Using tool"), "Missing tool execution indicator"); + println!("βœ… Found tool execution permission prompt"); + + // Allow the tool execution + let show_response = chat.execute_command("y")?; + println!("πŸ“ Allow response: {} bytes", show_response.len()); + println!("πŸ“ ALLOW RESPONSE:"); + println!("{}", show_response); + println!("πŸ“ END ALLOW RESPONSE"); + + + // Verify successful status retrieval + assert!(show_response.contains("Scope"), "Missing Scope"); + assert!(show_response.contains("Agent"), "Missing Agent"); + assert!(show_response.contains("Command"), "Missing Command"); + assert!(show_response.contains("Disabled"), "Missing Disabled"); + assert!(show_response.contains("Env Vars"), "Missing Env Vars"); + + // Now test removing the MCP server + println!("\nπŸ” Executing remove command: 'q mcp remove --name aws-documentation'"); + let remove_response = chat.execute_command("execute below bash command q mcp remove --name aws-documentation")?; + println!("πŸ“ Remove response: {} bytes", remove_response.len()); + println!("πŸ“ REMOVE RESPONSE:"); + println!("{}", remove_response); + println!("πŸ“ END REMOVE RESPONSE"); + + // Verify remove tool execution details + assert!(response.contains("Using tool"), "Missing using tool indicator"); + assert!(remove_response.contains("q mcp remove --name aws-documentation"), "Missing full remove command"); + assert!(remove_response.contains("Allow this action?"), "Missing remove permission prompt"); + println!("βœ… Found remove tool execution permission prompt"); + + // Allow the remove tool execution + let remove_allow_response = chat.execute_command("y")?; + println!("πŸ“ Remove allow response: {} bytes", remove_allow_response.len()); + println!("πŸ“ REMOVE ALLOW RESPONSE:"); + println!("{}", remove_allow_response); + println!("πŸ“ END REMOVE ALLOW RESPONSE"); + + // Verify successful removal + assert!(remove_allow_response.contains("Removed") && remove_allow_response.contains("'aws-documentation'"), "Missing removal success message"); + assert!(remove_allow_response.contains("/Users/") && remove_allow_response.contains("/.aws/amazonq/mcp.json"), "Missing config file path in removal"); + println!("βœ… Found successful removal message"); // Release the lock before cleanup drop(chat); // Cleanup only if this is the last test cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - Ok(()) -} \ No newline at end of file +} diff --git a/e2etests/tests/mcp/test_q_mcp_subcommand.rs b/e2etests/tests/mcp/test_q_mcp_subcommand.rs new file mode 100644 index 0000000000..2f691e1728 --- /dev/null +++ b/e2etests/tests/mcp/test_q_mcp_subcommand.rs @@ -0,0 +1,353 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp --help subcommand... | Description: Tests the q mcp --help subcommand to display comprehensive MCP management help including all commands"); + + println!("\nπŸ” Executing q [subcommand]: 'q mcp --help'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "--help"])?; + + println!("πŸ“ MCP help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify complete help content + assert!(response.contains("Model Context Protocol (MCP)"), "Missing MCP description"); + assert!(response.contains("Usage") && response.contains("qchat mcp"), "Missing usage information"); + assert!(response.contains("Commands"), "Missing Commands section"); + + // Verify command descriptions + assert!(response.contains("add"), "Missing add command description"); + assert!(response.contains("remove"), "Missing remove command description"); + assert!(response.contains("list"), "Missing list command description"); + assert!(response.contains("import"), "Missing import command description"); + assert!(response.contains("status"), "Missing status command description"); + assert!(response.contains("help"), "Missing help command"); + println!("βœ… Found all MCP commands with descriptions"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_remove_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp remove --help subcommand... | Description: Tests the q mcp remove --help subcommand to display help information for removing MCP servers"); + + println!("\nπŸ” Executing q [subcommand]: 'q mcp remove --help'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "remove", "--help"])?; + + println!("πŸ“ MCP remove help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify complete help content in final response + assert!(response.contains("Usage") && response.contains("qchat mcp remove"), "Missing usage information"); + assert!(response.contains("Options"), "Missing option information"); + assert!(response.contains("--name"), "Missing --name option"); + assert!(response.contains("--scope"), "Missing --scope option"); + assert!(response.contains("--agent"), "Missing --agent option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing help option"); + println!("βœ… Found all expected MCP remove help content and completion"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_add_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp add --help subcommand... | Description: Tests the q mcp add --help subcommand to display help information for adding new MCP servers"); + + println!("\nπŸ” Executing q [subcommand]: 'q mcp add --help'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "add", "--help"])?; + + println!("πŸ“ Restart response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify mcp add help output + assert!(response.contains("Usage") && response.contains("qchat mcp add"), "Missing usage information"); + assert!(response.contains("Options"), "Missing Options"); + assert!(response.contains("--name"), "Missing --name option"); + assert!(response.contains("--command"), "Missing --command option"); + assert!(response.contains("--scope"), "Missing --scope option"); + assert!(response.contains("--agent"), "Missing --agent option"); + println!("βœ… MCP add help subcommand executed successfully"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_import_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp import --help subcommand... | Description: Tests the q mcp import --help subcommand to display help information for importing MCP server configurations"); + + println!("\nπŸ” Executing q [subcommand]: 'q mcp import --help'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "import", "--help"])?; + + println!("πŸ“ Restart response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Options section + assert!(response.contains("Options"), "Missing Options section"); + assert!(response.contains("--file"), "Missing --file option"); + assert!(response.contains("--force"), "Missing --force option"); + assert!(response.contains("-v") && response.contains("--verbose"), "Missing --verbose option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing --help option"); + println!("βœ… Found all options with descriptions"); + + println!("βœ… All q mcp import --help content verified successfully"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_list_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp list subcommand... | Description: Tests the q mcp list subcommand to display all configured MCP servers and their status"); + + println!("\nπŸ” Executing q [subcommand]: 'q mcp list'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "list"])?; + + println!("πŸ“ MCP list response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify MCP server listing + assert!(response.contains("q_cli_default"), "Missing q_cli_default server"); + println!("βœ… Found MCP server listing with servers and completion"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_list_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp list --help subcommand... | Description: Tests the q mcp list --help subcommand to display help information for listing MCP servers"); + + println!("\nπŸ” Executing q [subcommand]: 'q mcp list --help'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "list", "--help"])?; + + println!("πŸ“ MCP list help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify help content + assert!(response.contains("Usage"), "Missing usage format"); + + // Verify arguments section + assert!(response.contains("Arguments"), "Missing Arguments section"); + assert!(response.contains("[SCOPE]"), "Missing scope argument"); + + // Verify options section + assert!(response.contains("Options"), "Missing Options section"); + assert!(response.contains("-v") && response.contains("--verbose"), "Missing verbose option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing help option"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_status_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp status --help subcommand... | Description: Tests the q mcp status --help subcommand to display help information for checking MCP server status"); + + // Execute mcp status --help subcommand + println!("\nπŸ” Executing q [subcommand]: 'q mcp status --help'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "status", "--help"])?; + + println!("πŸ“ Restart response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify usage line + assert!(response.contains("Usage"), "Missing usage information"); + // Verify Options section + assert!(response.contains("Options"), "Missing Options section"); + assert!(response.contains("--name"), "Missing --name option"); + assert!(response.contains("-v") && response.contains("--verbose") , "Missing --verbose option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing --help option"); + println!("βœ… Found all options with descriptions"); + + println!("βœ… All q mcp status --help content verified successfully"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_add_and_remove_mcp_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp add and remove subcommands... | Description: Tests the q mcp add and q mcp remove subcommands to add and remove MCP servers"); + + // First install uv dependency before starting Q Chat + println!("\nπŸ” Installing uv dependency..."); + + std::process::Command::new("pip3") + .args(["install", "uv", "--break-system-packages"]) + .output() + .expect("Failed to install uv"); + + println!("βœ… uv dependency installed"); + + // First check if MCP already exists using q mcp list + println!("\nπŸ” Checking if aws-documentation MCP already exists..."); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "list"])?; + + println!("πŸ“ Response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Check if aws-documentation exists in the list or config file + let mcp_config_exists = std::fs::read_to_string(std::env::var("HOME").unwrap_or_default() + "/.aws/amazonq/mcp.json") + .map(|content| content.contains("aws-documentation")) + .unwrap_or(false); + + if response.contains("aws-documentation") && mcp_config_exists { + println!("\nπŸ” aws-documentation MCP already exists, removing it first..."); + + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "remove", "--name", "aws-documentation"])?; + + println!("πŸ“ Response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify successful removal + assert!(response.contains("Removed") && response.contains("'aws-documentation'"), "Missing removal success message"); + println!("βœ… Successfully removed existing aws-documentation MCP"); + } else { + println!("βœ… aws-documentation MCP does not exist, proceeding with add"); + } + + // Now add the MCP server + println!("\nπŸ” Executing q [subcommand]: 'q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "add", "--name", "aws-documentation", "--command", "uvx", "--args", "awslabs.aws-documentation-mcp-server@latest"])?; + + println!("πŸ“ Response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify successful addition + assert!(response.contains("Added") && response.contains("'aws-documentation'"), "Missing success message"); + assert!(response.contains("/.aws/amazonq/mcp.json"), "Missing config file path"); + println!("βœ… Found successful addition message"); + + // Now test removing the MCP server + println!("\nπŸ” Executing q [subcommand]: 'q mcp remove --name aws-documentation'"); + let remove_response = q_chat_helper::execute_q_subcommand("q", &["mcp", "remove", "--name", "aws-documentation"])?; + + println!("πŸ“ Remove response: {} bytes", remove_response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", remove_response); + println!("πŸ“ END OUTPUT"); + + // Verify successful removal + assert!(remove_response.contains("Removed") && remove_response.contains("'aws-documentation'"), "Missing removal success message"); + assert!(remove_response.contains("/.aws/amazonq/mcp.json"), "Missing config file path in removal"); + println!("βœ… Found successful removal message"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "mcp", feature = "sanity"))] +fn test_q_mcp_status_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q mcp status --name subcommand... | Description: Tests the q mcp status subcommand with server name to display detailed status information for a specific MCP server"); + + // First install uv dependency before starting Q Chat + println!("\nπŸ” Installing uv dependency..."); + + std::process::Command::new("pip3") + .args(["install", "uv", "--break-system-packages"]) + .output() + .expect("Failed to install uv"); + + println!("βœ… uv dependency installed"); + + // First check if MCP already exists using q mcp list + println!("\nπŸ” Checking if aws-documentation MCP already exists..."); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "list"])?; + + println!("πŸ“ Response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Check if aws-documentation exists in the list or config file + let mcp_config_exists = std::fs::read_to_string(std::env::var("HOME").unwrap_or_default() + "/.aws/amazonq/mcp.json") + .map(|content| content.contains("aws-documentation")) + .unwrap_or(false); + + if response.contains("aws-documentation") && mcp_config_exists { + println!("\nπŸ” aws-documentation MCP already exists, removing it first..."); + + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "remove", "--name", "aws-documentation"])?; + + println!("πŸ“ Response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify successful removal + assert!(response.contains("Removed") && response.contains("'aws-documentation'"), "Missing removal success message"); + println!("βœ… Successfully removed existing aws-documentation MCP"); + } else { + println!("βœ… aws-documentation MCP does not exist, proceeding with add"); + } + + // Execute mcp add command + println!("\nπŸ” Executing q [subcommand]: 'q mcp add --name aws-documentation --command uvx --args awslabs.aws-documentation-mcp-server@latest'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "add", "--name", "aws-documentation", "--command", "uvx", "--args", "awslabs.aws-documentation-mcp-server@latest"])?; + + println!("πŸ“ Response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify successful addition + assert!(response.contains("Added") && response.contains("'aws-documentation'"), "Missing success message"); + println!("βœ… Found successful addition message"); + + // Allow the tool execution + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "status", "--name", "aws-documentation"])?; + + println!("πŸ“ Allow response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify successful status retrieval + assert!(response.contains("Scope"), "Missing Scope"); + assert!(response.contains("Agent"), "Missing Agent"); + assert!(response.contains("Command"), "Missing Command"); + assert!(response.contains("Disabled"), "Missing Disabled"); + assert!(response.contains("Env Vars"), "Missing Env Vars"); + + // Now test removing the MCP server + println!("\nπŸ” Executing q [subcommand]: 'q mcp remove --name aws-documentation'"); + let response = q_chat_helper::execute_q_subcommand("q", &["mcp", "remove", "--name", "aws-documentation"])?; + + println!("πŸ“ Remove response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify successful removal + assert!(response.contains("Removed") && response.contains("'aws-documentation'"), "Missing removal success message"); + assert!(response.contains("/.aws/amazonq/mcp.json"), "Missing config file path in removal"); + println!("βœ… Found successful removal message"); + + Ok(()) +} + diff --git a/e2etests/tests/model/mod.rs b/e2etests/tests/model/mod.rs new file mode 100644 index 0000000000..e64adec313 --- /dev/null +++ b/e2etests/tests/model/mod.rs @@ -0,0 +1 @@ +pub mod test_model_dynamic_command; \ No newline at end of file diff --git a/e2etests/tests/test_model_dynamic_command.rs b/e2etests/tests/model/test_model_dynamic_command.rs similarity index 60% rename from e2etests/tests/test_model_dynamic_command.rs rename to e2etests/tests/model/test_model_dynamic_command.rs index 5457badb99..8127318c2c 100644 --- a/e2etests/tests/test_model_dynamic_command.rs +++ b/e2etests/tests/model/test_model_dynamic_command.rs @@ -1,19 +1,55 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); // List of covered tests +#[allow(dead_code)] const TEST_NAMES: &[&str] = &[ "test_model_dynamic_command", "test_model_help_command", + "test_model_h_command", ]; +#[allow(dead_code)] const TOTAL_TESTS: usize = TEST_NAMES.len(); #[test] -#[cfg(feature = "model")] +#[cfg(all(feature = "model", feature = "sanity"))] fn test_model_dynamic_command() -> Result<(), Box> { - println!("πŸ” Testing /model command with dynamic selection..."); + println!("\nπŸ” Testing /model command with dynamic selection... | Description: Tests the /model command interactive selection interface to choose different models and verify selection confirmation"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -58,7 +94,7 @@ fn test_model_dynamic_command() -> Result<(), Box> { // After finding prompt, parse model lines if found_prompt { let cleaned_line = strip_ansi(trimmed_line); - println!("πŸ” Row: '{}' -> Cleaned: '{}'", trimmed_line, cleaned_line); + println!("\nπŸ” Row: '{}' -> Cleaned: '{}'", trimmed_line, cleaned_line); if !trimmed_line.is_empty() { // Check if line contains a model (starts with ❯, spaces, or contains model names) @@ -69,7 +105,7 @@ fn test_model_dynamic_command() -> Result<(), Box> { .trim() .to_string(); - println!("πŸ” Extracted model: '{}'", model_name); + println!("\nπŸ” Extracted model: '{}'", model_name); if !model_name.is_empty() { models.push(model_name); } @@ -130,9 +166,9 @@ fn test_model_dynamic_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "model")] +#[cfg(all(feature = "model", feature = "sanity"))] fn test_model_help_command() -> Result<(), Box> { - println!("πŸ” Testing /model --help command..."); + println!("\nπŸ” Testing /model --help command... | Description: Tests the /model --help command to display help information for model selection functionality"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -144,10 +180,6 @@ fn test_model_help_command() -> Result<(), Box> { println!("{}", response); println!("πŸ“ END OUTPUT"); - /* Verify description - assert!(response.contains("Select") && response.contains("model"), "Missing model selection description"); - println!("βœ… Found model selection description");*/ - // Verify Usage section assert!(response.contains("Usage:"), "Missing Usage section"); assert!(response.contains("/model"), "Missing /model command in usage section"); @@ -158,8 +190,7 @@ fn test_model_help_command() -> Result<(), Box> { println!("βœ… Found Options section"); // Verify help flags - assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); - assert!(response.contains("Print help"), "Missing Print help description"); + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); println!("βœ… Found help flags: -h, --help with Print help description"); println!("βœ… All model help content verified!"); @@ -172,3 +203,42 @@ fn test_model_help_command() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(all(feature = "model", feature = "sanity"))] +fn test_model_h_command() -> Result<(), Box> { + println!("\nπŸ” Testing /model -h command... | Description: Tests the /model -h command (short form) to display help information for model selection functionality"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + let response = chat.execute_command("/model -h")?; + + println!("πŸ“ Model help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("/model"), "Missing /model command in usage section"); + println!("βœ… Found Usage section with /model command"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with Print help description"); + + println!("βœ… All model help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/q_subcommand/mod.rs b/e2etests/tests/q_subcommand/mod.rs new file mode 100644 index 0000000000..a7257d9716 --- /dev/null +++ b/e2etests/tests/q_subcommand/mod.rs @@ -0,0 +1,7 @@ +pub mod test_q_chat_subcommand; +pub mod test_q_doctor_subcommand; +pub mod test_q_translate_subcommand; +pub mod test_q_setting_subcommand; +pub mod test_q_whoami_subcommand; +pub mod test_q_debug_subcommand; +pub mod test_q_inline_subcommand; \ No newline at end of file diff --git a/e2etests/tests/q_subcommand/test_q_chat_subcommand.rs b/e2etests/tests/q_subcommand/test_q_chat_subcommand.rs new file mode 100644 index 0000000000..f35b9d7a39 --- /dev/null +++ b/e2etests/tests/q_subcommand/test_q_chat_subcommand.rs @@ -0,0 +1,29 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_chat_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q chat subcommand... | Description: Tests the q chat subcommand that opens Q terminal for interactive AI conversations."); + + println!("\nπŸ” Executing 'q chat' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["chat", "\"what is aws?\""])?; + + println!("πŸ“ Chat response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Validate we got a proper AWS response + assert!(response.contains("Amazon Web Services") || response.contains("AWS"), + "Response should contain AWS information"); + assert!(response.len() > 100, "Response should be substantial"); + + println!("βœ… Got substantial AI response ({} bytes)!", response.len()); + + println!("βœ… Chat subcommand executed!"); + + println!("βœ… q chat subcommand executed successfully!"); + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/q_subcommand/test_q_debug_subcommand.rs b/e2etests/tests/q_subcommand/test_q_debug_subcommand.rs new file mode 100644 index 0000000000..df10882df2 --- /dev/null +++ b/e2etests/tests/q_subcommand/test_q_debug_subcommand.rs @@ -0,0 +1,205 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_debug_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q debug subcommand... | Description: Tests the q debug subcommand that provides debugging utilities for the app including app debugging, build switching, logs viewing, and various diagnostic tools."); + + println!("\nπŸ” Executing 'q debug' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["debug"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert debug help output contains expected commands + assert!(response.contains("Debug the app"), "Response should contain debug description"); + assert!(response.contains("Commands:"), "Response should list available commands"); + assert!(response.contains("app"), "Response should contain 'app' command"); + assert!(response.contains("build"), "Response should contain 'build' command"); + assert!(response.contains("logs"), "Response should contain 'logs' command"); + + println!("βœ… Got debug help output ({} bytes)!", response.len()); + println!("βœ… q debug subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_debug_app_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q debug app subcommand... | Description: Tests the q debug app subcommand that provides debugging utilities for the app including app debugging, build switching, logs viewing, and various diagnostic tools."); + + println!("\nπŸ” Executing 'q debug app' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["debug", "app"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q debug app launches the Amazon Q interface + assert!(response.contains("Amazon Q"), "Response should contain 'Amazon Q'"); + assert!(response.contains("πŸ€– You are chatting with"), "Response should show chat interface"); + + println!("βœ… Got debug app output ({} bytes)!", response.len()); + println!("βœ… q debug app subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_debug_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q debug --help subcommand... | Description: Tests the q debug --help subcommand to validate help output format and content."); + + println!("\nπŸ” Executing 'q debug --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["debug", "help"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert debug help output contains expected commands + assert!(response.contains("Usage:") && response.contains("q debug") && response.contains("[OPTIONS]") && response.contains(""), + "Help should contain usage line"); + assert!(response.contains("Commands:"), "Response should list available commands"); + assert!(response.contains("app"), "Response should contain 'app' command"); + assert!(response.contains("build"), "Response should contain 'build' command"); + assert!(response.contains("logs"), "Response should contain 'logs' command"); + assert!(response.contains("Options:"), + "Help should contain Options section"); + assert!(response.contains("-v, --verbose"), + "Help should contain verbose option"); + assert!(response.contains("-h, --help"), + "Should contain help option"); + + println!("βœ… Got debug help output ({} bytes)!", response.len()); + println!("βœ… q debug --help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_debug_build_help() -> Result<(), Box> { + println!("\nπŸ” Testing q debug build --help subcommand... | Description: Tests the q debug build --help subcommand to validate help output format and available build options."); + + println!("\nπŸ” Executing 'q debug build --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["debug", "build", "--help"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert expected output + assert!(response.contains("Usage: q debug build [OPTIONS] [BUILD]"), "Response should contain usage line"); + assert!(response.contains(""), "Response should contain APP argument"); + assert!(response.contains("[BUILD]"), "Response should contain BUILD argument"); + assert!(response.contains("-v, --verbose... Increase logging verbosity"), "Response should contain verbose option"); + assert!(response.contains("-h, --help Print help"), "Response should contain help option"); + + println!("βœ… Got debug build help output ({} bytes)!", response.len()); + println!("βœ… q debug build --help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_debug_build_autocomplete() -> Result<(), Box> { + println!("\nπŸ” Testing q debug build autocomplete subcommand... | Description: Tests the q debug build autocomplete subcommand to get current autocomplete build version."); + + println!("\nπŸ” Executing 'q debug build autocomplete' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["debug", "build", "autocomplete"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert expected output (should be either "production" or "beta") + assert!(response.contains("production") || response.contains("beta"), "Response should contain either 'production' or 'beta'"); + + println!("βœ… Got debug build autocomplete output ({} bytes)!", response.len()); + println!("βœ… q debug build autocomplete subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_debug_build_dashboard() -> Result<(), Box> { + println!("\nπŸ” Testing q debug build dashboard subcommand... | Description: Tests the q debug build dashboard subcommand to get current dashboard build version."); + + println!("\nπŸ” Executing 'q debug build dashboard' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["debug", "build", "dashboard"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert expected output (should be either "production" or "beta") + assert!(response.contains("production") || response.contains("beta"), "Response should contain either 'production' or 'beta'"); + + println!("βœ… Got debug build dashboard output ({} bytes)!", response.len()); + println!("βœ… q debug build dashboard subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_debug_build_autocomplete_switch() -> Result<(), Box> { + println!("\nπŸ” Testing q debug build autocomplete switch functionality... | Description: Tests the q debug build autocomplete <build> subcommand to switch between different autocomplete builds and revert back."); + + let builds = ["production", "beta"]; + + // Get current build + println!("\nπŸ” Getting current build..."); + let current_response = q_chat_helper::execute_q_subcommand("q", &["debug", "build", "autocomplete"])?; + let current_build = current_response.split_whitespace().last().unwrap_or("production"); + + println!("πŸ“ Build response: {} bytes", current_response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", current_response); + println!("πŸ“ END OUTPUT"); + + // Find any different build from the array + let other_build = builds.iter().find(|&&b| b != current_build) + .unwrap_or(&"beta"); // fallback to beta if current not found in array + + + // Switch to other build + println!("\nπŸ” Switching to {} build...", other_build); + let switch_response = q_chat_helper::execute_q_subcommand("q", &["debug", "build", "autocomplete", other_build])?; + + println!("πŸ“ Switch response: {} bytes", switch_response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", switch_response); + println!("πŸ“ END OUTPUT"); + + assert!(switch_response.contains("Amazon Q") && switch_response.contains(other_build) && switch_response.contains("autocomplete")); + println!("βœ… Switched to {} build successfully!", other_build); + + // Switch back to original build + println!("\nπŸ” Switching back to {} build...", current_build); + let revert_response = q_chat_helper::execute_q_subcommand("q", &["debug", "build", "autocomplete", current_build])?; + + println!("πŸ“ Switching back response: {} bytes", revert_response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", revert_response); + println!("πŸ“ END OUTPUT"); + + assert!(revert_response.contains("Amazon Q") && revert_response.contains(current_build) && revert_response.contains("autocomplete")); + println!("βœ… Switched back to {} build successfully!", current_build); + + println!("βœ… Build switching test completed successfully!"); + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/q_subcommand/test_q_doctor_subcommand.rs b/e2etests/tests/q_subcommand/test_q_doctor_subcommand.rs new file mode 100644 index 0000000000..43afb94cbe --- /dev/null +++ b/e2etests/tests/q_subcommand/test_q_doctor_subcommand.rs @@ -0,0 +1,27 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_doctor_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q doctor subcommand... | Description: Tests the q doctor subcommand that debugs installation issues"); + + println!("\nπŸ” Executing 'q doctor' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["doctor"])?; + + println!("πŸ“ Doctor response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + assert!(response.contains("q issue"), "Missing troubleshooting message"); + println!("βœ… Found troubleshooting message"); + + if response.contains("Everything looks good!") { + println!("βœ… Doctor check passed - everything looks good!"); + } + + println!("βœ… Doctor subcommand output verified!"); + + Ok(()) +} diff --git a/e2etests/tests/q_subcommand/test_q_inline_subcommand.rs b/e2etests/tests/q_subcommand/test_q_inline_subcommand.rs new file mode 100644 index 0000000000..1e4f07478b --- /dev/null +++ b/e2etests/tests/q_subcommand/test_q_inline_subcommand.rs @@ -0,0 +1,301 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline subcommand... | Description: Tests the q inline subcommand for inline shell completion"); + + println!("\nπŸ” Executing 'q inline' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline shows inline shell completions help + assert!(response.contains("Inline shell completions"), "Response should contain 'Inline shell completions'"); + assert!(response.contains("enable"), "Response should show 'enable' command"); + assert!(response.contains("disable"), "Response should show 'disable' command"); + assert!(response.contains("status"), "Response should show 'status' command"); + + println!("βœ… q inline subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline --help subcommand... | Description: Tests the q inline --help subcommand for inline shell completion"); + + println!("\nπŸ” Executing 'q inline --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand_with_stdin("q", &["inline"], Some("--help"))?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline shows inline shell completions help + assert!(response.contains("Inline shell completions"), "Response should contain 'Inline shell completions'"); + assert!(response.contains("enable"), "Response should show 'enable' command"); + assert!(response.contains("disable"), "Response should show 'disable' command"); + assert!(response.contains("status"), "Response should show 'status' command"); + + println!("βœ… q inline help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_disable_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline disable subcommand... | Description: Tests the q inline disable subcommand for disabling inline"); + + println!("\nπŸ” Executing 'q inline disable' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "disable"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline disable shows success message + assert!(response.contains("Inline disabled"), "Response should contain 'Inline disabled'"); + + println!("βœ… q inline disable subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_disable_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline disable --help subcommand... | Description: Tests the q inline disable --help subcommand to show help for disabling inline"); + + println!("\nπŸ” Executing 'q inline disable --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "disable", "--help"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + assert!(response.contains("q inline disable"), "Response should contain 'q inline disable'"); + + println!("βœ… q inline disable help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_enable_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline enable subcommand... | Description: Tests the q inline enable subcommand for enabling inline"); + + println!("\nπŸ” Executing 'q inline enable' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "enable"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline enable shows success message + assert!(response.contains("Inline enabled"), "Response should contain 'Inline enabled'"); + + println!("βœ… q inline enable subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_enable_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline enable --help subcommand... | Description: Tests the q inline enable --help subcommand to show help for enabling inline"); + + println!("\nπŸ” Executing 'q inline enable --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "enable", "--help"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + assert!(response.contains("q inline enable"), "Response should contain 'q inline enable'"); + + println!("βœ… q inline enable help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_status_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline status subcommand... | Description: Tests the q inline status subcommand for showing inline status"); + + println!("\nπŸ” Executing 'q inline status' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "status"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline status shows available customizations + assert!(response.contains("Inline is enabled"), "Response should contain 'Inline is enabled'"); + + println!("\nπŸ” Executing 'q setting all' subcommand to verify settings..."); + let response = q_chat_helper::execute_q_subcommand("q", &["setting", "all"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + if response.contains("inline.enabled") { + println!("βœ… Verified: inline_enabled is set to true"); + } else { + println!("❌ Verification failed: inline_enabled is not set to true"); + } + + let response = q_chat_helper::execute_q_subcommand("q", &["settings", "inline.enabled", "--delete"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + assert!(response.contains("Removing") || response.contains("inline.enabled"), "Response should confirm deletion or non-existence of the setting"); + + println!("βœ… q inline status subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_status_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline status --help subcommand... | Description: Tests the q inline status --help subcommand to show help for inline status"); + + println!("\nπŸ” Executing 'q inline status --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "status", "--help"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + assert!(response.contains("q inline status"), "Response should contain 'q inline status'"); + + println!("βœ… q inline status help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_show_customizations_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline show-customizations subcommand... | Description: Tests the q inline show-customizations that show the available customizations"); + + println!("\nπŸ” Executing 'q inline show-customizations' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "show-customizations"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline show-customizations shows available customizations + assert!(response.contains("Amazon-Internal-V1"), "Response should contain 'Amazon-Internal-V1'"); + assert!(response.contains("Amazon-Aladdin-V1"), "Response should contain 'Amazon-Aladdin-V1'"); + + println!("βœ… q inline show-customizations subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_show_customizations_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline show-customizations --help subcommand... | Description: Tests the q inline show-customizations --help to show help for showing customizations"); + + println!("\nπŸ” Executing 'q inline show-customizations --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "show-customizations", "--help"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline show-customizations --help shows available customizations + assert!(response.contains("q inline show-customizations"), "Response should contain 'q inline show-customizations'"); + + println!("βœ… q inline show-customizations --help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_set_customization_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline set-customization subcommand... | Description: Tests the q inline set-customization interactive menu for selecting customizations"); + + // Use helper function to select second option (Amazon-Internal-V1) + let response = q_chat_helper::execute_interactive_menu_selection("q", &["inline", "set-customization"], 1)?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Just verify that the command executed (may select first option by default) + assert!(response.contains("Customization") && response.contains("Amazon-Internal-V1") && response.contains("selected"), "Should show selection confirmation"); + + println!("βœ… q inline set-customization subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_unset_customization_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline unset customization... | Description: Tests the q inline set-customization interactive menu for selecting 'None' to unset customization"); + + // Use helper function to select "None" (4th option, so 3 down arrows) + let response = q_chat_helper::execute_interactive_menu_selection("q", &["inline", "set-customization"], 3)?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify that None was selected (customization unset) + assert!(response.contains("Customization") && response.contains("unset"), "Should show None selection or unset confirmation"); + + println!("βœ… q inline unset customization executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_inline_set_customization_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q inline set-customization --help subcommand... | Description: Tests the q inline set-customization --help to show help for setting customizations"); + + let response = q_chat_helper::execute_q_subcommand("q", &["inline", "set-customization", "--help"])?; + + println!("πŸ“ Debug response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Assert that q inline set-customization --help shows available customizations + assert!(response.contains("q inline set-customization"), "Response should contain 'set-customization'"); + + println!("βœ… q inline set-customization --help subcommand executed successfully!"); + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/q_subcommand/test_q_setting_subcommand.rs b/e2etests/tests/q_subcommand/test_q_setting_subcommand.rs new file mode 100644 index 0000000000..ccb6dff710 --- /dev/null +++ b/e2etests/tests/q_subcommand/test_q_setting_subcommand.rs @@ -0,0 +1,102 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +/// Tests the q settings --help subcommand +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_setting_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q settings --help subcommand... | Description: Tests the q settings --help subcommand to validate help output format and content."); + + println!("\nπŸ› οΈ Running 'q settings --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["settings", "--help"])?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Validate help output contains expected sections + assert!(response.contains("Usage:") && response.contains("q settings") && response.contains("[OPTIONS]") && response.contains("[KEY]") && response.contains("[VALUE]") && response.contains(""), + "Help should contain usage line"); + assert!(response.contains("Commands:"), + "Help should contain commands section"); + assert!(response.contains("open") && response.contains("all") && response.contains("help"), + "Help should contain all subcommands related to q setting subcommand"); + assert!(response.contains("Arguments:"), + "Help should contain Arguments section"); + assert!(response.contains("Options:"), + "Help should contain Options section"); + assert!(response.contains("-d, --delete"), + "Help should contain delete option"); + assert!(response.contains("-f, --format "), + "Help should contain format option"); + assert!(response.contains("-v, --verbose"), + "Help should contain verbose option"); + assert!(response.contains("-h, --help"), + "Should contain help option"); + println!("βœ… Help output validated successfully!"); + + Ok(()) +} + +/// Tests the q setting all subcommand +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_settings_all_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q settings all subcommand... | Description: Tests the q settings all subcommand to display all settings."); + + println!("\nπŸ› οΈ Running 'q settings all' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["settings", "all"])?; + + println!("πŸ“ All settings response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Validate output contains expected settings + assert!(response.contains("chat.defaultAgent"), "Should contain chat.defaultAgent setting"); + assert!(response.len() > 10, "Response should be substantial"); + + println!("βœ… All settings displayed successfully!"); + + Ok(()) +} + +/// Tests the q settings help subcommand +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_settings_help_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q settings help subcommand... | Description: Tests the q settings help subcommand to validate help output format and content."); + + println!("\nπŸ› οΈ Running 'q settings help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["settings", "help"])?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Validate help output contains expected sections + assert!(response.contains("Usage:") && response.contains("q settings") && response.contains("[OPTIONS]") && response.contains("[KEY]") && response.contains("[VALUE]") && response.contains(""), + "Help should contain usage line"); + assert!(response.contains("Commands:"), + "Help should contain commands section"); + assert!(response.contains("open") && response.contains("all") && response.contains("help"), + "Help should contain all subcommands related to q setting subcommand"); + assert!(response.contains("Arguments:"), + "Help should contain Arguments section"); + assert!(response.contains("Options:"), + "Help should contain Options section"); + assert!(response.contains("-d, --delete"), + "Help should contain delete option"); + assert!(response.contains("-f, --format "), + "Help should contain format option"); + assert!(response.contains("-v, --verbose"), + "Help should contain verbose option"); + assert!(response.contains("-h, --help"), + "Should contain help option"); + println!("βœ… Help output validated successfully!"); + + Ok(()) +} + diff --git a/e2etests/tests/q_subcommand/test_q_translate_subcommand.rs b/e2etests/tests/q_subcommand/test_q_translate_subcommand.rs new file mode 100644 index 0000000000..6c47803387 --- /dev/null +++ b/e2etests/tests/q_subcommand/test_q_translate_subcommand.rs @@ -0,0 +1,26 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_translate_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q translate subcommand... | Description: Tests the q translate subcommand for Natural Language to Shell translation"); + + println!("\nπŸ” Executing 'q translate' subcommand with input 'hello'..."); + + // Use stdin function for translate subcommand + let response = q_chat_helper::execute_q_subcommand_with_stdin("q", &["translate"], Some("hello"))?; + + println!("πŸ“ Translate response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify translation output contains shell subcommand + assert!(response.contains("echo") || response.contains("Shell"), "Missing shell subcommand in translation"); + println!("βœ… Found shell subcommand translation"); + + println!("βœ… Translate subcommand executed successfully!"); + + Ok(()) +} diff --git a/e2etests/tests/q_subcommand/test_q_whoami_subcommand.rs b/e2etests/tests/q_subcommand/test_q_whoami_subcommand.rs new file mode 100644 index 0000000000..ff4f4ffd6f --- /dev/null +++ b/e2etests/tests/q_subcommand/test_q_whoami_subcommand.rs @@ -0,0 +1,25 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; + +/// Tests the q whoami subcommand +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_whoami_subcommand() -> Result<(), Box> { + println!("\nπŸ” Testing q whoami subcommand... | Description: Tests the q whoami subcommand to display user profile information."); + + println!("\nπŸ› οΈ Running 'q whoami' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["whoami"])?; + + println!("πŸ“ Whoami response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Validate output contains expected authentication information + assert!(response.contains("Logged"), "Should contain IAM Identity Center login info"); + assert!(response.contains("Profile:"), "Should contain Profile section"); + + println!("βœ… Whoami information displayed successfully!"); + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/save_load/mod.rs b/e2etests/tests/save_load/mod.rs new file mode 100644 index 0000000000..e7a90d3b4b --- /dev/null +++ b/e2etests/tests/save_load/mod.rs @@ -0,0 +1 @@ +pub mod test_save_load_command; \ No newline at end of file diff --git a/e2etests/tests/save_load/test_save_load_command.rs b/e2etests/tests/save_load/test_save_load_command.rs new file mode 100644 index 0000000000..dcf1756b82 --- /dev/null +++ b/e2etests/tests/save_load/test_save_load_command.rs @@ -0,0 +1,528 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_save_command", + "test_save_command_argument_validation", + "test_save_help_command", + "test_save_h_flag_command", + "test_save_force_command", + "test_save_f_flag_command", + "test_load_help_command", + "test_load_h_flag_command", + "test_load_command", + "test_load_command_argument_validation" +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[allow(dead_code)] +struct FileCleanup<'a> { + path: &'a str, +} + +impl<'a> Drop for FileCleanup<'a> { + fn drop(&mut self) { + if std::path::Path::new(self.path).exists() { + let _ = std::fs::remove_file(self.path); + println!("βœ… Cleaned up test file"); + } + } +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_save_command() -> Result<(), Box> { + println!("\nπŸ” Testing /save command... | Description: Tests the /save command to export conversation state to a file and verify successful file creation with conversation data"); + + let save_path = "/tmp/qcli_test_save.json"; + let _cleanup = FileCleanup { path: save_path }; + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Create actual conversation content + let _help_response = chat.execute_command("/help")?; + let _tools_response = chat.execute_command("/tools")?; + println!("βœ… Created conversation content with /help and /tools commands"); + + // Execute /save command + let response = chat.execute_command(&format!("/save {}", save_path))?; + + println!("πŸ“ Save response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify "Exported conversation state to [file path]" message + assert!(response.contains("Exported") && response.contains(save_path), "Missing export confirmation message"); + println!("βœ… Found expected export message with file path"); + + // Verify file was created and contains expected data + assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); + println!("βœ… Save file created at {}", save_path); + + let file_content = std::fs::read_to_string(save_path)?; + assert!(file_content.contains("help") || file_content.contains("tools"), "File missing expected conversation data"); + println!("βœ… File contains expected conversation data"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_save_command_argument_validation() -> Result<(), Box> { + println!("\nπŸ” Testing /save command argument validation... | Description: Tests the /save command without required arguments to verify proper error handling and usage display"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/save")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify save error message + assert!(response.contains("error"), "Missing save error message"); + println!("βœ… Found save error message"); + + assert!(response.contains("Usage"), "Missing Usage section"); + assert!(response.contains("/save"), "Missing /save command in usage"); + println!("βœ… Found Usage section with /save command"); + + assert!(response.contains("Arguments"), "Missing Arguments section"); + assert!(response.contains(""), "Missing PATH argument"); + println!("βœ… Found Arguments section with PATH parameter"); + + println!("βœ… All help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_save_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /save --help command... | Description: Tests the /save --help command to display comprehensive help information for save functionality"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/save --help")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify save command help content + assert!(response.contains("Save"), "Missing save command description"); + println!("βœ… Found save command description"); + + assert!(response.contains("Usage"), "Missing Usage section"); + assert!(response.contains("/save"), "Missing /save command in usage"); + println!("βœ… Found Usage section with /save command"); + + assert!(response.contains("Arguments"), "Missing Arguments section"); + assert!(response.contains(""), "Missing PATH argument"); + println!("βœ… Found Arguments section with PATH parameter"); + + assert!(response.contains("Options"), "Missing Options section"); + println!("βœ… Found Options section"); + + println!("βœ… All help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_save_h_flag_command() -> Result<(), Box> { + println!("\nπŸ” Testing /save -h command... | Description: Tests the /save -h command (short form) to display save help information"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/save -h")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify save command help content + assert!(response.contains("Save"), "Missing save command description"); + println!("βœ… Found save command description"); + + assert!(response.contains("Usage"), "Missing Usage section"); + assert!(response.contains("/save"), "Missing /save command in usage"); + println!("βœ… Found Usage section with /save command"); + + assert!(response.contains("Arguments"), "Missing Arguments section"); + assert!(response.contains(""), "Missing PATH argument"); + println!("βœ… Found Arguments section with PATH parameter"); + + assert!(response.contains("Options"), "Missing Options section"); + println!("βœ… Found Options section"); + + println!("βœ… All help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_save_force_command() -> Result<(), Box> { + println!("\nπŸ” Testing /save --force command... | Description: Tests the /save --force command to overwrite existing files and verify force save functionality"); + + let save_path = "/tmp/qcli_test_save.json"; + let _cleanup = FileCleanup { path: save_path }; + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Create actual conversation content + let _help_response = chat.execute_command("/help")?; + let _tools_response = chat.execute_command("/tools")?; + println!("βœ… Created conversation content with /help and /tools commands"); + + // Execute /save command first + let response = chat.execute_command(&format!("/save {}", save_path))?; + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + assert!(response.contains("Exported"), "Initial save should succeed"); + println!("βœ… Initial save completed"); + + // Add more conversation content after initial save + let _prompt_response = chat.execute_command("/context show")?; + println!("βœ… Added more conversation content after initial save"); + + // Execute /save --force command to overwrite with new content + let force_response = chat.execute_command(&format!("/save --force {}", save_path))?; + + println!("πŸ“ Save force response: {} bytes", force_response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", force_response); + println!("πŸ“ END OUTPUT"); + + // Verify force save message + assert!(force_response.contains("Exported") && force_response.contains(save_path), "Missing export confirmation message"); + println!("βœ… Found expected export message with file path"); + + // Verify file exists and contains data + assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); + println!("βœ… Save file created at {}", save_path); + + let file_content = std::fs::read_to_string(save_path)?; + assert!(file_content.contains("help") || file_content.contains("tools"), "File missing initial conversation data"); + assert!(file_content.contains("context"), "File missing additional conversation data"); + println!("βœ… File contains expected conversation data including additional content"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_save_f_flag_command() -> Result<(), Box> { + println!("\nπŸ” Testing /save -f command... | Description: Tests the /save -f command (short form) to force overwrite existing files"); + + let save_path = "/tmp/qcli_test_save.json"; + let _cleanup = FileCleanup { path: save_path }; + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Create actual conversation content + let _help_response = chat.execute_command("/help")?; + let _tools_response = chat.execute_command("/tools")?; + println!("βœ… Created conversation content with /help and /tools commands"); + + // Execute /save command first + let response = chat.execute_command(&format!("/save {}", save_path))?; + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + assert!(response.contains("Exported"), "Initial save should succeed"); + println!("βœ… Initial save completed"); + + // Add more conversation content after initial save + let _prompt_response = chat.execute_command("/context show")?; + println!("βœ… Added more conversation content after initial save"); + + // Execute /save -f command to overwrite with new content + let force_response = chat.execute_command(&format!("/save -f {}", save_path))?; + + println!("πŸ“ Save force response: {} bytes", force_response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", force_response); + println!("πŸ“ END OUTPUT"); + + // Verify force save message + assert!(force_response.contains("Exported") && force_response.contains(save_path), "Missing export confirmation message"); + println!("βœ… Found expected export message with file path"); + + // Verify file exists and contains data + assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); + println!("βœ… Save file created at {}", save_path); + + let file_content = std::fs::read_to_string(save_path)?; + assert!(file_content.contains("help") || file_content.contains("tools"), "File missing initial conversation data"); + assert!(file_content.contains("context"), "File missing additional conversation data"); + println!("βœ… File contains expected conversation data including additional content"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_load_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /load --help command... | Description: Tests the /load --help command to display comprehensive help information for load functionality"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/load --help")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify load command help content + assert!(response.contains("Load"), "Missing load command description"); + println!("βœ… Found load command description"); + + assert!(response.contains("Usage"), "Missing Usage section"); + assert!(response.contains("/load"), "Missing /load command in usage"); + println!("βœ… Found Usage section with /load command"); + + assert!(response.contains("Arguments"), "Missing Arguments section"); + assert!(response.contains(""), "Missing PATH argument"); + println!("βœ… Found Arguments section with PATH parameter"); + + assert!(response.contains("Options"), "Missing Options section"); + println!("βœ… Found Options section"); + + println!("βœ… All help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_load_h_flag_command() -> Result<(), Box> { + println!("\nπŸ” Testing /load -h command... | Description: Tests the /load -h command (short form) to display load help information"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/load -h")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify load command help content + assert!(response.contains("Load"), "Missing load command description"); + println!("βœ… Found load command description"); + + assert!(response.contains("Usage"), "Missing Usage section"); + assert!(response.contains("/load"), "Missing /load command in usage"); + println!("βœ… Found Usage section with /load command"); + + assert!(response.contains("Arguments"), "Missing Arguments section"); + assert!(response.contains(""), "Missing PATH argument"); + println!("βœ… Found Arguments section with PATH parameter"); + + assert!(response.contains("Options"), "Missing Options section"); + println!("βœ… Found Options section"); + + println!("βœ… All help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_load_command() -> Result<(), Box> { + println!("\nπŸ” Testing /load command... | Description: Tests the /load command to import conversation state from a saved file and verify successful restoration"); + + let save_path = "/tmp/qcli_test_load.json"; + let _cleanup = FileCleanup { path: save_path }; + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + // Create actual conversation content + let _help_response = chat.execute_command("/help")?; + let _tools_response = chat.execute_command("/tools")?; + println!("βœ… Created conversation content with /help and /tools commands"); + + // Execute /save command to create a file to load + let save_response = chat.execute_command(&format!("/save {}", save_path))?; + + println!("πŸ“ Save response: {} bytes", save_response.len()); + println!("πŸ“ SAVE OUTPUT:"); + println!("{}", save_response); + println!("πŸ“ END SAVE OUTPUT"); + + // Verify save was successful + assert!(save_response.contains("Exported") && save_response.contains(save_path), "Missing export confirmation message"); + println!("βœ… Save completed successfully"); + + // Verify file was created + assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); + println!("βœ… Save file created at {}", save_path); + + // Execute /load command to load the saved conversation + let load_response = chat.execute_command(&format!("/load {}", save_path))?; + + println!("πŸ“ Load response: {} bytes", load_response.len()); + println!("πŸ“ LOAD OUTPUT:"); + println!("{}", load_response); + println!("πŸ“ END LOAD OUTPUT"); + + // Verify load was successful + assert!(!load_response.is_empty(), "Load command should return non-empty response"); + assert!(load_response.contains("Imported") && load_response.contains(save_path), "Missing import confirmation message"); + println!("βœ… Load command executed successfully and imported conversation state"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "save_load", feature = "sanity"))] +fn test_load_command_argument_validation() -> Result<(), Box> { + println!("\nπŸ” Testing /load command argument validation... | Description: Tests the /load command without required arguments to verify proper error handling and usage display"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/load")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify load error message + assert!(response.contains("error"), "Missing load error message"); + println!("βœ… Found load error message"); + + assert!(response.contains("Usage"), "Missing Usage section"); + assert!(response.contains("/load"), "Missing /load command in usage"); + println!("βœ… Found Usage section with /load command"); + + assert!(response.contains("Arguments"), "Missing Arguments section"); + assert!(response.contains(""), "Missing PATH argument"); + println!("βœ… Found Arguments section with PATH parameter"); + + assert!(response.contains("Options"), "Missing Options section"); + println!("βœ… Found Options section"); + + println!("βœ… All help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + diff --git a/e2etests/tests/session_mgmt/mod.rs b/e2etests/tests/session_mgmt/mod.rs new file mode 100644 index 0000000000..ee6705bcc0 --- /dev/null +++ b/e2etests/tests/session_mgmt/mod.rs @@ -0,0 +1,3 @@ +// Module declaration for session_mgmt tests +pub mod test_compact_command; +pub mod test_usage_command; \ No newline at end of file diff --git a/e2etests/tests/session_mgmt/test_compact_command.rs b/e2etests/tests/session_mgmt/test_compact_command.rs new file mode 100644 index 0000000000..140cb7b3b7 --- /dev/null +++ b/e2etests/tests/session_mgmt/test_compact_command.rs @@ -0,0 +1,580 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} + +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_compact_command", + "test_compact_help_command", + "test_compact_h_command", + "test_compact_truncate_true_command", + "test_compact_truncate_false_command", + "test_show_summary", + "test_max_message_truncate_true", + "test_max_message_truncate_false", + "test_max_message_length_invalid", + "test_compact_messages_to_exclude_command", + "test_compact_messages_to_exclude_show_sumary_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_compact_command() -> Result<(), Box> { + println!("\nπŸ” Testing /compact command... | Description: Tests the /compact command to compress conversation history and verify successful compaction or appropriate messaging for short conversations"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify compact response - either success or too short + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected compact response"); + } + + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_compact_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /compact --help command... | Description: Tests the /compact --help command to display comprehensive help information for conversation compaction functionality"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/compact --help")?; + + println!("πŸ“ Compact help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing usage format"); + println!("βœ… Found usage format"); + + // Verify Arguments section + assert!(response.contains("Arguments:"), "Missing Arguments section"); + println!("βœ… Found Arguments section"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + assert!(response.contains("--show-summary"), "Missing --show-summary option"); + assert!(response.contains("--messages-to-exclude"), "Missing --messages-to-exclude option"); + assert!(response.contains("--truncate-large-messages"), "Missing --truncate-large-messages option"); + assert!(response.contains("--max-message-length"), "Missing --max-message-length option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found all options and help flags"); + + println!("βœ… All compact help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_compact_h_command() -> Result<(), Box> { + println!("\nπŸ” Testing /compact -h command... | Description: Tests the /compact -h command (short form) to display compact help information"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/compact -h")?; + + println!("πŸ“ Compact help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing usage format"); + println!("βœ… Found usage format"); + + // Verify Arguments section + assert!(response.contains("Arguments:"), "Missing Arguments section"); + println!("βœ… Found Arguments section"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + assert!(response.contains("--show-summary"), "Missing --show-summary option"); + assert!(response.contains("--messages-to-exclude"), "Missing --messages-to-exclude option"); + assert!(response.contains("--truncate-large-messages"), "Missing --truncate-large-messages option"); + assert!(response.contains("--max-message-length"), "Missing --max-message-length option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("βœ… Found all options and help flags"); + + println!("βœ… All compact help content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_compact_truncate_true_command() -> Result<(), Box> { + println!("πŸ” Testing /compact --truncate-large-messages true command... | Description: Test that the /compact β€”truncate-large-messages true truncates large messages"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --truncate-large-messages true")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + if response.to_lowercase().contains("truncating") { + println!("βœ… Truncation of large messages verified!"); + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected message"); + } + + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_compact_truncate_false_command() -> Result<(), Box> { + println!("πŸ” Testing /compact --truncate-large-messages false command... | Description: Tests the /compact --truncate-large-messages false command to verify no message truncation occurs"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --truncate-large-messages false")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify compact response - either success or too short + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected compact response"); + } + + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_show_summary() -> Result<(), Box> { + println!("πŸ” Testing /compact --show-summary command... | Description: Tests the /compact --show-summary command to display conversation summary after compaction"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("What is DL?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --show-summary")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify compact response - either success or too short + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected compact response"); + } + + // Verify compact sumary response + assert!(response.to_lowercase().contains("conversation") && response.to_lowercase().contains("summary"), "Missing Summary section"); + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_max_message_truncate_true() -> Result<(), Box> { + println!("πŸ” Testing /compact --truncate-large-messages true --max-message-length command... | Description: Test /compact --truncate-large-messages true --max-message-length command compacts the conversation by summarizing it to free up context space, truncating large messages to a maximum of provided . "); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("What is DL?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --truncate-large-messages true --max-message-length 5")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify compact response - either success or too short + if response.to_lowercase().contains("truncating") { + println!("βœ… Truncation of large messages verified!"); + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected message"); + } + + // Verify compact sumary response + assert!(response.to_lowercase().contains("conversation") && response.to_lowercase().contains("summary"), "Missing Summary section"); + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_max_message_truncate_false() -> Result<(), Box> { + println!("πŸ” Testing /compact --truncate-large-messages false --max-message-length command... | Description: Test /compact --truncate-large-messages false --max-message-length command compacts the conversation by summarizing it to free up context space, but keeps large messages intact (no truncation) despite the max-message-length setting."); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("What is DL?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --truncate-large-messages false --max-message-length 5")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify compact response - either success or too short + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected compact response"); + } + + // Verify compact sumary response + assert!(response.to_lowercase().contains("conversation") && response.to_lowercase().contains("summary"), "Missing Summary section"); + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_max_message_length_invalid() -> Result<(), Box> { + println!("πŸ” Testing /compact --max-message-length command... | Description: Tests the /compact --max-message-length command with invalid subcommand to verify proper error handling and help display"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("What is DL?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --max-message-length 5")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify error message for missing required argument + assert!(response.contains("error"), "Missing error message"); + assert!(response.contains("--truncate-large-messages") && response.contains("") && response.contains("--max-message-length") && response.contains(""), "Missing required argument info"); + assert!(response.contains("Usage"), "Missing usage info"); + assert!(response.contains("--help"), "Missing help suggestion"); + println!("βœ… Found expected error message for missing --truncate-large-messages argument"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_compact_messages_to_exclude_command() -> Result<(), Box> { + println!("\nπŸ” Testing /compact command... | Description: Test /compact --messages-to-exclude command compacts the conversation by summarizing it to free up context space, excluding provided number of user-assistant message pair from the summarization process."); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("What is fibonacci?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --messages-to-exclude 1")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify compact response - either success or too short + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected compact response"); + } + + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "compact", feature = "sanity"))] +fn test_compact_messages_to_exclude_show_sumary_command() -> Result<(), Box> { + println!("\nπŸ” Testing /compact command... | Description: Test /compact --messages-to-exclude --show-summary command compacts the conversation by summarizing it to free up context space, excluding provided number of user-assistant message pair from the summarization process and prints the coversation summary."); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + chat.execute_command("/clear")?; + chat.execute_command("y")?; + let response = chat.execute_command("What is AWS?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("What is fibonacci?")?; + + println!("πŸ“ AI response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + let response = chat.execute_command("/compact --messages-to-exclude 1 --show-summary")?; + + println!("πŸ“ Compact response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify compact response - either success or too short + if response.contains("history") && response.contains("compacted") && response.contains("successfully") { + println!("βœ… Found compact success message"); + } else if response.contains("Conversation") && response.contains("short") { + println!("βœ… Found conversation too short message"); + } else { + panic!("Missing expected compact response"); + } + + // Verify compact sumary response + assert!(response.to_lowercase().contains("conversation") && response.to_lowercase().contains("summary"), "Missing Summary section"); + println!("βœ… All compact content verified!"); + + // Verify messages got excluded + assert!(!response.to_lowercase().contains("fibonacci"), "Fibonacci should not be present in compact response"); + println!("βœ… All compact content verified!"); + + println!("βœ… All compact content verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/session_mgmt/test_usage_command.rs b/e2etests/tests/session_mgmt/test_usage_command.rs new file mode 100644 index 0000000000..84a38594d2 --- /dev/null +++ b/e2etests/tests/session_mgmt/test_usage_command.rs @@ -0,0 +1,197 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} + +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_usage_command", + "test_usage_help_command", + "test_usage_h_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +/// Tests the /usage command to display current context window usage +/// Verifies token usage information, progress bar, breakdown sections, and Pro Tips +#[test] +#[cfg(all(feature = "usage", feature = "sanity"))] +fn test_usage_command() -> Result<(), Box> { + println!("\nπŸ” Testing /usage command... | Description: Tests the /usage command to display current context window usage. Verifies token usage information, progress bar, breakdown sections, and Pro Tips"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/usage")?; + + println!("πŸ“ Tools response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify context window information + assert!(response.contains("Current context window"), "Missing context window header"); + assert!(response.contains("tokens"), "Missing tokens used information"); + println!("βœ… Found context window and token usage information"); + + // Verify progress bar + assert!(response.contains("%"), "Missing percentage display"); + println!("βœ… Found progress bar with percentage"); + + // Verify token breakdown sections + assert!(response.contains(" Context files:"), "Missing Context files section"); + assert!(response.contains(" Tools:"), "Missing Tools section"); + assert!(response.contains(" Q responses:"), "Missing Q responses section"); + assert!(response.contains(" Your prompts:"), "Missing Your prompts section"); + println!("βœ… Found all token breakdown sections"); + + // Verify token counts and percentages format + assert!(response.contains("tokens ("), "Missing token count format"); + assert!(response.contains("%)"), "Missing percentage format in breakdown"); + println!("βœ… Verified token count and percentage format"); + + // Verify Pro Tips section + assert!(response.contains(" Pro Tips:"), "Missing Pro Tips section"); + println!("βœ… Found Pro Tips section"); + + // Verify specific tip commands + assert!(response.contains("/compact"), "Missing /compact command tip"); + assert!(response.contains("/clear"), "Missing /clear command tip"); + assert!(response.contains("/context show"), "Missing /context show command tip"); + println!("βœ… Found all command tips: /compact, /clear, /context show"); + + println!("βœ… All usage content verified!"); + + println!("βœ… Test completed successfully"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +/// Tests the /usage --help command to display help information for the usage command +/// Verifies Usage section, Options section, and help flags (-h, --help) +#[test] +#[cfg(all(feature = "usage", feature = "sanity"))] +fn test_usage_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /usage --help command... | Description: Tests the /usage --help command to display help information for the usage command. Verifies Usage section, Options section, and help flags (-h, --help)"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/usage --help")?; + + println!("πŸ“ Usage help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing Usage section"); + + assert!(response.contains("/usage"), "Missing /usage command in usage section"); + println!("βœ… Found Usage section with /usage command"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with description"); + + println!("βœ… All usage help content verified!"); + + println!("βœ… Test completed successfully"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +/// Tests the /usage -h command (short form of --help) +/// Verifies Usage section, Options section, and help flags (-h, --help) +#[test] +#[cfg(all(feature = "usage", feature = "sanity"))] +fn test_usage_h_command() -> Result<(), Box> { + println!("\nπŸ” Testing /usage -h command... | Description: Tests the /usage -h command (short form of --help). Verifies Usage section, Options section, and help flags (-h, --help)"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + let response = chat.execute_command("/usage -h")?; + + println!("πŸ“ Usage help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + + // Verify Usage section + assert!(response.contains("Usage:"), "Missing Usage section"); + assert!(response.contains("/usage"), "Missing /usage command in usage section"); + println!("βœ… Found Usage section with /usage command"); + + // Verify Options section + assert!(response.contains("Options:"), "Missing Options section"); + println!("βœ… Found Options section"); + + // Verify help flags + assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); + println!("βœ… Found help flags: -h, --help with description"); + + println!("βœ… All usage help content verified!"); + + println!("βœ… Test completed successfully"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file diff --git a/e2etests/tests/test_ai_prompt.rs b/e2etests/tests/test_ai_prompt.rs deleted file mode 100644 index 58d092f753..0000000000 --- a/e2etests/tests/test_ai_prompt.rs +++ /dev/null @@ -1,91 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "ai_prompts")] -fn test_what_is_aws_prompt() -> Result<(), Box> { - println!("πŸ” [AI PROMPTS] Testing 'What is AWS?' AI prompt..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.send_prompt("What is AWS?")?; - - println!("πŸ“ AI response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Check if we got an actual AI response - if response.contains("Amazon Web Services") || - response.contains("cloud") || - response.contains("AWS") || - response.len() > 100 { - println!("βœ… Got substantial AI response ({} bytes)!", response.len()); - - // Additional checks for quality response - if response.contains("Amazon Web Services") { - println!("βœ… Response correctly identifies 'Amazon Web Services'"); - } - if response.contains("cloud") { - println!("βœ… Response mentions cloud computing concepts"); - } - if response.contains("AWS") { - println!("βœ… Response uses AWS acronym appropriately"); - } - - // Check for technical depth - let technical_terms = ["service", "platform", "infrastructure", "compute", "storage"]; - let found_terms: Vec<&str> = technical_terms.iter() - .filter(|&&term| response.to_lowercase().contains(term)) - .copied() - .collect(); - if !found_terms.is_empty() { - println!("βœ… Response includes technical terms: {:?}", found_terms); - } - } else { - println!("⚠️ Response seems limited or just echoed input"); - println!("⚠️ Expected AWS explanation but got: {} bytes", response.len()); - } - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} - -#[test] -#[cfg(feature = "ai_prompts")] -fn test_simple_greeting() -> Result<(), Box> { - println!("πŸ” Testing simple 'Hello' prompt..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.send_prompt("Hello")?; - - println!("πŸ“ Greeting response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Check if we got any response - if response.trim().is_empty() { - println!("⚠️ No response to greeting - AI may not be responding"); - } else if response.to_lowercase().contains("hello") || - response.to_lowercase().contains("hi") || - response.to_lowercase().contains("greet") { - println!("βœ… Got appropriate greeting response!"); - println!("βœ… AI recognized and responded to greeting appropriately"); - } else if response.len() > 20 { - println!("βœ… Got substantial response ({} bytes) to greeting", response.len()); - println!("ℹ️ Response doesn't contain typical greeting words but seems AI-generated"); - } else { - println!("ℹ️ Got minimal response - unclear if AI-generated or echo"); - println!("ℹ️ Response length: {} bytes", response.len()); - } - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_clear_command.rs b/e2etests/tests/test_clear_command.rs deleted file mode 100644 index 7eb8d203e5..0000000000 --- a/e2etests/tests/test_clear_command.rs +++ /dev/null @@ -1,40 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "core_session")] -fn test_clear_command() -> Result<(), Box> { - println!("πŸ” Testing /clear command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - // Send initial message - println!("πŸ” Sending prompt: 'My name is TestUser'"); - let _initial_response = chat.send_prompt("My name is TestUser")?; - println!("πŸ“ Initial response: {} bytes", _initial_response.len()); - println!("πŸ“ INITIAL RESPONSE OUTPUT:"); - println!("{}", _initial_response); - println!("πŸ“ END INITIAL RESPONSE"); - - // Execute clear command - println!("πŸ” Executing command: '/clear'"); - let _clear_response = chat.execute_command("/clear")?; - - println!("βœ… Clear command executed"); - - // Check if AI remembers previous conversation - println!("πŸ” Sending prompt: 'What is my name?'"); - let test_response = chat.send_prompt("What is my name?")?; - println!("πŸ“ Test response: {} bytes", test_response.len()); - println!("πŸ“ TEST RESPONSE OUTPUT:"); - println!("{}", test_response); - println!("πŸ“ END TEST RESPONSE"); - - // Verify history is cleared - AI shouldn't remember the name - assert!(!test_response.to_lowercase().contains("testuser"), "Clear command failed - AI still remembers previous conversation"); - println!("βœ… Clear command successful - Conversation history cleared."); - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} \ No newline at end of file diff --git a/e2etests/tests/test_compact_command.rs b/e2etests/tests/test_compact_command.rs deleted file mode 100644 index 4c7e101738..0000000000 --- a/e2etests/tests/test_compact_command.rs +++ /dev/null @@ -1,115 +0,0 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); - -// List of covered tests -const TEST_NAMES: &[&str] = &[ - "test_compact_command", - "test_compact_help_command", -]; -const TOTAL_TESTS: usize = TEST_NAMES.len(); - -#[test] -#[cfg(feature = "compact")] -fn test_compact_command() -> Result<(), Box> { - println!("πŸ” Testing /compact command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/compact")?; - - println!("πŸ“ Compact response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify compact response - either success or too short - if response.contains("history") && response.contains("compacted") && response.contains("successfully") { - println!("βœ… Found compact success message"); - } else if response.contains("Conversation") && response.contains("short") { - println!("βœ… Found conversation too short message"); - } else { - panic!("Missing expected compact response"); - } - - println!("βœ… All compact content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "compact")] -fn test_compact_help_command() -> Result<(), Box> { - println!("πŸ” Testing /compact --help command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/compact --help")?; - - println!("πŸ“ Compact help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - /* Verify main description - assert!(response.contains("/compact") && response.contains("summarizes") && response.contains("history"), "Missing compact description"); - println!("βœ… Found compact description");*/ - - // Verify When to use section - assert!(response.contains("When to use"), "Missing When to use section"); - /*assert!(response.contains("memory constraint"), "Missing memory constraint warning"); - assert!(response.contains("conversation") && response.contains("running") && response.contains("long time"), "Missing long conversation note"); - assert!(response.contains("new topic") && response.contains("same session"), "Missing new topic note"); - assert!(response.contains("complex tool operations"), "Missing tool operations note");*/ - println!("βœ… Found When to use section with all scenarios"); - - // Verify How it works section - assert!(response.contains("How it works"), "Missing How it works section"); - /*assert!(response.contains("AI-generated summary"), "Missing AI summary description"); - assert!(response.contains("key information") && response.contains("code") && response.contains("tool executions"), "Missing key elements"); - assert!(response.contains("free up space"), "Missing free up space description"); - assert!(response.contains("reference the summary context"), "Missing summary context reference");*/ - println!("βœ… Found How it works section with all details"); - - // Verify auto-compaction information - //assert!(response.contains("Compaction will be automatically performed whenever the context window overflows"), "Missing auto-compaction note"); - //assert!(response.contains("To disable this behavior, run: `q settings chat.disableAutoCompaction true`"), "Missing disable instruction"); - assert!(response.contains("run: `q settings chat.disableAutoCompaction true`"), "Missing disable instruction"); - println!("βœ… Found auto-compaction information"); - - // Verify Usage section - assert!(response.contains("Usage:"), "Missing usage format"); - println!("βœ… Found usage format"); - - // Verify Arguments section - assert!(response.contains("Arguments:"), "Missing Arguments section"); - println!("βœ… Found Arguments section"); - - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("--show-summary"), "Missing --show-summary option"); - assert!(response.contains("--messages-to-exclude"), "Missing --messages-to-exclude option"); - assert!(response.contains("--truncate-large-messages"), "Missing --truncate-large-messages option"); - assert!(response.contains("--max-message-length"), "Missing --max-message-length option"); - assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); - println!("βœ… Found all options and help flags"); - - println!("βœ… All compact help content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} \ No newline at end of file diff --git a/e2etests/tests/test_editor_help_command.rs b/e2etests/tests/test_editor_help_command.rs deleted file mode 100644 index a1e76560a8..0000000000 --- a/e2etests/tests/test_editor_help_command.rs +++ /dev/null @@ -1,101 +0,0 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); - -// List of covered tests -const TEST_NAMES: &[&str] = &[ - "test_editor_help_command", - "test_help_editor_command", -]; -const TOTAL_TESTS: usize = TEST_NAMES.len(); - -#[test] -#[cfg(feature = "editor")] -fn test_editor_help_command() -> Result<(), Box> { - println!("πŸ” Testing /editor --help command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/editor --help")?; - - println!("πŸ“ Editor help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - /* Verify description - assert!(response.contains("Open $EDITOR"), "Missing editor description"); - println!("βœ… Found editor description");*/ - - // Verify Usage section - assert!(response.contains("Usage:") && response.contains("/editor") && response.contains("[INITIAL_TEXT]"), "Missing Usage section"); - println!("βœ… Found Usage section with /editor command"); - - // Verify Arguments section - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains("[INITIAL_TEXT]"), "Missing INITIAL_TEXT argument"); - println!("βœ… Found Arguments section"); - - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - println!("βœ… Found Options section"); - - // Verify help flags - assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); - println!("βœ… Found help flags: -h, --help with Print help description"); - - println!("βœ… All editor help content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "editor")] -fn test_help_editor_command() -> Result<(), Box> { - println!("πŸ” Testing /help editor command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/help editor")?; - - println!("πŸ“ Help editor response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify Usage section - assert!(response.contains("Usage:") && response.contains("/editor") && response.contains("[INITIAL_TEXT]"), "Missing Usage section"); - println!("βœ… Found Usage section with /editor command"); - - // Verify Arguments section - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains("[INITIAL_TEXT]"), "Missing INITIAL_TEXT argument"); - println!("βœ… Found Arguments section"); - - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - println!("βœ… Found Options section"); - - // Verify help flags - assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); - println!("βœ… Found help flags: -h, --help with Print help description"); - - println!("βœ… All editor help content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} \ No newline at end of file diff --git a/e2etests/tests/test_help_command.rs b/e2etests/tests/test_help_command.rs deleted file mode 100644 index 39a3b932e2..0000000000 --- a/e2etests/tests/test_help_command.rs +++ /dev/null @@ -1,54 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "core_session")] -fn test_help_command() -> Result<(), Box> { - println!("πŸ” [CORE SESSION] Testing /help command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.execute_command("/help")?; - - println!("πŸ“ Help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify help content - assert!(response.contains("Commands:"), "Missing Commands section"); - println!("βœ… Found Commands section with all available commands"); - - assert!(response.contains("quit"), "Missing quit command"); - assert!(response.contains("clear"), "Missing clear command"); - assert!(response.contains("tools"), "Missing tools command"); - assert!(response.contains("help"), "Missing help command"); - println!("βœ… Verified core commands: quit, clear, tools, help"); - - assert!(response.contains("Options:"), "Missing Options section"); - println!("βœ… Found Options section with -h, --help flags"); - - assert!(response.contains("MCP:"), "Missing MCP section"); - println!("βœ… Found MCP section with configuration documentation link"); - - assert!(response.contains("Tips:"), "Missing Tips section"); - println!("βœ… Found Tips section with keyboard shortcuts and settings"); - - // Verify specific useful commands - if response.contains("context") { - println!("βœ… Found context management command"); - } - if response.contains("agent") { - println!("βœ… Found agent management command"); - } - if response.contains("model") { - println!("βœ… Found model selection command"); - } - - println!("βœ… All help content verified!"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_hooks_command.rs b/e2etests/tests/test_hooks_command.rs deleted file mode 100644 index 274de06487..0000000000 --- a/e2etests/tests/test_hooks_command.rs +++ /dev/null @@ -1,101 +0,0 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); - -// List of covered tests -const TEST_NAMES: &[&str] = &[ - "test_hooks_command", - "test_hooks_help_command", -]; -const TOTAL_TESTS: usize = TEST_NAMES.len(); - -#[test] -#[cfg(feature = "hooks")] -fn test_hooks_command() -> Result<(), Box> { - println!("πŸ” Testing /hooks command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/hooks")?; - - println!("πŸ“ Hooks command response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify no hooks configured message - assert!(response.contains("No hooks"), "Missing no hooks configured message"); - println!("βœ… Found no hooks configured message"); - - // Verify documentation reference - assert!(response.contains("documentation"), "Missing documentation reference"); - assert!(response.contains("https://github.com/aws/amazon-q-developer-cli/blob/main/docs/agent-format.md#hooks-field"), "Missing documentation URL"); - println!("βœ… Found documentation reference and URL"); - - println!("βœ… All hooks command functionality verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "hooks")] -fn test_hooks_help_command() -> Result<(), Box> { - println!("πŸ” Testing /hooks --help command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/hooks --help")?; - - println!("πŸ“ Hooks help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - /* Verify context hooks description - assert!(response.contains("context hooks"), "Missing context hooks"); - println!("βœ… Found context hooks description");*/ - - // Verify documentation reference - assert!(response.contains("documentation"), "Missing documentation reference"); - assert!(response.contains("https://github.com/aws/amazon-q-developer-cli/blob/main/docs/agent-format.md#hooks-field"), "Missing documentation URL"); - println!("βœ… Found documentation reference and URL"); - - // Verify Notes section - assert!(response.contains("Notes:"), "Missing Notes section"); - /*assert!(response.contains("executed in parallel"), "Missing parallel execution note"); - assert!(response.contains("conversation_start"), "Missing conversation_start hook type"); - assert!(response.contains("per_prompt"), "Missing per_prompt hook type");*/ - println!("βœ… Found Notes section with hook types and execution details"); - - // Verify Usage section - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/hooks"), "Missing /hooks command in usage section"); - println!("βœ… Found Usage section with /hooks command"); - - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - println!("βœ… Found Options section"); - - // Verify help flags - assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); - println!("βœ… Found help flags: -h, --help with Print help description"); - - println!("βœ… All hooks help content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} \ No newline at end of file diff --git a/e2etests/tests/test_issue_command.rs b/e2etests/tests/test_issue_command.rs deleted file mode 100644 index 7821ac99c2..0000000000 --- a/e2etests/tests/test_issue_command.rs +++ /dev/null @@ -1,119 +0,0 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); - -// List of covered tests -const TEST_NAMES: &[&str] = &[ - "test_issue_command", - "test_issue_force_command", - "test_issue_help_command", -]; -const TOTAL_TESTS: usize = TEST_NAMES.len(); - -#[test] -#[cfg(feature = "issue_reporting")] -fn test_issue_command() -> Result<(), Box> { - println!("πŸ” Testing /issue command with bug report..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/issue \"Bug: Q CLI crashes when using large files\"")?; - - println!("πŸ“ Issue command response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify command executed successfully (GitHub opens automatically) - assert!(response.contains("Heading over to GitHub..."), "Missing browser opening confirmation"); - println!("βœ… Found browser opening confirmation"); - - println!("βœ… All issue command functionality verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "issue_reporting")] -fn test_issue_force_command() -> Result<(), Box> { - println!("πŸ” Testing /issue --force command with critical bug..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/issue --force \"Critical bug in file handling\"")?; - - println!("πŸ“ Issue force command response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify command executed successfully (GitHub opens automatically) - assert!(response.contains("Heading over to GitHub..."), "Missing browser opening confirmation"); - println!("βœ… Found browser opening confirmation"); - - println!("βœ… All issue --force command functionality verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "issue_reporting")] -fn test_issue_help_command() -> Result<(), Box> { - println!("πŸ” Testing /issue --help command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/issue --help")?; - - println!("πŸ“ Issue help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - /* Verify description - assert!(response.contains("issue") && response.contains("feature request"), "Missing issue description"); - println!("βœ… Found issue description");*/ - - // Verify Usage section - //assert!(response.contains("Usage: /issue [OPTIONS] [DESCRIPTION]..."), "Missing usage format"); - assert!(response.contains("Usage:") && response.contains("/issue") && response.contains("[DESCRIPTION]") && response.contains("[OPTIONS]"), "Missing Usage section"); - println!("βœ… Found usage format"); - - // Verify Arguments section - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains("[DESCRIPTION]"), "Missing DESCRIPTION argument"); - println!("βœ… Found Arguments section"); - - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-f") && response.contains("--force"), "Missing force option"); - assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); - println!("βœ… Found Options section with force and help flags"); - - println!("βœ… All issue help content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - diff --git a/e2etests/tests/test_load_command.rs b/e2etests/tests/test_load_command.rs deleted file mode 100644 index 873640252f..0000000000 --- a/e2etests/tests/test_load_command.rs +++ /dev/null @@ -1,65 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -struct FileCleanup<'a> { - path: &'a str, -} - -impl<'a> Drop for FileCleanup<'a> { - fn drop(&mut self) { - if std::path::Path::new(self.path).exists() { - let _ = std::fs::remove_file(self.path); - println!("βœ… Cleaned up test file"); - } - } -} - -#[test] -#[cfg(feature = "save_load")] -fn test_load_command() -> Result<(), Box> { - println!("πŸ” Testing /load command..."); - - let save_path = "/tmp/qcli_test_load.json"; - let _cleanup = FileCleanup { path: save_path }; - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - // Create actual conversation content - let _help_response = chat.execute_command("/help")?; - let _tools_response = chat.execute_command("/tools")?; - println!("βœ… Created conversation content with /help and /tools commands"); - - // Execute /save command to create a file to load - let save_response = chat.execute_command(&format!("/save {}", save_path))?; - - println!("πŸ“ Save response: {} bytes", save_response.len()); - println!("πŸ“ SAVE OUTPUT:"); - println!("{}", save_response); - println!("πŸ“ END SAVE OUTPUT"); - - // Verify save was successful - assert!(save_response.contains("Exported conversation state to") && save_response.contains(save_path), "Missing export confirmation message"); - println!("βœ… Save completed successfully"); - - // Verify file was created - assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); - println!("βœ… Save file created at {}", save_path); - - // Execute /load command to load the saved conversation - let load_response = chat.execute_command(&format!("/load {}", save_path))?; - - println!("πŸ“ Load response: {} bytes", load_response.len()); - println!("πŸ“ LOAD OUTPUT:"); - println!("{}", load_response); - println!("πŸ“ END LOAD OUTPUT"); - - // Verify load was successful - assert!(!load_response.is_empty(), "Load command should return non-empty response"); - assert!(load_response.contains("Imported conversation state from") && load_response.contains(save_path), "Missing import confirmation message"); - println!("βœ… Load command executed successfully and imported conversation state"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_load_command_argument_validation.rs b/e2etests/tests/test_load_command_argument_validation.rs deleted file mode 100644 index 50475fef5d..0000000000 --- a/e2etests/tests/test_load_command_argument_validation.rs +++ /dev/null @@ -1,44 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "save_load")] -fn test_load_command_argument_validation() -> Result<(), Box> { - println!("πŸ” Testing /load command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.execute_command("/load")?; - - println!("πŸ“ Help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify load error message - assert!(response.contains("error:") && response.contains("the following required arguments were not provided:"), "Missing load error message"); - println!("βœ… Found load error message"); - - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/load"), "Missing /load command in usage"); - println!("βœ… Found Usage section with /load command"); - - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains(""), "Missing PATH argument"); - println!("βœ… Found Arguments section with PATH parameter"); - - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help") || response.contains("β€”help"), "Missing --help flag"); - println!("βœ… Found Options section with -h, --help flags"); - - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found help flag description"); - - println!("βœ… All help content verified!"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_load_help_command.rs b/e2etests/tests/test_load_help_command.rs deleted file mode 100644 index 3591df228e..0000000000 --- a/e2etests/tests/test_load_help_command.rs +++ /dev/null @@ -1,87 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "save_load")] -fn test_load_help_command() -> Result<(), Box> { - println!("πŸ” Testing /load --help command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.execute_command("/load --help")?; - - println!("πŸ“ Help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify load command help content - assert!(response.contains("Load a previous conversation"), "Missing load command description"); - println!("βœ… Found load command description"); - - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/load"), "Missing /load command in usage"); - println!("βœ… Found Usage section with /load command"); - - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains(""), "Missing PATH argument"); - println!("βœ… Found Arguments section with PATH parameter"); - - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help"), "Missing --help flag"); - println!("βœ… Found Options section with -h, --help flags"); - - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found help flag description"); - - println!("βœ… All help content verified!"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} - -#[test] -#[cfg(feature = "save_load")] -fn test_load_h_flag_command() -> Result<(), Box> { - println!("πŸ” Testing /load -h command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.execute_command("/load -h")?; - - println!("πŸ“ Help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify load command help content - assert!(response.contains("Load a previous conversation"), "Missing load command description"); - println!("βœ… Found load command description"); - - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/load"), "Missing /load command in usage"); - println!("βœ… Found Usage section with /load command"); - - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains(""), "Missing PATH argument"); - println!("βœ… Found Arguments section with PATH parameter"); - - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help"), "Missing --help flag"); - println!("βœ… Found Options section with -h, --help flags"); - - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found help flag description"); - - println!("βœ… All help content verified!"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_quit_command.rs b/e2etests/tests/test_quit_command.rs deleted file mode 100644 index c05b95d075..0000000000 --- a/e2etests/tests/test_quit_command.rs +++ /dev/null @@ -1,16 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "core_session")] -fn test_quit_command() -> Result<(), Box> { - println!("πŸ” Testing /quit command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - chat.quit()?; - println!("βœ… /quit command executed successfully"); - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_save_command.rs b/e2etests/tests/test_save_command.rs deleted file mode 100644 index 9a247e6cc2..0000000000 --- a/e2etests/tests/test_save_command.rs +++ /dev/null @@ -1,56 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -struct FileCleanup<'a> { - path: &'a str, -} - -impl<'a> Drop for FileCleanup<'a> { - fn drop(&mut self) { - if std::path::Path::new(self.path).exists() { - let _ = std::fs::remove_file(self.path); - println!("βœ… Cleaned up test file"); - } - } -} - -#[test] -#[cfg(feature = "save_load")] -fn test_save_command() -> Result<(), Box> { - println!("πŸ” Testing /save command..."); - - let save_path = "/tmp/qcli_test_save.json"; - let _cleanup = FileCleanup { path: save_path }; - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - // Create actual conversation content - let _help_response = chat.execute_command("/help")?; - let _tools_response = chat.execute_command("/tools")?; - println!("βœ… Created conversation content with /help and /tools commands"); - - // Execute /save command - let response = chat.execute_command(&format!("/save {}", save_path))?; - - println!("πŸ“ Save response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify "Exported conversation state to [file path]" message - assert!(response.contains("Exported conversation state to") && response.contains(save_path), "Missing export confirmation message"); - println!("βœ… Found expected export message with file path"); - - // Verify file was created and contains expected data - assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); - println!("βœ… Save file created at {}", save_path); - - let file_content = std::fs::read_to_string(save_path)?; - assert!(file_content.contains("help") || file_content.contains("tools"), "File missing expected conversation data"); - println!("βœ… File contains expected conversation data"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_save_command_argument_validation.rs b/e2etests/tests/test_save_command_argument_validation.rs deleted file mode 100644 index 3cd037947b..0000000000 --- a/e2etests/tests/test_save_command_argument_validation.rs +++ /dev/null @@ -1,46 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "save_load")] -fn test_save_command_argument_validation() -> Result<(), Box> { - println!("πŸ” Testing /save command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.execute_command("/save")?; - - println!("πŸ“ Help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify save error message - assert!(response.contains("error:") && response.contains("the following required arguments were not provided:"), "Missing save error message"); - println!("βœ… Found save error message"); - - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/save"), "Missing /save command in usage"); - println!("βœ… Found Usage section with /save command"); - - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains(""), "Missing PATH argument"); - println!("βœ… Found Arguments section with PATH parameter"); - - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-f"), "Missing -f flag"); - assert!(response.contains("--force"), "Missing --force flag"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help") || response.contains("β€”help"), "Missing --help flag"); - println!("βœ… Found Options section with -f, --force, -h, --help flags"); - - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found help flag description"); - - println!("βœ… All help content verified!"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_save_force_command.rs b/e2etests/tests/test_save_force_command.rs deleted file mode 100644 index b8b6939240..0000000000 --- a/e2etests/tests/test_save_force_command.rs +++ /dev/null @@ -1,124 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -struct FileCleanup<'a> { - path: &'a str, -} - -impl<'a> Drop for FileCleanup<'a> { - fn drop(&mut self) { - if std::path::Path::new(self.path).exists() { - let _ = std::fs::remove_file(self.path); - println!("βœ… Cleaned up test file"); - } - } -} - -#[test] -#[cfg(feature = "save_load")] -fn test_save_force_command() -> Result<(), Box> { - println!("πŸ” Testing /save --force command..."); - - let save_path = "/tmp/qcli_test_save.json"; - let _cleanup = FileCleanup { path: save_path }; - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - // Create actual conversation content - let _help_response = chat.execute_command("/help")?; - let _tools_response = chat.execute_command("/tools")?; - println!("βœ… Created conversation content with /help and /tools commands"); - - // Execute /save command first - let response = chat.execute_command(&format!("/save {}", save_path))?; - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - assert!(response.contains("Exported conversation state to"), "Initial save should succeed"); - println!("βœ… Initial save completed"); - - // Add more conversation content after initial save - let _prompt_response = chat.execute_command("/context show")?; - println!("βœ… Added more conversation content after initial save"); - - // Execute /save --force command to overwrite with new content - let force_response = chat.execute_command(&format!("/save --force {}", save_path))?; - - println!("πŸ“ Save force response: {} bytes", force_response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", force_response); - println!("πŸ“ END OUTPUT"); - - // Verify force save message - assert!(force_response.contains("Exported conversation state to") && force_response.contains(save_path), "Missing export confirmation message"); - println!("βœ… Found expected export message with file path"); - - // Verify file exists and contains data - assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); - println!("βœ… Save file created at {}", save_path); - - let file_content = std::fs::read_to_string(save_path)?; - assert!(file_content.contains("help") || file_content.contains("tools"), "File missing initial conversation data"); - assert!(file_content.contains("context"), "File missing additional conversation data"); - println!("βœ… File contains expected conversation data including additional content"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} - -#[test] -#[cfg(feature = "save_load")] -fn test_save_f_flag_command() -> Result<(), Box> { - println!("πŸ” Testing /save -f command..."); - - let save_path = "/tmp/qcli_test_save.json"; - let _cleanup = FileCleanup { path: save_path }; - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - // Create actual conversation content - let _help_response = chat.execute_command("/help")?; - let _tools_response = chat.execute_command("/tools")?; - println!("βœ… Created conversation content with /help and /tools commands"); - - // Execute /save command first - let response = chat.execute_command(&format!("/save {}", save_path))?; - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - assert!(response.contains("Exported conversation state to"), "Initial save should succeed"); - println!("βœ… Initial save completed"); - - // Add more conversation content after initial save - let _prompt_response = chat.execute_command("/context show")?; - println!("βœ… Added more conversation content after initial save"); - - // Execute /save -f command to overwrite with new content - let force_response = chat.execute_command(&format!("/save -f {}", save_path))?; - - println!("πŸ“ Save force response: {} bytes", force_response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", force_response); - println!("πŸ“ END OUTPUT"); - - // Verify force save message - assert!(force_response.contains("Exported conversation state to") && force_response.contains(save_path), "Missing export confirmation message"); - println!("βœ… Found expected export message with file path"); - - // Verify file exists and contains data - assert!(std::path::Path::new(save_path).exists(), "Save file was not created"); - println!("βœ… Save file created at {}", save_path); - - let file_content = std::fs::read_to_string(save_path)?; - assert!(file_content.contains("help") || file_content.contains("tools"), "File missing initial conversation data"); - assert!(file_content.contains("context"), "File missing additional conversation data"); - println!("βœ… File contains expected conversation data including additional content"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_save_help_command.rs b/e2etests/tests/test_save_help_command.rs deleted file mode 100644 index 03cd658039..0000000000 --- a/e2etests/tests/test_save_help_command.rs +++ /dev/null @@ -1,91 +0,0 @@ -use q_cli_e2e_tests::q_chat_helper::QChatSession; - -#[test] -#[cfg(feature = "save_load")] -fn test_save_help_command() -> Result<(), Box> { - println!("πŸ” Testing /save --help command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.execute_command("/save --help")?; - - println!("πŸ“ Help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify save command help content - assert!(response.contains("Save the current conversation"), "Missing save command description"); - println!("βœ… Found save command description"); - - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/save"), "Missing /save command in usage"); - println!("βœ… Found Usage section with /save command"); - - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains(""), "Missing PATH argument"); - println!("βœ… Found Arguments section with PATH parameter"); - - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-f"), "Missing -f flag"); - assert!(response.contains("--force"), "Missing --force flag"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help") || response.contains("β€”help"), "Missing --help flag"); - println!("βœ… Found Options section with -f, --force, -h, --help flags"); - - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found help flag description"); - - println!("βœ… All help content verified!"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} - -#[test] -#[cfg(feature = "save_load")] -fn test_save_h_flag_command() -> Result<(), Box> { - println!("πŸ” Testing /save -h command..."); - - let mut chat = QChatSession::new()?; - println!("βœ… Q Chat session started"); - - let response = chat.execute_command("/save -h")?; - - println!("πŸ“ Help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify save command help content - assert!(response.contains("Save the current conversation"), "Missing save command description"); - println!("βœ… Found save command description"); - - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/save"), "Missing /save command in usage"); - println!("βœ… Found Usage section with /save command"); - - assert!(response.contains("Arguments:"), "Missing Arguments section"); - assert!(response.contains(""), "Missing PATH argument"); - println!("βœ… Found Arguments section with PATH parameter"); - - assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-f"), "Missing -f flag"); - assert!(response.contains("--force"), "Missing --force flag"); - assert!(response.contains("-h"), "Missing -h flag"); - assert!(response.contains("--help") || response.contains("β€”help"), "Missing --help flag"); - println!("βœ… Found Options section with -f, --force, -h, --help flags"); - - assert!(response.contains("Print help"), "Missing help description"); - println!("βœ… Found help flag description"); - - println!("βœ… All help content verified!"); - - chat.quit()?; - println!("βœ… Test completed successfully"); - - Ok(()) -} diff --git a/e2etests/tests/test_subscribe_command.rs b/e2etests/tests/test_subscribe_command.rs deleted file mode 100644 index 5b79658c7b..0000000000 --- a/e2etests/tests/test_subscribe_command.rs +++ /dev/null @@ -1,89 +0,0 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); - -// List of covered tests -const TEST_NAMES: &[&str] = &[ - "test_subscribe_command", - "test_subscribe_help_command", -]; -const TOTAL_TESTS: usize = TEST_NAMES.len(); - - -#[test] -#[cfg(feature = "subscribe")] -fn test_subscribe_command() -> Result<(), Box> { - println!("πŸ” Testing /subscribe command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/subscribe")?; - - println!("πŸ“ Subscribe response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify subscription management message - assert!(response.contains("Q Developer Pro subscription") && response.contains("IAM Identity Center"), "Missing subscription management message"); - println!("βœ… Found subscription management message"); - - println!("βœ… All subscribe content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} -#[test] -#[cfg(feature = "subscribe")] -fn test_subscribe_help_command() -> Result<(), Box> { - println!("πŸ” Testing /subscribe --help command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/subscribe --help")?; - - println!("πŸ“ Subscribe help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify description - assert!(response.contains("Q Developer Pro subscription"), "Missing subscription description"); - println!("βœ… Found subscription description"); - - // Verify Usage section - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/subscribe"), "Missing /subscribe command in usage section"); - assert!(response.contains("[OPTIONS]"), "Missing [OPTIONS] in usage section"); - println!("βœ… Found Usage section with /subscribe [OPTIONS]"); - - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - println!("βœ… Found Options section"); - - // Verify manage option - assert!(response.contains("--manage"), "Missing --manage option"); - println!("βœ… Found --manage option"); - - // Verify help flags - assert!(response.contains("-h") && response.contains("--help") , "Missing -h, --help flags"); - println!("βœ… Found help flags: -h, --help with Print help description"); - - println!("βœ… All subscribe help content verified!"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} diff --git a/e2etests/tests/test_usage_command.rs b/e2etests/tests/test_usage_command.rs deleted file mode 100644 index 977afb870b..0000000000 --- a/e2etests/tests/test_usage_command.rs +++ /dev/null @@ -1,115 +0,0 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); - -// List of covered tests -const TEST_NAMES: &[&str] = &[ - "test_usage_command", - "test_usage_help_command", -]; -const TOTAL_TESTS: usize = TEST_NAMES.len(); - -#[test] -#[cfg(feature = "usage")] -fn test_usage_command() -> Result<(), Box> { - println!("πŸ” Testing /usage command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/usage")?; - - println!("πŸ“ Tools response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - // Verify context window information - assert!(response.contains("Current context window"), "Missing context window header"); - assert!(response.contains("tokens"), "Missing tokens used information"); - println!("βœ… Found context window and token usage information"); - - // Verify progress bar - assert!(response.contains("%"), "Missing percentage display"); - println!("βœ… Found progress bar with percentage"); - - // Verify token breakdown sections - assert!(response.contains(" Context files:"), "Missing Context files section"); - assert!(response.contains(" Tools:"), "Missing Tools section"); - assert!(response.contains(" Q responses:"), "Missing Q responses section"); - assert!(response.contains(" Your prompts:"), "Missing Your prompts section"); - println!("βœ… Found all token breakdown sections"); - - // Verify token counts and percentages format - assert!(response.contains("tokens ("), "Missing token count format"); - assert!(response.contains("%)"), "Missing percentage format in breakdown"); - println!("βœ… Verified token count and percentage format"); - - // Verify Pro Tips section - assert!(response.contains(" Pro Tips:"), "Missing Pro Tips section"); - println!("βœ… Found Pro Tips section"); - - // Verify specific tip commands - assert!(response.contains("/compact"), "Missing /compact command tip"); - assert!(response.contains("/clear"), "Missing /clear command tip"); - assert!(response.contains("/context show"), "Missing /context show command tip"); - println!("βœ… Found all command tips: /compact, /clear, /context show"); - - println!("βœ… All usage content verified!"); - - println!("βœ… Test completed successfully"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} - -#[test] -#[cfg(feature = "usage")] -fn test_usage_help_command() -> Result<(), Box> { - println!("πŸ” Testing /usage --help command..."); - - let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - - let response = chat.execute_command("/usage --help")?; - - println!("πŸ“ Usage help response: {} bytes", response.len()); - println!("πŸ“ FULL OUTPUT:"); - println!("{}", response); - println!("πŸ“ END OUTPUT"); - - /* Verify description - assert!(response.contains("context window ") && response.contains("usage"), "Missing usage command description"); - println!("βœ… Found usage command description");*/ - - // Verify Usage section - assert!(response.contains("Usage:"), "Missing Usage section"); - assert!(response.contains("/usage"), "Missing /usage command in usage section"); - println!("βœ… Found Usage section with /usage command"); - - // Verify Options section - assert!(response.contains("Options:"), "Missing Options section"); - println!("βœ… Found Options section"); - - // Verify help flags - assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing -h, --help flags"); - println!("βœ… Found help flags: -h, --help with description"); - - println!("βœ… All usage help content verified!"); - - println!("βœ… Test completed successfully"); - - // Release the lock before cleanup - drop(chat); - - // Cleanup only if this is the last test - cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; - - Ok(()) -} \ No newline at end of file diff --git a/e2etests/tests/todos/mod.rs b/e2etests/tests/todos/mod.rs new file mode 100644 index 0000000000..2b2628ac75 --- /dev/null +++ b/e2etests/tests/todos/mod.rs @@ -0,0 +1 @@ +pub mod test_todos_command; \ No newline at end of file diff --git a/e2etests/tests/todos/test_todos_command.rs b/e2etests/tests/todos/test_todos_command.rs new file mode 100644 index 0000000000..34d8908859 --- /dev/null +++ b/e2etests/tests/todos/test_todos_command.rs @@ -0,0 +1,195 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} + +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] +static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); + +// List of covered tests +#[allow(dead_code)] +const TEST_NAMES: &[&str] = &[ + "test_todos_command", + "test_todos_help_command", + "test_todos_view_command" +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "todos", feature = "sanity"))] +fn test_todos_command() -> Result<(), Box> { + println!("\nπŸ” Testing /todos command... | Description: Tests the /todos command to view, manage, and resume to-do lists"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + println!("βœ… Q Chat session started"); + + let response = chat.execute_command("/todos")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify help content + assert!(response.contains("Commands:"), "Missing Commands section"); + println!("βœ… Found Commands section with all available commands"); + + assert!(response.contains("resume"), "Missing resume command"); + assert!(response.contains("view"), "Missing view command"); + assert!(response.contains("delete"), "Missing delete command"); + assert!(response.contains("help"), "Missing help command"); + println!("βœ… Found core commands: resume, view, delete, help"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "todos", feature = "sanity"))] +fn test_todos_help_command() -> Result<(), Box> { + println!("\nπŸ” Testing /todos help command... | Description: Tests the /todos help command to display help information about the todos "); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + println!("βœ… Q Chat session started"); + + let response = chat.execute_command("/todos help")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify help content + assert!(response.contains("Commands:"), "Missing Commands section"); + println!("βœ… Found Commands section with all available commands"); + + assert!(response.contains("resume"), "Missing resume command"); + assert!(response.contains("view"), "Missing view command"); + assert!(response.contains("delete"), "Missing delete command"); + assert!(response.contains("help"), "Missing help command"); + println!("βœ… Found core commands: resume, view, delete, help"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "todos", feature = "sanity"))] +fn test_todos_view_command() -> Result<(), Box> { + println!("\nπŸ” Testing /todos view command... | Description: Tests the /todos view command to view to-do lists"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + + println!("Executing 'q settings chat.enableTodoList true' to enable todos feature..."); + q_chat_helper::execute_q_subcommand("q", &["settings", "chat.enableTodoList", "true"])?; + + let response = q_chat_helper::execute_q_subcommand("q", &["settings", "all"])?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + assert!(response.contains("chat.enableTodoList = true"), "Failed to enable todos feature"); + println!("βœ… Todos feature enabled"); + + println!("βœ… Q Chat session started"); + + let response = chat.execute_command("Add task in todos list: I have to update the timecard ")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify help content + assert!(response.contains("Using tool"), "Missing tool usage confirmation"); + assert!(response.contains("todo_list"), "Missing todo_list tool usage"); + assert!(response.contains("timecard"), "Missing timecard message"); + println!("βœ… Confirmed todo_list tool usage"); + + let response = chat.execute_command("/todos view")?; + + println!("πŸ“ Help response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + assert!(response.contains("to-do"), "Missing to-do message"); + assert!(response.contains("view"), "Missing view message"); + println!("βœ… Confirmed to-do item presence in view output"); + + // Send down arrow to select different model + let selection_response = chat.send_key_input("\x1b[B")?; + + println!("πŸ“ Selection response: {} bytes", selection_response.len()); + println!("πŸ“ SELECTION RESPONSE:"); + println!("{}", selection_response); + println!("πŸ“ END SELECTION RESPONSE"); + + // Send Enter to confirm + let confirm_response = chat.send_key_input("\r")?; + + println!("πŸ“ Confirm response: {} bytes", confirm_response.len()); + println!("πŸ“ CONFIRM RESPONSE:"); + println!("{}", confirm_response); + println!("πŸ“ END CONFIRM RESPONSE"); + + assert!(confirm_response.contains("Viewing"), "Missing viewing list confirmation"); + assert!(confirm_response.contains("timecard"), "Missing timecard to-do item"); + println!("βœ… Confirmed viewing of selected to-do list with items"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} diff --git a/e2etests/tests/tools/mod.rs b/e2etests/tests/tools/mod.rs new file mode 100644 index 0000000000..bed96d0878 --- /dev/null +++ b/e2etests/tests/tools/mod.rs @@ -0,0 +1 @@ +pub mod test_tools_command; \ No newline at end of file diff --git a/e2etests/tests/test_tools_command.rs b/e2etests/tests/tools/test_tools_command.rs similarity index 56% rename from e2etests/tests/test_tools_command.rs rename to e2etests/tests/tools/test_tools_command.rs index 5f4b2ae2be..39281bc3f2 100644 --- a/e2etests/tests/test_tools_command.rs +++ b/e2etests/tests/tools/test_tools_command.rs @@ -1,9 +1,43 @@ -use q_cli_e2e_tests::{get_chat_session, cleanup_if_last_test}; -use std::sync::atomic::{AtomicUsize, Ordering}; +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +#[allow(dead_code)] +static INIT: Once = Once::new(); +#[allow(dead_code)] +static mut CHAT_SESSION: Option> = None; + +#[allow(dead_code)] +pub fn get_chat_session() -> &'static Mutex { + unsafe { + INIT.call_once(|| { + let chat = q_chat_helper::QChatSession::new().expect("Failed to create chat session"); + println!("βœ… Q Chat session started"); + CHAT_SESSION = Some(Mutex::new(chat)); + }); + (&raw const CHAT_SESSION).as_ref().unwrap().as_ref().unwrap() + } +} +#[allow(dead_code)] +pub fn cleanup_if_last_test(test_count: &AtomicUsize, total_tests: usize) -> Result> { + let count = test_count.fetch_add(1, Ordering::SeqCst) + 1; + if count == total_tests { + unsafe { + if let Some(session) = (&raw const CHAT_SESSION).as_ref().unwrap() { + if let Ok(mut chat) = session.lock() { + chat.quit()?; + println!("βœ… Test completed successfully"); + } + } + } + } + Ok(count) +} +#[allow(dead_code)] static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); // List of covered tests +#[allow(dead_code)] const TEST_NAMES: &[&str] = &[ "test_tools_command", "test_tools_help_command", @@ -14,13 +48,33 @@ const TEST_NAMES: &[&str] = &[ "test_tools_trust_help_command", "test_tools_untrust_help_command", "test_tools_schema_help_command", + "test_fs_write_and_fs_read_tools", + "test_execute_bash_tool", + "test_report_issue_tool", + "test_use_aws_tool", + "test_trust_execute_bash_for_direct_execution" ]; +#[allow(dead_code)] const TOTAL_TESTS: usize = TEST_NAMES.len(); +#[allow(dead_code)] +struct FileCleanup<'a> { + path: &'a str, +} + +impl<'a> Drop for FileCleanup<'a> { + fn drop(&mut self) { + if std::path::Path::new(self.path).exists() { + let _ = std::fs::remove_file(self.path); + println!("βœ… Cleaned up test file"); + } + } +} + #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_command() -> Result<(), Box> { - println!("πŸ” Testing /tools command..."); + println!("\nπŸ” Testing /tools command... | Description: Tests the /tools command to display all available tools with their permission status including built-in and MCP tools"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -73,9 +127,9 @@ fn test_tools_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_help_command() -> Result<(), Box> { - println!("πŸ” Testing /tools --help command..."); + println!("\nπŸ” Testing /tools --help command... | Description: Tests the /tools --help command to display comprehensive help information about tools management including available subcommands and options"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -87,15 +141,6 @@ fn test_tools_help_command() -> Result<(), Box> { println!("{}", response); println!("πŸ“ END OUTPUT"); - /* Verify description - assert!(response.contains("permission"), "Missing permission description"); - println!("βœ… Found tools permission description");*/ - - // Verify documentation reference - //assert!(response.contains("documentation"), "Missing documentation reference"); - assert!(response.contains("https://github.com/aws/amazon-q-developer-cli/blob/main/docs/agent-format.md#tools-field"), "Missing documentation URL"); - println!("βœ… Found documentation reference and URL"); - // Verify Usage section assert!(response.contains("Usage:") && response.contains("/tools") && response.contains("[COMMAND]"), "Missing Usage section"); println!("βœ… Found usage format"); @@ -128,9 +173,9 @@ fn test_tools_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_trust_all_command() -> Result<(), Box> { - println!("πŸ” Testing /tools trust-all command..."); + println!("\nπŸ” Testing /tools trust-all command... | Description: Tests the /tools trust-all command to trust all available tools and verify all tools show trusted status, then tests reset functionality"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -143,11 +188,9 @@ fn test_tools_trust_all_command() -> Result<(), Box> { println!("{}", trust_all_response); println!("πŸ“ END TRUST-ALL OUTPUT"); - /* Verify trust-all confirmation message - assert!(trust_all_response.contains("confirmation"), "Missing trust-all confirmation message"); - assert!(trust_all_response.contains("risk"), "Missing risk warning message");*/ - assert!(trust_all_response.contains("https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-chat-security.html#command-line-chat-trustall-safety"), "Missing documentation link"); - println!("βœ… Found documentation link"); + // Verify that all tools now show "trusted" permission + assert!(trust_all_response.contains("All tools") && trust_all_response.contains("trusted"), "Missing trusted tools after trust-all"); + println!("βœ… trust-all confirmation message!!"); // Now check tools list to verify all tools are trusted let tools_response = chat.execute_command("/tools")?; @@ -210,9 +253,9 @@ fn test_tools_trust_all_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_trust_all_help_command() -> Result<(), Box> { - println!("πŸ” Testing /tools trust-all --help command..."); + println!("\nπŸ” Testing /tools trust-all --help command... | Description: Tests the /tools trust-all --helpcommand to display help information for the trust-all subcommand"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -224,9 +267,6 @@ fn test_tools_trust_all_help_command() -> Result<(), Box> println!("{}", response); println!("πŸ“ END OUTPUT"); - /* Verify command description - assert!(response.contains("Trust"), "Missing command description"); - println!("βœ… Found command description");*/ // Verify usage format assert!(response.contains("Usage:") && response.contains("/tools trust-all"), "Missing usage format"); @@ -234,7 +274,7 @@ fn test_tools_trust_all_help_command() -> Result<(), Box> // Verify options section assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing help option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing help option"); println!("βœ… Found options section with help flag"); println!("βœ… All tools trust-all help functionality verified!"); @@ -249,9 +289,9 @@ fn test_tools_trust_all_help_command() -> Result<(), Box> } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_reset_help_command() -> Result<(), Box> { - println!("πŸ” Testing /tools reset --help command..."); + println!("\nπŸ” Testing /tools reset --help command... | Description: Tests the /tools reset --help command to display help information for the reset subcommand"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -263,17 +303,13 @@ fn test_tools_reset_help_command() -> Result<(), Box> { println!("{}", response); println!("πŸ“ END OUTPUT"); - /* Verify command description - assert!(response.contains("Reset"), "Missing command description"); - println!("βœ… Found command description");*/ - // Verify usage format assert!(response.contains("Usage:") && response.contains("/tools reset"), "Missing usage format"); println!("βœ… Found usage format"); // Verify options section assert!(response.contains("Options:"), "Missing Options section"); - assert!(response.contains("-h") && response.contains("--help") && response.contains("Print help"), "Missing help option"); + assert!(response.contains("-h") && response.contains("--help"), "Missing help option"); println!("βœ… Found options section with help flag"); println!("βœ… All tools reset help functionality verified!"); @@ -288,9 +324,9 @@ fn test_tools_reset_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_trust_command() -> Result<(), Box> { - println!("πŸ” Testing /tools trust command..."); + println!("\nπŸ” Testing /tools trust command... | Description: Tests the /tools trust and untrust commands to manage individual tool permissions and verify trust status changes"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -366,9 +402,9 @@ fn test_tools_trust_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_trust_help_command() -> Result<(), Box> { - println!("πŸ” Testing /tools trust --help command..."); + println!("\nπŸ” Testing /tools trust --help command... | Description: Tests the /tools trust --help command to display help information for trusting specific tools"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -380,10 +416,6 @@ fn test_tools_trust_help_command() -> Result<(), Box> { println!("{}", response); println!("πŸ“ END OUTPUT"); - /* Verify command description - assert!(response.contains("Trust"), "Missing command description"); - println!("βœ… Found command description");*/ - // Verify usage format assert!(response.contains("Usage:") && response.contains("/tools trust") && response.contains(""), "Missing usage format"); println!("βœ… Found usage format"); @@ -409,9 +441,9 @@ fn test_tools_trust_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_untrust_help_command() -> Result<(), Box> { - println!("πŸ” Testing /tools untrust --help command..."); + println!("\nπŸ” Testing /tools untrust --help command... | Description: Tests the /tools untrust --help command to display help information for untrusting specific tools"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -423,10 +455,6 @@ fn test_tools_untrust_help_command() -> Result<(), Box> { println!("{}", response); println!("πŸ“ END OUTPUT"); - /* Verify command description - assert!(response.contains("Revert"), "Missing command description"); - println!("βœ… Found command description");*/ - // Verify usage format assert!(response.contains("Usage:") && response.contains("/tools untrust") && response.contains(""), "Missing usage format"); println!("βœ… Found usage format"); @@ -452,9 +480,9 @@ fn test_tools_untrust_help_command() -> Result<(), Box> { } #[test] -#[cfg(feature = "tools")] +#[cfg(all(feature = "tools", feature = "sanity"))] fn test_tools_schema_help_command() -> Result<(), Box> { - println!("πŸ” Testing /tools schema --help command..."); + println!("\nπŸ” Testing /tools schema --help command... | Description: Tests the /tools schema --help command to display help information for viewing tool schemas"); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -465,11 +493,7 @@ fn test_tools_schema_help_command() -> Result<(), Box> { println!("πŸ“ FULL OUTPUT:"); println!("{}", response); println!("πŸ“ END OUTPUT"); - - /* Verify command description - assert!(response.contains("Show the input schema for all available tools"), "Missing command description"); - println!("βœ… Found command description");*/ - + // Verify usage format assert!(response.contains("Usage:") && response.contains("/tools schema"), "Missing usage format"); println!("βœ… Found usage format"); @@ -489,11 +513,11 @@ fn test_tools_schema_help_command() -> Result<(), Box> { Ok(()) } - +//TODO: As response not giving full content , need to check this. /*#[test] #[cfg(feature = "tools")] fn test_tools_schema_command() -> Result<(), Box> { - println!("πŸ” Testing /tools schema command..."); + println!("\nπŸ” Testing /tools schema command..."); let session = get_chat_session(); let mut chat = session.lock().unwrap(); @@ -546,3 +570,237 @@ fn test_tools_schema_command() -> Result<(), Box> { Ok(()) }*/ + +#[test] +#[cfg(all(feature = "tools", feature = "sanity"))] +fn test_fs_write_and_fs_read_tools() -> Result<(), Box> { + println!("\nπŸ” Testing `fs_write` and `fs_read` tool ... | Description: Tests the fs_write and fs_read tools by creating a file with specific content and reading it back to verify file I/O operations work correctly"); + + let save_path = "demo.txt"; + let _cleanup = FileCleanup { path: save_path }; + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + // Test fs_write tool by asking to create a file with "Hello World" content + let response = chat.execute_command(&format!("Create a file at {} with content 'Hello World'", save_path))?; + + println!("πŸ“ fs_write response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify tool usage indication + assert!(response.contains("Using tool") && response.contains("fs_write"), "Missing fs_write tool usage indication"); + println!("βœ… Found fs_write tool usage indication"); + + // Verify file path in response + assert!(response.contains("demo.txt"), "Missing expected file path"); + println!("βœ… Found expected file path in response"); + + // Allow the tool execution + let allow_response = chat.execute_command("y")?; + + println!("πŸ“ Allow response: {} bytes", allow_response.len()); + println!("πŸ“ ALLOW RESPONSE:"); + println!("{}", allow_response); + println!("πŸ“ END ALLOW RESPONSE"); + + // Verify content reference + assert!(allow_response.contains("Hello World"), "Missing expected content reference"); + println!("βœ… Found expected content reference"); + + // Verify success indication + assert!(allow_response.contains("Created"), "Missing success indication"); + println!("βœ… Found success indication"); + + // Test fs_read tool by asking to read the created file + let response = chat.execute_command(&format!("Read file {}'", save_path))?; + + println!("πŸ“ fs_read response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify tool usage indication + assert!(response.contains("Using tool") && response.contains("fs_read"), "Missing fs_read tool usage indication"); + println!("βœ… Found fs_read tool usage indication"); + + // Verify file path in response + assert!(response.contains("demo.txt"), "Missing expected file path"); + println!("βœ… Found expected file path in response"); + + // Verify content reference + assert!(allow_response.contains("Hello World"), "Missing expected content reference"); + println!("βœ… Found expected content reference"); + + println!("βœ… All fs_write and fs_read tool functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "tools", feature = "sanity"))] +fn test_execute_bash_tool() -> Result<(), Box> { + println!("\nπŸ” Testing `execute_bash` tool ... | Description: Tests the execute_bash tool by running the 'pwd' command and verifying proper command execution and output"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + // Test execute_bash tool by asking to run pwd command + let response = chat.execute_command("Run pwd")?; + + println!("πŸ“ execute_bash response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify tool usage indication + assert!(response.contains("Using tool") && response.contains("execute_bash"), "Missing execute_bash tool usage indication"); + println!("βœ… Found execute_bash tool usage indication"); + + // Verify command in response + assert!(response.contains("pwd"), "Missing expected command"); + println!("βœ… Found pwd command in response"); + + // Verify success indication + assert!(response.contains("current working directory"), "Missing success indication"); + println!("βœ… Found success indication"); + + println!("βœ… All execute_bash functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "tools", feature = "sanity"))] +fn test_report_issue_tool() -> Result<(), Box> { + println!("\nπŸ” Testing `report_issue` tool ... | Description: Tests the report_issue reporting functionality by creating a sample issue and verifying the browser opens GitHub for issue submission"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + // Test report_issue tool by asking to report an issue + let response = chat.execute_command("Report an issue: 'File creation not working properly'")?; + + println!("πŸ“ report_issue response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify tool usage indication + assert!(response.contains("Using tool") && response.contains("gh_issue"), "Missing report_issue tool usage indication"); + println!("βœ… Found report_issue tool usage indication"); + + // Verify command executed successfully (GitHub opens automatically) + assert!(response.contains("Heading over to GitHub..."), "Missing browser opening confirmation"); + println!("βœ… Found browser opening confirmation"); + + println!("βœ… All report_issue functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "tools", feature = "sanity"))] +fn test_use_aws_tool() -> Result<(), Box> { + println!("\nπŸ” Testing `use_aws` tool ... | Description: Tests the use_aws tool by executing AWS commands to describe EC2 instances and verifying proper AWS CLI integration"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + // Test use_aws tool by asking to describe EC2 instances in us-west-2 + let response = chat.execute_command("Describe EC2 instances in us-west-2")?; + + println!("πŸ“ use_aws response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify tool usage indication + assert!(response.contains("Using tool") && response.contains("use_aws"), "Missing use_aws tool usage indication"); + println!("βœ… Found use_aws tool usage indication"); + + // Verify command executed successfully. + assert!(response.contains("aws"), "Missing aws information"); + println!("βœ… Found aws information"); + + println!("βœ… All use_aws functionality verified!"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} + +#[test] +#[cfg(all(feature = "tools", feature = "sanity"))] +fn test_trust_execute_bash_for_direct_execution() -> Result<(), Box> { + println!("\nπŸ” Testing Trust execute_bash for direct execution ... | Description: Tests the ability to trust the execute_bash tool so it runs commands without asking for user confirmation each time"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + // First, trust the execute_bash tool + let trust_response = chat.execute_command("/tools trust execute_bash")?; + + println!("πŸ“ Trust response: {} bytes", trust_response.len()); + println!("πŸ“ TRUST OUTPUT:"); + println!("{}", trust_response); + println!("πŸ“ END TRUST OUTPUT"); + + // Verify trust confirmation + assert!(trust_response.contains("trusted") || trust_response.contains("execute_bash"), "Missing trust confirmation"); + println!("βœ… Found trust confirmation"); + + // Now test execute_bash tool with a simple command that should run directly without confirmation + let response = chat.execute_command("Run mkdir -p test_dir && echo 'test' > test_dir/test.txt")?; + + println!("πŸ“ execute_bash response: {} bytes", response.len()); + println!("πŸ“ FULL OUTPUT:"); + println!("{}", response); + println!("πŸ“ END OUTPUT"); + + // Verify tool usage indication + assert!(response.contains("Using tool") && response.contains("execute_bash"), "Missing execute_bash tool usage indication"); + println!("βœ… Found execute_bash tool usage indication"); + + // Verify the command was executed directly without asking for confirmation + assert!(response.contains("Created") && response.contains("directory") && response.contains("test_dir") , "Missing success message"); + println!("βœ… Found success message"); + + println!("βœ… All trusted execute_bash functionality verified!"); + + chat.execute_command("Delete the directory test_dir/test.txt")?; + + println!("βœ… Directory successfully deleted"); + + // Release the lock before cleanup + drop(chat); + + // Cleanup only if this is the last test + cleanup_if_last_test(&TEST_COUNT, TOTAL_TESTS)?; + + Ok(()) +} \ No newline at end of file