diff --git a/e2etests/Cargo.toml b/e2etests/Cargo.toml index d446e62d5f..3ab4239a45 100644 --- a/e2etests/Cargo.toml +++ b/e2etests/Cargo.toml @@ -7,12 +7,14 @@ edition = "2021" [dependencies] expectrl = "0.7" +regex = "1.0" [features] -core_session = ["help", "quit", "clear"] # Core Session Commands (/help, /quit, /clear) +core_session = ["help", "quit", "clear", "changelog"] # Core Session Commands (/help, /quit, /clear, /changelog) help = [] # Help Command (/help) quit = [] # Quit Command (/quit) clear = [] # Clear Command (/clear) +changelog = [] # Changelog Command (/changelog) tools = [] # Tools Command (/tools) agent = [] # Agent Commands (/agent list, /agent create, etc.) @@ -36,6 +38,7 @@ ai_prompts = [] # AI Prompts ("What is AWS?", "Hello") q_subcommand = [] # Q SubCommand (q chat, q doctor, q translate) todos = [] # todos command +experiment=[] # experiment command regression = [] # Regression Tests sanity = [] # Sanity Tests - Quick smoke tests for basic functionality diff --git a/e2etests/README.md b/e2etests/README.md index 7420336c98..a6e6cdd3ad 100644 --- a/e2etests/README.md +++ b/e2etests/README.md @@ -15,130 +15,246 @@ This test framework provides comprehensive end-to-end testing capabilities for A ## ๐ŸŽฏ Categorized Test Framework ### **Feature-Based Organization** -Tests are organized into 9 functional categories using Rust features: +Tests are organized into 12 functional categories using Rust features: -1. **Core Session Commands** (4 tests) - `core_session` - - `/help`, `/tools`, `/quit`, `/clear` - -2. **Agent Commands** (8 tests) - `agent` +1. **Agent Commands** (8 tests) - `agent` - `/agent list`, `/agent create`, `/agent help`, etc. -3. **Context Commands** (5 tests) - `context` +2. **AI Prompts** (5 tests) - `ai_prompts` + - "What is AWS?", "Hello" prompts + +3. **Context Commands** (10 tests) - `context` - `/context show`, `/context add`, `/context help`, etc. -4. **Save/Load Commands** (4 tests) - `save_load` - - `/save`, `/load`, help commands +4. **Core Session Commands** (3 tests) - `core_session` + - `/help`, `/quit`, `/clear` + +5. **Integration Commands** (21 tests) - `integration` + - `/subscribe`, `/hooks`, `/editor` help commands + +6. **MCP Commands** (18 tests) - `mcp` + - `/mcp`, `/mcp --help` -5. **Model Commands** (2 tests) - `model` +7. **Model Commands** (3 tests) - `model` - `/model`, `/model --help` -6. **Session Management Commands** (4 tests) - `session_mgmt` - - `/compact`, `/usage`, help commands +8. **Q Subcommands** (15 tests) - `q_subcommand` + - q chat, q debug, q doctor, etc. -7. **Integration Commands** (4 tests) - `integration` - - `/subscribe`, `/hooks`, `/editor` help commands +9. **Save/Load Commands** (10 tests) - `save_load` + - `/save`, `/load`, help commands -8. **MCP Commands** (2 tests) - `mcp` - - `/mcp`, `/mcp --help` +10. **Session Management Commands** (14 tests) - `session_mgmt` + - `/compact`, `/usage`, help commands -9. **AI Prompts** (2 tests) - `ai_prompts` - - "What is AWS?", "Hello" prompts +11. **Todos Commands** - `todos` + - todos command + +12. **Tools Commands** (15 tests) - `tools` + - `/tools`, tool management commands ## ๐Ÿ“ Core Files -### **`tests/q_chat_helper.rs`** +### **`src/lib.rs`** Base helper class providing: -- `QChatSession::new()` - Start Q Chat session -- `execute_command(cmd)` - Execute commands using expectrl + carriage return (`0x0D`) -- `send_prompt(prompt)` - Send AI prompts using direct process streams +- `QChatSession::new()` - Start Q Chat session with timeout configuration +- `execute_command(cmd)` - Execute commands using expectrl + carriage return (`0x0D`) with character-by-character typing +- `send_prompt(prompt)` - Send AI prompts using direct process streams with concurrent stdout/stderr reading +- `send_key_input(key)` - Send key inputs for interactive navigation +- `execute_q_subcommand()` - Execute Q CLI subcommands directly in terminal +- `execute_q_subcommand_with_stdin()` - Execute Q CLI subcommands with stdin input support +- `execute_interactive_menu_selection()` - Handle interactive menu navigation with arrow keys and selection +- `execute_interactive_menu_selection_with_command()` - Execute interactive menu with full command string +- `read_response()` - Internal method for reading session responses with timeout handling - `quit()` - Clean session termination -### **`run_simple_categorized.sh`** -Advanced categorized test runner with: +### **`tests/all_tests.rs`** +Main test entry point that includes all test modules and organizes them by feature categories. + +### **`tests/*/mod.rs`** +Module files in test subdirectories that group related test functions by feature area. + +### **`run_tests.py`** +Python test runner with: - **Real-time per-test feedback** - Shows โœ…/โŒ as each test completes - **Category organization** - Groups tests by functional area - **Configurable categories** - Enable/disable categories for faster iteration - **Quiet and verbose modes** - Control output detail level - **Final summary reporting** - Shows passed/failed categories +- **HTML and JSON reports** - Generates detailed test reports in both formats +- **Run complete test suites** - Execute all available test categories +- **Run individual features** - Target specific test categories +- **Check list of available features** - Display all available test categories +- **Convert JSON report to HTML** - Transform JSON reports into HTML format +- **Custom binary support** - Specify custom Q CLI binary path, defaults to system-installed `q` if not provided + +**Example Commands:** +```bash + +# Run individual features +python run_tests.py --features agent,context --quiet + +# Run default sanity test suite +python run_tests.py --quiet + +# List available features +python run_tests.py --list-features + +# Convert JSON to HTML report +python run_tests.py --json-to-html reports/test_report.json +``` ## ๐Ÿš€ Usage -### **Categorized Test Runner (Recommended)** +### **Python Test Runner (Recommended)** ```bash # Run all categories with real-time feedback -./run_simple_categorized.sh +python run_tests.py --quiet -# Quiet mode - faster, less verbose -./run_simple_categorized.sh --quiet +# Check help section for all available options +python run_tests.py --help -# With custom Q CLI binary -./run_simple_categorized.sh /path/to/custom/q --quiet ``` **Example Output:** ``` -๐Ÿงช Core Session Commands ---------------------- -๐Ÿ“‹ Tests in this category: - โ€ข test_help_command - โ€ข test_tools_command - โ€ข test_quit_command - โ€ข test_clear_command - -๐Ÿ”„ Running tests... - โœ… test_help_command - โœ… test_tools_command - โœ… test_quit_command - โœ… test_clear_command -โœ… Core Session Commands completed successfully +๐Ÿงช Running Sanity Test Suite +======================================== +๐Ÿ”„ Running: usage with sanity +โœ… usage (sanity) - 59.41s - 3 passed, 0 failed + +๐Ÿ“‹ Feature Summary: + โœ… usage (sanity): 3 passed, 0 failed + โœ… session_mgmt::test_usage_command::test_usage_command ... + โœ… session_mgmt::test_usage_command::test_usage_h_command ... + โœ… session_mgmt::test_usage_command::test_usage_help_command ... ๐ŸŽฏ FINAL SUMMARY ================================ -โœ… Categories Passed: 9 -โŒ Categories Failed: 0 -๐Ÿ“Š Total Categories: 9 -๐ŸŽ‰ All categories passed! +๐Ÿท๏ธ Features Tested: 1 +โœ… Features 100% Pass: 1 +โŒ Features with Failures: 0 +โœ… Individual Tests Passed: 3 +โŒ Individual Tests Failed: 0 +๐Ÿ“Š Total Individual Tests: 3 +๐Ÿ“ˆ Success Rate: 100.0% + +๐ŸŽ‰ All tests passed! + +๐Ÿ“„ Detailed report saved to: reports/qcli_test_summary_usage_sanity_091625223834.json +๐ŸŒ HTML report saved to: reports/qcli_test_summary_usage_sanity_091625223834.html ``` -### **Category Configuration** -Edit the top of `run_simple_categorized.sh` to enable/disable categories: +### **Help and Configuration** +Use the help command to see all available options: ```bash -# Enable/disable categories for faster iteration -RUN_CORE_SESSION=true -RUN_AGENT=false # Skip agent tests -RUN_CONTEXT=true -RUN_AI_PROMPTS=true -# etc... +# View all available options and categories +python run_tests.py --help +``` + ``` -### **Individual Category Testing** +e2etests % python run_tests.py --help + +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: + run_tests.py [options] # Run tests with default settings + run_tests.py --features # Run specific features + run_tests.py --list-features # List available features + run_tests.py --json-to-html # Convert JSON report to HTML (provide JSON file path) + +options: + -h, --help show this help message and exit + --list-features List all available features + --json-to-html JSON_PATH + Convert JSON report to HTML (provide JSON file path) + --features FEATURES Comma-separated list of features + --binary BINARY Path to Q CLI binary + --quiet Quiet mode + +Examples: + # Basic usage + run_tests.py # Run all tests with default sanity suite + run_tests.py --features usage # Run usage tests with default sanity suite + run_tests.py --features "usage,agent" # Run usage+agent tests with default sanity suite + + # Test suites + run_tests.py --features sanity # Run all tests with sanity suite + run_tests.py --features regression # Run all tests with regression suite + run_tests.py --features "usage,regression" # Run usage tests with regression suite + + + # Multiple features (different ways) + run_tests.py --features "usage,agent,context" # Comma-separated features with default sanity suite + run_tests.py --features usage --features agent # Multiple --features flags with default sanity suite + run_tests.py --features core_session # Run grouped feature (includes help,quit,clear) with default sanity suite + + # Binary and output options + run_tests.py --binary /path/to/q --features usage # Executes the usage tests on provided q-cli binary instead of installed + run_tests.py --quiet --features sanity # Executes the tests in quiet mode + + # Utility commands + run_tests.py --list-features # List all available features + run_tests.py --json-to-html report.json # Convert JSON report (previously generated by running test) to HTML + + # Advanced examples + run_tests.py --features "core_session,regression" --binary ./target/release/q + run_tests.py --features "agent,mcp,sanity" --quiet +``` + +### **Individual Category Testing Using Cargo** ```bash -# Test specific categories +# Test specific categories using cargo directly cargo test --tests --features "core_session" -- --nocapture cargo test --tests --features "agent" -- --nocapture cargo test --tests --features "ai_prompts" -- --nocapture ``` -### **Legacy Test Runner** -```bash -# Original test runner (still available) -./run_tests.sh -./run_tests.sh ../target/release/chat_cli -``` - ## โœ… Comprehensive Test Coverage -### **Commands Tested (32+ tests)** -- **Core Session**: `/help`, `/tools`, `/quit`, `/clear` -- **Agent Management**: `/agent list`, `/agent create`, `/agent help`, etc. -- **Context Management**: `/context show`, `/context add`, `/context help`, etc. -- **Save/Load**: `/save`, `/load`, help commands -- **Model Selection**: `/model`, `/model --help` -- **Session Management**: `/compact`, `/usage`, help commands -- **Integration**: `/subscribe`, `/hooks`, `/editor` help commands -- **MCP**: `/mcp`, `/mcp --help` +### **Commands Tested (122+ tests)** +- **Agent Commands** (8 tests): `/agent list`, `/agent create`, `/agent help`, etc. +- **AI Prompts** (5 tests): "What is AWS?", "Hello" prompts +- **Context Commands** (10 tests): `/context show`, `/context add`, `/context help`, etc. +- **Core Session** (3 tests): `/help`, `/quit`, `/clear` +- **Integration Commands** (21 tests): `/subscribe`, `/hooks`, `/editor` help commands +- **MCP Commands** (18 tests): `/mcp`, `/mcp --help` +- **Model Commands** (3 tests): `/model`, `/model --help` +- **Q Subcommands** (15 tests): q chat, q debug, q doctor, etc. +- **Save/Load Commands** (10 tests): `/save`, `/load`, help commands +- **Session Management** (14 tests): `/compact`, `/usage`, help commands +- **Todos Commands**: todos command +- **Tools Commands** (15 tests): `/tools`, tool management commands ### **AI Prompts Tested** - "What is AWS?" - Technical explanation with verification @@ -146,13 +262,11 @@ cargo test --tests --features "ai_prompts" -- --nocapture ### **Verification Includes** - **Content verification**: Specific text and sections present -- **Response quality**: Technical terms, appropriate length -- **Full output capture**: Complete interaction including UI elements - **Real-time feedback**: Per-test pass/fail status ## ๐ŸŽฏ Success Metrics -- **34 Total Tests** across 9 functional categories โœ… +- **122 Total Tests** across 12 functional categories โœ… - **Real-time feedback** with per-test results โœ… - **Categorized organization** for better reporting โœ… - **Configurable execution** for faster iteration โœ… @@ -164,7 +278,7 @@ This E2E test framework is designed to work with the Q CLI workspace: - **Default binary**: Uses system `q` command (from PATH) - **Workspace integration**: Can test the workspace build -- **CI/CD ready**: Can be integrated into build pipelines with categorized reporting +- **CI/CD integration**: Currently blocked due to Q CLI authentication issues - **Custom binary support**: Test different builds as needed ## ๐Ÿ”ง Extending @@ -174,30 +288,60 @@ This E2E test framework is designed to work with the Q CLI workspace: 1. **Create test file** in `tests/` directory 2. **Add feature attribute** to categorize the test: ```rust + /// Brief description of what the test does + /// More detailed description of verification steps and expected behavior #[test] - #[cfg(feature = "category_name")] + #[cfg(all(feature = "category_name", feature = "sanity"))] fn test_new_command() -> Result<(), Box> { - // Test implementation + println!("\n๐Ÿ” Testing /new command... | Description: Tests the /new command to verify functionality and expected behavior"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + let response = chat.execute_command("/new")?; + + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify response content + assert!(response.contains("expected_text"), "Missing expected content"); + println!("โœ… Command executed successfully!"); + + Ok(()) } ``` -3. **Update category configuration** in `run_simple_categorized.sh` if needed +3. **Update category configuration** in `run_tests.py` if needed ### **Adding New Categories** 1. **Add feature** to `Cargo.toml`: ```toml [features] - new_category = [] - ``` -2. **Add category** to `run_simple_categorized.sh`: - ```bash - RUN_NEW_CATEGORY=true - # ... - if [ "$RUN_NEW_CATEGORY" = true ]; then - run_category "new_category" "New Category Commands" - fi + core_session = ["help", "quit", "clear"] + help = [] + quit = [] + clear = [] + tools = [] + agent = [] + context = [] + save_load = [] + model = [] + session_mgmt = ["compact", "usage"] + compact = [] + usage = [] + integration = ["subscribe", "hooks", "editor", "issue_reporting"] + subscribe = [] + hooks = [] + editor = [] + issue_reporting = [] + mcp = [] + ai_prompts = [] + q_subcommand = [] + regression = [] + sanity = [] ``` - +**Python script will automatically pick the features from toml file. ### **Test Patterns** - **For Commands**: Use `execute_command()` method with expectrl diff --git a/e2etests/run_mcp_clean.sh b/e2etests/run_mcp_clean.sh deleted file mode 100755 index e7846eb788..0000000000 --- a/e2etests/run_mcp_clean.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# Clean MCP-only test runner - runs only MCP test files -# Usage: ./run_mcp_clean.sh [path_to_q_binary] - -Q_BINARY="q" - -if [ $# -gt 0 ]; then - Q_BINARY="$1" - export Q_CLI_PATH="$Q_BINARY" -fi - -echo "๐Ÿš€ Running MCP Commands Tests" -echo "=============================" -echo "" - -# Run only the specific MCP test files -echo "๐Ÿ”„ Running MCP tests..." -cargo test --test --features "mcp" -- --nocapture --test-threads=1 - -exit_code=$? - -echo "" -if [ $exit_code -eq 0 ]; then - echo "๐ŸŽ‰ All MCP tests passed!" -else - echo "๐Ÿ’ฅ Some MCP tests failed!" -fi - -exit $exit_code diff --git a/e2etests/run_mcp_only.sh b/e2etests/run_mcp_only.sh deleted file mode 100755 index 2f5a0a39f0..0000000000 --- a/e2etests/run_mcp_only.sh +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/bash - -# Simple categorized test runner with real-time output and summary -# Usage: ./run_mcp_only.sh [path_to_q_binary] [--quiet] - -# ============================================================================ -# CATEGORY CONFIGURATION - Set to true/false to enable/disable categories -# ============================================================================ -RUN_CORE_SESSION=false -RUN_AGENT=false -RUN_CONTEXT=false -RUN_SAVE_LOAD=false -RUN_MODEL=false -RUN_SESSION_MGMT=false -RUN_INTEGRATION=false -RUN_MCP=true -RUN_AI_PROMPTS=false -RUN_ISSUE_REPORTING=false -RUN_TOOLS=false -# ============================================================================ - -Q_BINARY="q" -QUIET_MODE=false - -# Parse arguments properly -while [[ $# -gt 0 ]]; do - case $1 in - --quiet|-q) - QUIET_MODE=false - shift - ;; - *) - if [ "$Q_BINARY" = "q" ]; then - Q_BINARY="$1" - fi - shift - ;; - esac -done - -if [ "$Q_BINARY" != "q" ]; then - export Q_CLI_PATH="$Q_BINARY" -fi - -echo "๐Ÿš€ Running Q CLI E2E tests by category" -echo "======================================" - -# Initialize counters -total_passed=0 -total_failed=0 -failed_categories="" - -run_category() { - local category=$1 - local name=$2 - - echo "" - echo "๐Ÿงช $name" - echo "$(printf '%*s' ${#name} '' | tr ' ' '-')" - - # Show which tests will run in this category - echo "๐Ÿ“‹ Tests in this category:" - test_count=0 - for file in tests/*.rs; do - if grep -q "cfg(feature = \"$category\")" "$file" 2>/dev/null; then - # Extract actual test function names from the file - grep -n "fn test_" "$file" | while read -r line; do - test_name=$(echo "$line" | sed 's/.*fn \(test_[^(]*\).*/\1/') - echo " โ€ข $test_name" - ((test_count++)) - done - fi - done - if [ $test_count -eq 0 ]; then - echo " โ€ข No tests found for this category" - fi - echo "" - - echo "๐Ÿ”„ Running tests..." - - if [ "$QUIET_MODE" = true ]; 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 - if echo "$line" | grep -q "test .* \.\.\. ok$"; then - test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. ok/\1/') - echo " โœ… $test_name" - elif echo "$line" | grep -q "test .* \.\.\. FAILED$"; then - test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. FAILED/\1/') - echo " โŒ $test_name" - fi - done - - # Check the exit status of cargo test - if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo "โœ… $name completed successfully" - return 0 - else - echo "โŒ $name had failures" - if [ -n "$failed_categories" ]; then - failed_categories="$failed_categories\n$name" - else - failed_categories="$name" - fi - return 1 - 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 - echo "$line" - if echo "$line" | grep -q "test .* \.\.\. ok$"; then - test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. ok/\1/') - echo " โœ… $test_name PASSED" - elif echo "$line" | grep -q "test .* \.\.\. FAILED$"; then - test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. FAILED/\1/') - echo " โŒ $test_name FAILED" - fi - done - - # Check the exit status of cargo test - if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo "" - echo "โœ… $name completed successfully" - return 0 - else - echo "" - echo "โŒ $name had failures" - if [ -n "$failed_categories" ]; then - failed_categories="$failed_categories\n$name" - else - failed_categories="$name" - fi - return 1 - fi - fi -} - -# Run each category and track results -if [ "$RUN_CORE_SESSION" = true ]; then - if run_category "core_session" "Core Session Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_AGENT" = true ]; then - if run_category "agent" "Agent Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_CONTEXT" = true ]; then - if run_category "context" "Context Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_SAVE_LOAD" = true ]; then - if run_category "save_load" "Save/Load Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_MODEL" = true ]; then - if run_category "model" "Model Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_SESSION_MGMT" = true ]; then - if run_category "session_mgmt" "Session Management Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_INTEGRATION" = true ]; then - if run_category "integration" "Integration Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_MCP" = true ]; then - if run_category "mcp" "MCP Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_AI_PROMPTS" = true ]; then - if run_category "ai_prompts" "AI Prompts"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_ISSUE_REPORTING" = true ]; then - if run_category "issue_reporting" "ISSUE REPORTING"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_TOOLS" = true ]; then - if run_category "tools" "TOOLS"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -# Final summary -echo "" -echo "๐ŸŽฏ FINAL SUMMARY" -echo "================================" -echo "โœ… Categories Passed: $total_passed" -echo "โŒ Categories Failed: $total_failed" -echo "๐Ÿ“Š Total Categories: $((total_passed + total_failed))" - -if [ -n "$failed_categories" ]; then - echo "" - echo "โŒ Failed Categories:" - echo -e "$failed_categories" | while read -r category; do - if [ -n "$category" ]; then - echo " โ€ข $category" - fi - done - echo "" - echo "๐Ÿ’ฅ Some tests failed!" - exit 1 -else - echo "" - echo "๐ŸŽ‰ All categories passed!" - exit 0 -fi diff --git a/e2etests/run_simple_categorized.sh b/e2etests/run_simple_categorized.sh deleted file mode 100755 index f031731696..0000000000 --- a/e2etests/run_simple_categorized.sh +++ /dev/null @@ -1,288 +0,0 @@ -#!/bin/bash - -# Simple categorized test runner with real-time output and summary -# Usage: ./run_simple_categorized.sh [path_to_q_binary] [--quiet] - -# ============================================================================ -# CATEGORY CONFIGURATION - Set to true/false to enable/disable categories -# ============================================================================ -RUN_CORE_SESSION=true -RUN_AGENT=true -RUN_CONTEXT=true -RUN_SAVE_LOAD=true -RUN_MODEL=true -RUN_SESSION_MGMT=true -RUN_INTEGRATION=true -RUN_MCP=true -RUN_AI_PROMPTS=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" -QUIET_MODE=false - -# Parse arguments properly -while [[ $# -gt 0 ]]; do - case $1 in - --quiet|-q) - QUIET_MODE=false - shift - ;; - *) - if [ "$Q_BINARY" = "q" ]; then - Q_BINARY="$1" - fi - shift - ;; - esac -done - -if [ "$Q_BINARY" != "q" ]; then - export Q_CLI_PATH="$Q_BINARY" -fi - -echo "๐Ÿš€ Running Q CLI E2E tests by category" -echo "======================================" - -# Initialize counters -total_passed=0 -total_failed=0 -failed_categories="" - -run_category() { - local category=$1 - local name=$2 - - echo "" - echo "๐Ÿงช $name" - echo "$(printf '%*s' ${#name} '' | tr ' ' '-')" - - # Show which tests will run in this category - echo "๐Ÿ“‹ Tests in this category:" - for file in tests/*.rs; do - if grep -q "cfg(feature = \"$category\")" "$file" 2>/dev/null; then - test_name=$(basename "$file" .rs) - echo " โ€ข $test_name" - fi - done - echo "" - - echo "๐Ÿ”„ Running tests..." - - if [ "$QUIET_MODE" = false ]; then - # Quiet mode - show individual test results in real-time - 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" - elif echo "$line" | grep -q "test .* \.\.\. FAILED$"; then - test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. FAILED/\1/') - echo " โŒ $test_name" - fi - done - - # Check the exit status of cargo test - if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo "โœ… $name completed successfully" - return 0 - else - echo "โŒ $name had failures" - if [ -n "$failed_categories" ]; then - failed_categories="$failed_categories\n$name" - else - failed_categories="$name" - fi - return 1 - fi - else - # Verbose mode - show full output with real-time test results - 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/') - echo " โœ… $test_name PASSED" - elif echo "$line" | grep -q "test .* \.\.\. FAILED$"; then - test_name=$(echo "$line" | sed 's/test \(.*\) \.\.\. FAILED/\1/') - echo " โŒ $test_name FAILED" - fi - done - - # Check the exit status of cargo test - if [ ${PIPESTATUS[0]} -eq 0 ]; then - echo "" - echo "โœ… $name completed successfully" - return 0 - else - echo "" - echo "โŒ $name had failures" - if [ -n "$failed_categories" ]; then - failed_categories="$failed_categories\n$name" - else - failed_categories="$name" - fi - return 1 - fi - fi -} - -# Run each category and track results -if [ "$RUN_CORE_SESSION" = true ]; then - if run_category "core_session" "Core Session Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_AGENT" = true ]; then - if run_category "agent" "Agent Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_CONTEXT" = true ]; then - if run_category "context" "Context Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_SAVE_LOAD" = true ]; then - if run_category "save_load" "Save/Load Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_MODEL" = true ]; then - if run_category "model" "Model Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_SESSION_MGMT" = true ]; then - if run_category "session_mgmt" "Session Management Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_INTEGRATION" = true ]; then - if run_category "integration" "Integration Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_MCP" = true ]; then - if run_category "mcp" "MCP Commands"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_AI_PROMPTS" = true ]; then - if run_category "ai_prompts" "AI Prompts"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_ISSUE_REPORTING" = true ]; then - if run_category "issue_reporting" "ISSUE REPORTING"; then - ((total_passed++)) - else - ((total_failed++)) - fi -fi - -if [ "$RUN_TOOLS" = true ]; then - if run_category "tools" "TOOLS"; 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 "" -echo "๐ŸŽฏ FINAL SUMMARY" -echo "================================" -echo "โœ… Categories Passed: $total_passed" -echo "โŒ Categories Failed: $total_failed" -echo "๐Ÿ“Š Total Categories: $((total_passed + total_failed))" - -if [ -n "$failed_categories" ]; then - echo "" - echo "โŒ Failed Categories:" - echo -e "$failed_categories" | while read -r category; do - if [ -n "$category" ]; then - echo " โ€ข $category" - fi - done - echo "" - echo "๐Ÿ’ฅ Some tests failed!" - exit 1 -else - echo "" - echo "๐ŸŽ‰ All categories passed!" - exit 0 -fi diff --git a/e2etests/run_tests.sh b/e2etests/run_tests.sh deleted file mode 100755 index b1123478e5..0000000000 --- a/e2etests/run_tests.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# Q CLI E2E Test Runner -# Usage: ./run_tests.sh [path_to_q_binary] [test_name] - -Q_BINARY=${1:-"q"} -TEST_NAME=${2:-""} - -echo "๐Ÿš€ Running Q CLI E2E tests with binary: $Q_BINARY" - -if [ "$Q_BINARY" != "q" ]; then - if [ ! -f "$Q_BINARY" ]; then - echo "โŒ Error: Q CLI binary not found at: $Q_BINARY" - exit 1 - fi - echo "๐Ÿ“ Using custom Q CLI binary: $Q_BINARY" - export Q_CLI_PATH="$Q_BINARY" -else - echo "๐Ÿ“ Using default system Q CLI binary" - export Q_CLI_PATH="$Q_BINARY" -fi - -if [ -n "$TEST_NAME" ]; then - echo "๐Ÿงช Running specific test: $TEST_NAME" - cargo test --test "$TEST_NAME" -- --nocapture -else - echo "๐Ÿงช Running all E2E tests" - cargo test --tests -- --nocapture --test-threads=1 -fi diff --git a/e2etests/run_tools_clean.sh b/e2etests/run_tools_clean.sh deleted file mode 100755 index c5645dada2..0000000000 --- a/e2etests/run_tools_clean.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# Clean tools command test runner -# Usage: ./run_tools_clean.sh [path_to_q_binary] - -Q_BINARY="q" - -if [ $# -gt 0 ]; then - Q_BINARY="$1" - export Q_CLI_PATH="$Q_BINARY" -fi - -echo "๐Ÿš€ Running Tools Command Test" -echo "=============================" -echo "" - -# Run only the /tools command test -echo "๐Ÿ”„ Running /tools test..." -cargo test --test test_tools_command --features "core_session" -- --nocapture --test-threads=1 - -exit_code=$? - -echo "" -if [ $exit_code -eq 0 ]; then - echo "๐ŸŽ‰ Tools test passed!" -else - echo "๐Ÿ’ฅ Tools test failed!" -fi - -exit $exit_code diff --git a/e2etests/src/lib.rs b/e2etests/src/lib.rs index 854730b062..ac60e42fb6 100644 --- a/e2etests/src/lib.rs +++ b/e2etests/src/lib.rs @@ -104,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(5000)); + std::thread::sleep(Duration::from_millis(10000)); if total_content.len() > 0 { break; } }, Err(_) => break, } - std::thread::sleep(Duration::from_millis(5000)); + std::thread::sleep(Duration::from_millis(10000)); } Ok(total_content) diff --git a/e2etests/tests/agent/test_agent_commands.rs b/e2etests/tests/agent/test_agent_commands.rs index 830c66d31c..dfdeec2de3 100644 --- a/e2etests/tests/agent/test_agent_commands.rs +++ b/e2etests/tests/agent/test_agent_commands.rs @@ -59,7 +59,7 @@ fn agent_without_subcommand() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/agent")?; @@ -118,7 +118,7 @@ fn test_agent_create_command() -> Result<(), Box> { let agent_name = format!("test_demo_agent_{}", timestamp); let session = get_chat_session(); - let mut chat = session.lock().unwrap(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let create_response = chat.execute_command(&format!("/agent create --name {}", agent_name))?; @@ -181,7 +181,7 @@ fn test_agent_create_missing_args() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/agent create")?; @@ -232,7 +232,7 @@ fn test_agent_help_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/agent help")?; @@ -241,33 +241,23 @@ fn test_agent_help_command() -> Result<(), Box> { println!("{}", response); println!("๐Ÿ“ END OUTPUT"); - let mut failures = Vec::new(); - - if !response.contains("Agents allow you to organize") { failures.push("Missing description"); } - if !response.contains("manage different sets of context") { failures.push("Missing context description"); } - if !response.contains("Notes") { failures.push("Missing notes section"); } - if !response.contains("Launch q chat with a specific agent") { failures.push("Missing launch note"); } - if !response.contains("--agent") { failures.push("Missing agent flag"); } - if !response.contains("Construct an agent under") { failures.push("Missing construct note"); } - if !response.contains("~/.aws/amazonq/cli-agents/") { failures.push("Missing global path"); } - if !response.contains("cwd/.aws/amazonq/cli-agents") { failures.push("Missing workspace path"); } - if !response.contains("Manage agents") { failures.push("Missing manage section"); } - if !response.contains("Usage:") { failures.push("Missing usage label"); } - if !response.contains("/agent") { failures.push("Missing agent command"); } - if !response.contains("") { failures.push("Missing command parameter"); } - if !response.contains("Commands:") { failures.push("Missing commands section"); } - if !response.contains("list") { failures.push("Missing list command"); } - if !response.contains("create") { failures.push("Missing create command"); } - if !response.contains("schema") { failures.push("Missing schema command"); } - if !response.contains("set-default") { failures.push("Missing set-default command"); } - if !response.contains("help") { failures.push("Missing help command"); } - if !response.contains("Options:") { failures.push("Missing options section"); } - if !response.contains("-h") { failures.push("Missing short help flag"); } - if !response.contains("--help") { failures.push("Missing long help flag"); } + assert!(response.contains("~/.aws/amazonq/cli-agents/"), "Missing global path"); + assert!(response.contains("cwd/.amazonq/cli-agents"), "Missing workspace path"); + assert!(response.contains("Usage:"), "Missing usage label"); + assert!(response.contains("/agent"), "Missing agent command"); + assert!(response.contains(""), "Missing command parameter"); + assert!(response.contains("Commands:"), "Missing commands section"); + assert!(response.contains("list"), "Missing list command"); + assert!(response.contains("create"), "Missing create command"); + assert!(response.contains("schema"), "Missing schema command"); + assert!(response.contains("set-default"), "Missing set-default command"); + assert!(response.contains("help"), "Missing help command"); + println!("โœ… Found all expected commands in help output"); - if !failures.is_empty() { - panic!("Test failures: {}", failures.join(", ")); - } + assert!(response.contains("Options:"), "Missing options section"); + assert!(response.contains("-h"), "Missing short help flag"); + assert!(response.contains("--help"), "Missing long help flag"); + println!("โœ… Found all expected options in help output"); println!("โœ… All expected help content found"); println!("โœ… /agent help executed successfully"); @@ -289,7 +279,7 @@ fn test_agent_invalid_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/agent invalidcommand")?; @@ -328,7 +318,7 @@ fn test_agent_list_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/agent list")?; @@ -403,7 +393,7 @@ fn test_agent_set_default_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/agent set-default -n q_cli_default")?; @@ -444,7 +434,7 @@ fn test_agent_set_default_missing_args() -> Result<(), Box /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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/agent set-default")?; diff --git a/e2etests/tests/all_tests.rs b/e2etests/tests/all_tests.rs index 2c02d604b5..1a51ea765b 100644 --- a/e2etests/tests/all_tests.rs +++ b/e2etests/tests/all_tests.rs @@ -10,4 +10,5 @@ mod q_subcommand; mod save_load; mod session_mgmt; mod tools; -mod todos; \ No newline at end of file +mod todos; +mod experiment; diff --git a/e2etests/tests/core_session/mod.rs b/e2etests/tests/core_session/mod.rs index 4868ad9f8e..de2daf69a7 100644 --- a/e2etests/tests/core_session/mod.rs +++ b/e2etests/tests/core_session/mod.rs @@ -1,3 +1,4 @@ pub mod test_clear_command; pub mod test_help_command; -pub mod test_quit_command; \ No newline at end of file +pub mod test_quit_command; +pub mod test_changelog_command; \ No newline at end of file diff --git a/e2etests/tests/core_session/test_changelog_command.rs b/e2etests/tests/core_session/test_changelog_command.rs new file mode 100644 index 0000000000..25b8b3deb4 --- /dev/null +++ b/e2etests/tests/core_session/test_changelog_command.rs @@ -0,0 +1,128 @@ +#[allow(unused_imports)] +use q_cli_e2e_tests::q_chat_helper; +use std::sync::{Mutex, Once, atomic::{AtomicUsize, Ordering}}; +use regex::Regex; + +#[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] = &[ + "test_changelog_command", + "test_changelog_help_command", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "changelog", feature = "sanity"))] +fn test_changelog_command() -> Result<(), Box> { + println!("\n๐Ÿ” Testing /changelog command... | Description: Tests the /changelog command to display version history and updates"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + println!("โœ… Q Chat session started"); + + let response = chat.execute_command("/changelog")?; + + println!("๐Ÿ“ Changelog response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify changelog content + assert!(response.contains("New") && response.contains("Amazon Q CLI"), "Missing changelog header"); + println!("โœ… Found changelog header"); + + // Verify version format (e.g., 1.16.2) + let version_regex = Regex::new(r"## \d+\.\d+\.\d+").unwrap(); + assert!(version_regex.is_match(&response), "Missing version format (x.x.x)"); + println!("โœ… Found valid version format"); + + // Verify date format (e.g., 2025-09-19) + let date_regex = Regex::new(r"\(\d{4}-\d{2}-\d{2}\)").unwrap(); + assert!(date_regex.is_match(&response), "Missing date format (YYYY-MM-DD)"); + println!("โœ… Found valid date format"); + + // Verify /changelog command reference + assert!(response.contains("/changelog"), "Missing /changelog command reference"); + println!("โœ… Found /changelog command reference"); + + println!("โœ… /changelog command 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 = "changelog", feature = "sanity"))] +fn test_changelog_help_command() -> Result<(), Box> { + println!("\n๐Ÿ” Testing /changelog -h command... | Description: Tests the /changelog -h command to display help information"); + + let session = get_chat_session(); + let mut chat = session.lock().unwrap(); + + println!("โœ… Q Chat session started"); + + let response = chat.execute_command("/changelog -h")?; + + println!("๐Ÿ“ Help response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify help content + assert!(response.contains("Usage:") && response.contains("/changelog"), "Missing usage information"); + assert!(response.contains("Options:"), "Missing Options section"); + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("โœ… Found all expected help content"); + + println!("โœ… /changelog -h command 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/experiment/mod.rs b/e2etests/tests/experiment/mod.rs new file mode 100644 index 0000000000..f79f6c16cd --- /dev/null +++ b/e2etests/tests/experiment/mod.rs @@ -0,0 +1 @@ +pub mod test_experiment_command; diff --git a/e2etests/tests/experiment/test_experiment_command.rs b/e2etests/tests/experiment/test_experiment_command.rs new file mode 100644 index 0000000000..4217f2f048 --- /dev/null +++ b/e2etests/tests/experiment/test_experiment_command.rs @@ -0,0 +1,564 @@ +#[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] = &[ + "test_knowledge_command", + "test_thinking_command", + "test_experiment_help_command", + "test_tangent_mode_experiment", + "test_todo_lists_experiment", +]; +#[allow(dead_code)] +const TOTAL_TESTS: usize = TEST_NAMES.len(); + +#[test] +#[cfg(all(feature = "experiment", feature = "sanity"))] +fn test_knowledge_command() -> Result<(), Box> { + println!("\n๐Ÿ” Testing /experiment command... | Description: Tests the /experiment command to toggle Knowledge experimental features"); + + 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("/experiment")?; + + println!("๐Ÿ“ Experiment response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify experiment menu content + assert!(response.contains("Select"), "Missing selection prompt"); + assert!(response.contains("Knowledge"), "Missing Knowledge experiment"); + println!("โœ… Found experiment menu with Knowledge option"); + + // Find Knowledge and check if it's already selected + let lines: Vec<&str> = response.lines().collect(); + let mut knowledge_menu_position = 0; + let mut knowledge_state = false; + let mut found = false; + let mut knowledge_already_selected = false; + + // Check if Knowledge is already selected (has โฏ) + for line in lines.iter() { + if line.contains("Knowledge") && line.trim_start().starts_with("โฏ") { + knowledge_already_selected = true; + knowledge_state = line.contains("[ON]"); + found = true; + break; + } + } + + // If not selected, find its position + if !knowledge_already_selected { + let mut menu_position = 0; + for line in lines.iter() { + let trimmed = line.trim_start(); + if trimmed.starts_with("โฏ") || (trimmed.contains("[ON]") || trimmed.contains("[OFF]")) { + if line.contains("Knowledge") { + knowledge_menu_position = menu_position; + knowledge_state = line.contains("[ON]"); + found = true; + break; + } + menu_position += 1; + } + } + } + + assert!(found, "Knowledge option not found in menu"); + println!("๐Ÿ“ Knowledge already selected: {}, position: {}, state: {}", knowledge_already_selected, knowledge_menu_position, if knowledge_state { "ON" } else { "OFF" }); + + // Navigate to Knowledge option using arrow keys (only if not already selected) + if !knowledge_already_selected { + for _ in 0..knowledge_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + + // Select the Knowledge option + let navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Navigate response: {} bytes", navigate_response.len()); + println!("๐Ÿ“ NAVIGATE RESPONSE:"); + println!("{}", navigate_response); + println!("๐Ÿ“ END NAVIGATE RESPONSE"); + + // Verify toggle response based on previous state + if knowledge_state { + assert!(navigate_response.contains("Knowledge experiment disabled"), "Expected Knowledge to be disabled"); + println!("โœ… Knowledge experiment disabled successfully"); + } else { + assert!(navigate_response.contains("Knowledge experiment enabled"), "Expected Knowledge to be enabled"); + println!("โœ… Knowledge experiment enabled successfully"); + } + + // Test reverting back to original state (run command again) + println!("๐Ÿ“ Testing revert to original state..."); + let revert_response = chat.execute_command("/experiment")?; + + // Navigate to Knowledge option again (only if not already selected) + if !knowledge_already_selected { + for _ in 0..knowledge_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + let revert_navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Revert response: {} bytes", revert_navigate_response.len()); + println!("๐Ÿ“ REVERT RESPONSE:"); + println!("{}", revert_navigate_response); + println!("๐Ÿ“ END REVERT RESPONSE"); + + // Verify it reverted to original state + if knowledge_state { + assert!(revert_navigate_response.contains("Knowledge experiment enabled"), "Expected Knowledge to be enabled (reverted)"); + println!("โœ… Knowledge experiment reverted to enabled successfully"); + } else { + assert!(revert_navigate_response.contains("Knowledge experiment disabled"), "Expected Knowledge to be disabled (reverted)"); + println!("โœ… Knowledge experiment reverted to disabled successfully"); + } + + println!("โœ… /experiment command 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 = "experiment", feature = "sanity"))] +fn test_thinking_command() -> Result<(), Box> { + println!("\n๐Ÿ” Testing /experiment command... | Description: Tests the /experiment command to toggle thinking experimental features"); + + 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("/experiment")?; + + println!("๐Ÿ“ Experiment response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify experiment menu content + assert!(response.contains("Select"), "Missing selection prompt"); + assert!(response.contains("Thinking"), "Missing Thinking experiment"); + println!("โœ… Found experiment menu with Thinking option"); + + // Find Thinking and check if it's already selected + let lines: Vec<&str> = response.lines().collect(); + let mut Thinking_menu_position = 0; + let mut Thinking_state = false; + let mut found = false; + let mut Thinking_already_selected = false; + + // Check if Thinking is already selected (has โฏ) + for line in lines.iter() { + if line.contains("Thinking") && line.trim_start().starts_with("โฏ") { + Thinking_already_selected = true; + Thinking_state = line.contains("[ON]"); + found = true; + break; + } + } + + // If not selected, find its position + if !Thinking_already_selected { + let mut menu_position = 0; + for line in lines.iter() { + let trimmed = line.trim_start(); + if trimmed.starts_with("โฏ") || (trimmed.contains("[ON]") || trimmed.contains("[OFF]")) { + if line.contains("Thinking") { + Thinking_menu_position = menu_position; + Thinking_state = line.contains("[ON]"); + found = true; + break; + } + menu_position += 1; + } + } + } + + assert!(found, "Thinking option not found in menu"); + println!("๐Ÿ“ Thinking already selected: {}, position: {}, state: {}", Thinking_already_selected, Thinking_menu_position, if Thinking_state { "ON" } else { "OFF" }); + + // Navigate to Thinking option using arrow keys (only if not already selected) + if !Thinking_already_selected { + for _ in 0..Thinking_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + + // Select the Thinking option + let navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Navigate response: {} bytes", navigate_response.len()); + println!("๐Ÿ“ NAVIGATE RESPONSE:"); + println!("{}", navigate_response); + println!("๐Ÿ“ END NAVIGATE RESPONSE"); + + // Verify toggle response based on previous state + if Thinking_state { + assert!(navigate_response.contains("Thinking experiment disabled"), "Expected Thinking to be disabled"); + println!("โœ… Thinking experiment disabled successfully"); + } else { + assert!(navigate_response.contains("Thinking experiment enabled"), "Expected Thinking to be enabled"); + println!("โœ… Thinking experiment enabled successfully"); + } + + // Test reverting back to original state (run command again) + println!("๐Ÿ“ Testing revert to original state..."); + let revert_response = chat.execute_command("/experiment")?; + + // Navigate to Thinking option again (only if not already selected) + if !Thinking_already_selected { + for _ in 0..Thinking_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + let revert_navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Revert response: {} bytes", revert_navigate_response.len()); + println!("๐Ÿ“ REVERT RESPONSE:"); + println!("{}", revert_navigate_response); + println!("๐Ÿ“ END REVERT RESPONSE"); + + // Verify it reverted to original state + if Thinking_state { + assert!(revert_navigate_response.contains("Thinking experiment enabled"), "Expected Thinking to be enabled (reverted)"); + println!("โœ… Thinking experiment reverted to enabled successfully"); + } else { + assert!(revert_navigate_response.contains("Thinking experiment disabled"), "Expected Thinking to be disabled (reverted)"); + println!("โœ… Thinking experiment reverted to disabled successfully"); + } + + println!("โœ… /experiment command 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 = "experiment", feature = "sanity"))] +fn test_experiment_help_command() -> Result<(), Box> { + println!("\n๐Ÿ” Testing /experiment --help command... | Description: Tests the /experiment --help command to display help information"); + + 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("/experiment --help")?; + + println!("๐Ÿ“ Help response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify help content + assert!(response.contains("Usage:") && response.contains("/experiment"), "Missing usage information"); + assert!(response.contains("Options:"), "Missing Options section"); + assert!(response.contains("-h") && response.contains("--help"), "Missing -h, --help flags"); + println!("โœ… Found all expected help content"); + + println!("โœ… /experiment --help command 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 = "experiment", feature = "sanity"))] +fn test_tangent_mode_experiment() -> Result<(), Box> { + println!("\n๐Ÿ” Testing Tangent Mode experiment... | Description: Tests the /experiment command to toggle Tangent Mode experimental feature"); + + 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("/experiment")?; + + println!("๐Ÿ“ Experiment response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify experiment menu content + assert!(response.contains("Select"), "Missing selection prompt"); + assert!(response.contains("Tangent Mode"), "Missing Tangent Mode experiment"); + println!("โœ… Found experiment menu with Tangent Mode option"); + + // Find Tangent Mode and check if it's already selected + let lines: Vec<&str> = response.lines().collect(); + let mut Tangent_menu_position = 0; + let mut Tangent_state = false; + let mut found = false; + let mut Tangent_already_selected = false; + + // Check if Tangent Mode is already selected (has โฏ) + for line in lines.iter() { + if line.contains("Tangent Mode") && line.trim_start().starts_with("โฏ") { + Tangent_already_selected = true; + Tangent_state = line.contains("[ON]"); + found = true; + break; + } + } + + // If not selected, find its position + if !Tangent_already_selected { + let mut menu_position = 0; + for line in lines.iter() { + let trimmed = line.trim_start(); + if trimmed.starts_with("โฏ") || (trimmed.contains("[ON]") || trimmed.contains("[OFF]")) { + if line.contains("Tangent Mode") { + Tangent_menu_position = menu_position; + Tangent_state = line.contains("[ON]"); + found = true; + break; + } + menu_position += 1; + } + } + } + + assert!(found, "Tangent Mode option not found in menu"); + println!("๐Ÿ“ Tangent Mode already selected: {}, position: {}, state: {}", Tangent_already_selected, Tangent_menu_position, if Tangent_state { "ON" } else { "OFF" }); + + // Navigate to Tangent Mode option using arrow keys (only if not already selected) + if !Tangent_already_selected { + for _ in 0..Tangent_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + + // Select the Tangent Mode option + let navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Navigate response: {} bytes", navigate_response.len()); + println!("๐Ÿ“ NAVIGATE RESPONSE:"); + println!("{}", navigate_response); + println!("๐Ÿ“ END NAVIGATE RESPONSE"); + + // Verify toggle response based on previous state + if Tangent_state { + assert!(navigate_response.contains("Tangent Mode experiment disabled"), "Expected Tangent Mode to be disabled"); + println!("โœ… Tangent Mode experiment disabled successfully"); + } else { + assert!(navigate_response.contains("Tangent Mode experiment enabled"), "Expected Tangent Mode to be enabled"); + println!("โœ… Tangent Mode experiment enabled successfully"); + } + + // Test reverting back to original state (run command again) + println!("๐Ÿ“ Testing revert to original state..."); + let revert_response = chat.execute_command("/experiment")?; + + // Navigate to Tangent Mode option again (only if not already selected) + if !Tangent_already_selected { + for _ in 0..Tangent_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + let revert_navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Revert response: {} bytes", revert_navigate_response.len()); + println!("๐Ÿ“ REVERT RESPONSE:"); + println!("{}", revert_navigate_response); + println!("๐Ÿ“ END REVERT RESPONSE"); + + // Verify it reverted to original state + if Tangent_state { + assert!(revert_navigate_response.contains("Tangent Mode experiment enabled"), "Expected Tangent Mode to be enabled (reverted)"); + println!("โœ… Tangent Mode experiment reverted to enabled successfully"); + } else { + assert!(revert_navigate_response.contains("Tangent Mode experiment disabled"), "Expected Tangent Mode to be disabled (reverted)"); + println!("โœ… Tangent Mode experiment reverted to disabled successfully"); + } + + println!("โœ… Tangent Mode experiment 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 = "experiment", feature = "sanity"))] +fn test_todo_lists_experiment() -> Result<(), Box> { + println!("\n๐Ÿ” Testing Todo Lists experiment... | Description: Tests the /experiment command to toggle Todo Lists experimental feature"); + + 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("/experiment")?; + + println!("๐Ÿ“ Experiment response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Verify experiment menu content + assert!(response.contains("Select"), "Missing selection prompt"); + assert!(response.contains("Todo Lists"), "Missing Todo Lists experiment"); + println!("โœ… Found experiment menu with Todo Lists option"); + + // Find Todo Lists and check if it's already selected + let lines: Vec<&str> = response.lines().collect(); + let mut TodoLists_menu_position = 0; + let mut TodoLists_state = false; + let mut found = false; + let mut TodoLists_already_selected = false; + + // Check if Todo Lists is already selected (has โฏ) + for line in lines.iter() { + if line.contains("Todo Lists") && line.trim_start().starts_with("โฏ") { + TodoLists_already_selected = true; + TodoLists_state = line.contains("[ON]"); + found = true; + break; + } + } + + // If not selected, find its position + if !TodoLists_already_selected { + let mut menu_position = 0; + for line in lines.iter() { + let trimmed = line.trim_start(); + if trimmed.starts_with("โฏ") || (trimmed.contains("[ON]") || trimmed.contains("[OFF]")) { + if line.contains("Todo Lists") { + TodoLists_menu_position = menu_position; + TodoLists_state = line.contains("[ON]"); + found = true; + break; + } + menu_position += 1; + } + } + } + + assert!(found, "Todo Lists option not found in menu"); + println!("๐Ÿ“ Todo Lists already selected: {}, position: {}, state: {}", TodoLists_already_selected, TodoLists_menu_position, if TodoLists_state { "ON" } else { "OFF" }); + + // Navigate to Todo Lists option using arrow keys (only if not already selected) + if !TodoLists_already_selected { + for _ in 0..TodoLists_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + + // Select the Todo Lists option + let navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Navigate response: {} bytes", navigate_response.len()); + println!("๐Ÿ“ NAVIGATE RESPONSE:"); + println!("{}", navigate_response); + println!("๐Ÿ“ END NAVIGATE RESPONSE"); + + // Verify toggle response based on previous state + if TodoLists_state { + assert!(navigate_response.contains("Todo Lists experiment disabled"), "Expected Todo Lists to be disabled"); + println!("โœ… Todo Lists experiment disabled successfully"); + } else { + assert!(navigate_response.contains("Todo Lists experiment enabled"), "Expected Todo Lists to be enabled"); + println!("โœ… Todo Lists experiment enabled successfully"); + } + + // Test reverting back to original state (run command again) + println!("๐Ÿ“ Testing revert to original state..."); + let revert_response = chat.execute_command("/experiment")?; + + // Navigate to Todo Lists option again (only if not already selected) + if !TodoLists_already_selected { + for _ in 0..TodoLists_menu_position { + chat.send_key_input("\x1b[B")?; // Down arrow + } + } + let revert_navigate_response = chat.send_key_input("\r")?; // Enter + + println!("๐Ÿ“ Revert response: {} bytes", revert_navigate_response.len()); + println!("๐Ÿ“ REVERT RESPONSE:"); + println!("{}", revert_navigate_response); + println!("๐Ÿ“ END REVERT RESPONSE"); + + // Verify it reverted to original state + if TodoLists_state { + assert!(revert_navigate_response.contains("Todo Lists experiment enabled"), "Expected Todo Lists to be enabled (reverted)"); + println!("โœ… Todo Lists experiment reverted to enabled successfully"); + } else { + assert!(revert_navigate_response.contains("Todo Lists experiment disabled"), "Expected Todo Lists to be disabled (reverted)"); + println!("โœ… Todo Lists experiment reverted to disabled successfully"); + } + + println!("โœ… Todo Lists experiment 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/integration/test_editor_help_command.rs b/e2etests/tests/integration/test_editor_help_command.rs index ed7cac3fbf..ceb3ec32cc 100644 --- a/e2etests/tests/integration/test_editor_help_command.rs +++ b/e2etests/tests/integration/test_editor_help_command.rs @@ -316,15 +316,31 @@ fn test_editor_with_file_path() -> Result<(), Box> { // 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"); + + if wq_response.contains("Using tool:") && wq_response.contains("Allow this action?"){ + 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 the file content is loaded in editor + assert!(allow_response.contains("Hello from test file"), "File content not loaded in editor"); + println!("โœ… File content loaded successfully in editor"); + + } + else{ + // 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"); + } - // 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(); diff --git a/e2etests/tests/q_subcommand/test_q_inline_subcommand.rs b/e2etests/tests/q_subcommand/test_q_inline_subcommand.rs index 1e4f07478b..8ee3865f15 100644 --- a/e2etests/tests/q_subcommand/test_q_inline_subcommand.rs +++ b/e2etests/tests/q_subcommand/test_q_inline_subcommand.rs @@ -252,7 +252,7 @@ fn test_q_inline_set_customization_subcommand() -> Result<(), Box Result<(), Box 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)?; + // Get the interactive menu to find None position (always at last line) + let menu_response = q_chat_helper::execute_q_subcommand("q", &["inline", "set-customization"])?; + let none_index = menu_response.lines().count(); + + + let response = q_chat_helper::execute_interactive_menu_selection("q", &["inline", "set-customization"], none_index)?; println!("๐Ÿ“ Debug response: {} bytes", response.len()); println!("๐Ÿ“ FULL OUTPUT:"); diff --git a/e2etests/tests/q_subcommand/test_q_whoami_subcommand.rs b/e2etests/tests/q_subcommand/test_q_whoami_subcommand.rs index ff4f4ffd6f..2bb95c5069 100644 --- a/e2etests/tests/q_subcommand/test_q_whoami_subcommand.rs +++ b/e2etests/tests/q_subcommand/test_q_whoami_subcommand.rs @@ -21,5 +21,113 @@ fn test_q_whoami_subcommand() -> Result<(), Box> { println!("โœ… Whoami information displayed successfully!"); + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_whoami_help_subcommand() -> Result<(), Box> { + println!("\n๐Ÿ” Testing q whoami --help subcommand... | Description: Tests the q whoami --help subcommand to validate help output format and content."); + + println!("\n๐Ÿ” Executing 'q whoami --help' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["whoami", "--help"])?; + + println!("๐Ÿ“ whoami response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Assert whoami help output contains expected commands + assert!(response.contains("Usage:") && response.contains("q whoami") && response.contains("[OPTIONS]"), + "Help should contain usage line"); + assert!(response.contains("Options:"), "Help should contain Options section"); + 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!("โœ… Got whoami help output ({} bytes)!", response.len()); + println!("โœ… q whoami --help subcommand executed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_whoami_f_plain_subcommand() -> Result<(), Box> { + println!("\n๐Ÿ” Testing q whoami -f plain subcommand... | Description: Tests the q whoami -f plain subcommand to display user profile information in plain format."); + + println!("\n๐Ÿ› ๏ธ Running 'q whoami -f plain' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["whoami", "-f", "plain"])?; + + 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(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_whoami_f_json_subcommand() -> Result<(), Box> { + println!("\n๐Ÿ” Testing q whoami -f json subcommand... | Description: Tests the q whoami -f json subcommand to display user profile information in json format."); + + println!("\n๐Ÿ› ๏ธ Running 'q whoami -f json' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["whoami", "-f", "json"])?; + + println!("๐Ÿ“ Whoami response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Check if accountType and region appear between { and } + let start = response.find('{').unwrap(); + let end = response.rfind('}').unwrap(); + let json_content = &response[start..=end]; + assert!(json_content.contains("accountType")); + assert!(json_content.contains("region")); + + // Validate JSON is single-line format + assert!(!json_content[1..json_content.len()-1].contains('\n'), "JSON should be in single-line format"); + + // Validate output contains expected authentication information + assert!(response.contains("Profile:"), "Should contain Profile section"); + + println!("โœ… Whoami information displayed successfully!"); + + Ok(()) +} + +#[test] +#[cfg(all(feature = "q_subcommand", feature = "sanity"))] +fn test_q_whoami_f_json_pretty_subcommand() -> Result<(), Box> { + println!("\n๐Ÿ” Testing q whoami -f json-pretty subcommand... | Description: Tests the q whoami -f json-pretty subcommand to display user profile information in pretty json format."); + + println!("\n๐Ÿ› ๏ธ Running 'q whoami -f json-pretty' subcommand..."); + let response = q_chat_helper::execute_q_subcommand("q", &["whoami", "-f", "json-pretty"])?; + + println!("๐Ÿ“ Whoami response: {} bytes", response.len()); + println!("๐Ÿ“ FULL OUTPUT:"); + println!("{}", response); + println!("๐Ÿ“ END OUTPUT"); + + // Check if accountType and region appear between { and } + let start = response.find('{').unwrap(); + let end = response.rfind('}').unwrap(); + let json_content = &response[start..=end]; + assert!(json_content.contains("accountType")); + assert!(json_content.contains("region")); + + // Validate output contains expected authentication information + 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/todos/test_todos_command.rs b/e2etests/tests/todos/test_todos_command.rs index 34d8908859..19b541136d 100644 --- a/e2etests/tests/todos/test_todos_command.rs +++ b/e2etests/tests/todos/test_todos_command.rs @@ -41,7 +41,9 @@ static TEST_COUNT: AtomicUsize = AtomicUsize::new(0); const TEST_NAMES: &[&str] = &[ "test_todos_command", "test_todos_help_command", - "test_todos_view_command" + "test_todos_view_command", + "test_todos_resume_command", + "test_todos_delete_command" ]; #[allow(dead_code)] const TOTAL_TESTS: usize = TEST_NAMES.len(); @@ -72,6 +74,8 @@ fn test_todos_command() -> Result<(), Box> { assert!(response.contains("delete"), "Missing delete command"); assert!(response.contains("help"), "Missing help command"); println!("โœ… Found core commands: resume, view, delete, help"); + + println!("โœ… /todos command test completed successfully"); // Release the lock before cleanup drop(chat); @@ -108,6 +112,8 @@ fn test_todos_help_command() -> Result<(), Box> { assert!(response.contains("delete"), "Missing delete command"); assert!(response.contains("help"), "Missing help command"); println!("โœ… Found core commands: resume, view, delete, help"); + + println!("โœ… /todos help command test completed successfully"); // Release the lock before cleanup drop(chat); @@ -141,7 +147,223 @@ fn test_todos_view_command() -> Result<(), Box> { println!("โœ… Q Chat session started"); - let response = chat.execute_command("Add task in todos list: I have to update the timecard ")?; + let response = chat.execute_command("Add task in todos list Review emails")?; + + 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("Review emails"), "Missing Review emails 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("TODO"), "Missing TODO message"); + assert!(confirm_response.contains("Review emails"), "Missing Review emails to-do item"); + println!("โœ… Confirmed viewing of selected to-do list with items"); + + let response = chat.execute_command("/todos delete")?; + + 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("delete"), "Missing delete message"); + + // 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("Deleted"), "Missing Deleted message"); + assert!(confirm_response.contains("to-do"), "Missing to-do item"); + println!("โœ… Confirmed deletion of selected to-do list"); + + println!("โœ… /todos view command 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 = "todos", feature = "sanity"))] +fn test_todos_resume_command() -> Result<(), Box> { + println!("\n๐Ÿ” Testing /todos resume command... | Description: Tests the /todos resume command to resume a specific to-do list"); + + 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 Review emails")?; + + 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("Review emails"), "Missing Review emails message"); + println!("โœ… Confirmed todo_list tool usage"); + + let response = chat.execute_command("/todos resume")?; + + 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("resume"), "Missing resume message"); + println!("โœ… Confirmed to-do item presence in resume 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("Review emails"), "Missing Review emails message"); + assert!(confirm_response.contains("TODO"), "Missing TODO item"); + println!("โœ… Confirmed resuming of selected to-do list with items"); + + let response = chat.execute_command("/todos delete")?; + + 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("delete"), "Missing delete message"); + + // 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("Deleted"), "Missing Deleted message"); + assert!(confirm_response.contains("to-do"), "Missing to-do item"); + println!("โœ… Confirmed deletion of selected to-do list"); + + println!("โœ… /todos resume command 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 = "todos", feature = "sanity"))] +fn test_todos_delete_command() -> Result<(), Box> { + println!("\n๐Ÿ” Testing /todos delete command... | Description: Tests the /todos delete command to delete a specific to-do list"); + + 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 Review emails")?; println!("๐Ÿ“ Help response: {} bytes", response.len()); println!("๐Ÿ“ FULL OUTPUT:"); @@ -151,7 +373,7 @@ fn test_todos_view_command() -> Result<(), Box> { // 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"); + assert!(response.contains("Review emails"), "Missing Review emails message"); println!("โœ… Confirmed todo_list tool usage"); let response = chat.execute_command("/todos view")?; @@ -181,9 +403,42 @@ fn test_todos_view_command() -> Result<(), Box> { 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"); + assert!(confirm_response.contains("TODO"), "Missing TODO message"); + assert!(confirm_response.contains("Review emails"), "Missing Review emails to-do item"); println!("โœ… Confirmed viewing of selected to-do list with items"); + + let response = chat.execute_command("/todos delete")?; + + 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("delete"), "Missing delete message"); + println!("โœ… Confirmed to-do item presence in delete 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("Deleted"), "Missing Deleted message"); + assert!(confirm_response.contains("to-do"), "Missing to-do item"); + println!("โœ… Confirmed deletion of selected to-do list"); + + println!("โœ… /todos delete command test completed successfully"); // Release the lock before cleanup drop(chat); diff --git a/e2etests/tests/tools/test_tools_command.rs b/e2etests/tests/tools/test_tools_command.rs index 39281bc3f2..a8cbc1475c 100644 --- a/e2etests/tests/tools/test_tools_command.rs +++ b/e2etests/tests/tools/test_tools_command.rs @@ -77,7 +77,7 @@ fn test_tools_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools")?; @@ -132,7 +132,7 @@ fn test_tools_help_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools --help")?; @@ -178,7 +178,7 @@ fn test_tools_trust_all_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Execute trust-all command let trust_all_response = chat.execute_command("/tools trust-all")?; @@ -258,7 +258,7 @@ fn test_tools_trust_all_help_command() -> Result<(), Box> 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools trust-all --help")?; @@ -294,7 +294,7 @@ fn test_tools_reset_help_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools reset --help")?; @@ -329,7 +329,7 @@ fn test_tools_trust_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // First get list of tools to find one that's not trusted let tools_response = chat.execute_command("/tools")?; @@ -407,7 +407,7 @@ fn test_tools_trust_help_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools trust --help")?; @@ -446,7 +446,7 @@ fn test_tools_untrust_help_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools untrust --help")?; @@ -485,7 +485,7 @@ fn test_tools_schema_help_command() -> Result<(), Box> { 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools schema --help")?; @@ -520,8 +520,7 @@ fn test_tools_schema_command() -> Result<(), Box> { println!("\n๐Ÿ” Testing /tools schema command..."); let session = get_chat_session(); - let mut chat = session.lock().unwrap(); - + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); let response = chat.execute_command("/tools schema")?; println!("๐Ÿ“ Tools schema response: {} bytes", response.len()); @@ -580,7 +579,7 @@ fn test_fs_write_and_fs_read_tools() -> Result<(), Box> { let _cleanup = FileCleanup { path: save_path }; let session = get_chat_session(); - let mut chat = session.lock().unwrap(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // 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))?; @@ -651,7 +650,7 @@ 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Test execute_bash tool by asking to run pwd command let response = chat.execute_command("Run pwd")?; @@ -690,7 +689,7 @@ 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // Test report_issue tool by asking to report an issue let response = chat.execute_command("Report an issue: 'File creation not working properly'")?; @@ -725,7 +724,7 @@ 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(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // 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")?; @@ -760,7 +759,7 @@ fn test_trust_execute_bash_for_direct_execution() -> Result<(), Boxexecute_bash tool so it runs commands without asking for user confirmation each time"); let session = get_chat_session(); - let mut chat = session.lock().unwrap(); + let mut chat = session.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); // First, trust the execute_bash tool let trust_response = chat.execute_command("/tools trust execute_bash")?;