diff --git a/src/core/src/processor/executor.rs b/src/core/src/processor/executor.rs index f50386ac..e166b5be 100644 --- a/src/core/src/processor/executor.rs +++ b/src/core/src/processor/executor.rs @@ -1,18 +1,12 @@ use super::formatter::format_request_name; +use super::incremental_loop::{RequestReporter, SyncSleep, block_on, run_requests}; use super::output; -use crate::request_substitution::{ - substitute_functions_in_request, substitute_request_variables_in_request, -}; -use crate::assertions; use crate::colors; -use crate::conditions; use crate::logging::Log; use crate::parser; use crate::redaction::{sanitize_request_for_output, sanitize_result_for_output}; use crate::runner; -use crate::types::{ - HttpFileResults, HttpRequest, HttpResult, ProcessorResults, RequestContext, -}; +use crate::types::{HttpFileResults, HttpRequest, HttpResult, ProcessorResults}; use anyhow::Result; pub struct ProcessorConfig<'a> { @@ -90,173 +84,177 @@ impl<'a> ProcessorConfig<'a> { } } -fn get_context_name(request: &HttpRequest, request_count: u32) -> String { - request - .name - .clone() - .unwrap_or_else(|| format!("request_{}", request_count)) +/// Reporter adapter for the CLI batch path: logs each outcome (reusing the +/// `output` helpers) and aggregates pass/fail/skip counts. Fail-fast is signalled +/// by returning `false` and recorded in `halted`. +struct BatchReporter<'a, 'b> { + config: &'a ProcessorConfig<'b>, + log: &'a mut Log, + counters: output::RequestCounters, + halted: bool, } -fn add_skipped_request_context( - request_contexts: &mut Vec, - processed_request: HttpRequest, - request_count: u32, -) { - let context_name = get_context_name(&processed_request, request_count); - request_contexts.push(RequestContext { - name: context_name, - request: processed_request, - result: None, - }); -} - -fn add_request_context_with_result( - request_contexts: &mut Vec, - processed_request: HttpRequest, - result: Option, - request_count: u32, -) { - let context_name = get_context_name(&processed_request, request_count); - request_contexts.push(RequestContext { - name: context_name, - request: processed_request, - result, - }); +impl<'a, 'b> BatchReporter<'a, 'b> { + fn new(config: &'a ProcessorConfig<'b>, log: &'a mut Log) -> Self { + Self { + config, + log, + counters: output::RequestCounters::new(), + halted: false, + } + } } -fn should_skip_due_to_dependency( - processed_request: &HttpRequest, - request_contexts: &[RequestContext], - log: &mut Log, -) -> bool { - if let Some(dep_name) = processed_request.depends_on.as_ref() - && !conditions::check_dependency(&Some(dep_name.clone()), request_contexts) - { - let name_str = format_request_name(&processed_request.name); +impl RequestReporter for BatchReporter<'_, '_> { + fn request_started(&mut self, _idx: usize, _total: usize, request: &HttpRequest) { + if self.config.verbose { + let sanitized_request = + sanitize_request_for_output(request, self.config.include_secrets); + output::log_request_details(&sanitized_request, self.log, self.config.pretty_json); + } + } - log.writeln(&format!( + fn dependency_skipped( + &mut self, + _idx: usize, + _total: usize, + request: &HttpRequest, + dep_name: &str, + ) -> bool { + self.counters.record_skip(); + let sanitized_request = sanitize_request_for_output(request, self.config.include_secrets); + let name_str = format_request_name(&sanitized_request.name); + self.log.writeln(&format!( "{} {} {} {} - Skipped: dependency '{}' not met (must succeed with HTTP 2xx)", colors::yellow("⏭️"), name_str, - processed_request.method, - processed_request.url, + sanitized_request.method, + sanitized_request.url, dep_name )); - - return true; + true } - false -} -fn should_skip_due_to_conditions( - processed_request: &HttpRequest, - request_contexts: &[RequestContext], - log: &mut Log, - verbose: bool, -) -> bool { - if processed_request.conditions.is_empty() { - return false; + fn conditions_skipped(&mut self, _idx: usize, _total: usize, request: &HttpRequest) -> bool { + self.counters.record_skip(); + let sanitized_request = sanitize_request_for_output(request, self.config.include_secrets); + output::log_conditions_not_met(&sanitized_request, self.log); + true } - if verbose { - match output::log_condition_evaluation_verbose(processed_request, request_contexts, log) { - Ok(conditions_met) => !conditions_met, - Err(e) => { - output::log_condition_error(processed_request, &e, log); - true - } - } - } else { - match conditions::evaluate_conditions(&processed_request.conditions, request_contexts) { - Ok(conditions_met) => { - if !conditions_met { - output::log_conditions_not_met(processed_request, log); - } - !conditions_met - } - Err(e) => { - output::log_condition_error(processed_request, &e, log); - true - } + fn condition_error( + &mut self, + _idx: usize, + _total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool { + // A condition that errors (rather than evaluating false) is a failure, not + // a benign skip — this mirrors the `Failed` event the UI path emits. + self.counters.record_failure(); + let sanitized_request = sanitize_request_for_output(request, self.config.include_secrets); + output::log_condition_error(&sanitized_request, error, self.log); + if self.config.fail_fast { + self.halted = true; + return false; } - } -} - -enum RequestProcessResult { - Skipped, - ExecutionError, - Completed(HttpResult), -} - -fn process_single_request( - request: HttpRequest, - request_contexts: &[RequestContext], - config: &ProcessorConfig, - executor: &F, - log: &mut Log, -) -> Result<(RequestProcessResult, HttpRequest)> -where - F: Fn(&HttpRequest, bool, bool) -> Result, -{ - let mut processed_request = request; - - // Check dependencies - if should_skip_due_to_dependency(&processed_request, request_contexts, log) { - return Ok((RequestProcessResult::Skipped, processed_request)); + true } - // Check conditions - if should_skip_due_to_conditions(&processed_request, request_contexts, log, config.verbose) { - return Ok((RequestProcessResult::Skipped, processed_request)); + fn substitution_error( + &mut self, + _idx: usize, + _total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool { + self.counters.record_failure(); + let sanitized_request = sanitize_request_for_output(request, self.config.include_secrets); + let name_str = format_request_name(&sanitized_request.name); + self.log.writeln(&format!( + "{} {} {} {} - Internal error: {}", + colors::red("❌"), + name_str, + sanitized_request.method, + sanitized_request.url, + error + )); + if self.config.fail_fast { + self.halted = true; + return false; + } + true } - // Apply substitutions - substitute_request_variables_in_request(&mut processed_request, request_contexts)?; - substitute_functions_in_request(&mut processed_request)?; + fn executed( + &mut self, + _idx: usize, + _total: usize, + request: &HttpRequest, + result: &HttpResult, + ) -> bool { + if result.success { + self.counters.record_success(); + } else { + self.counters.record_failure(); + } - // Apply pre-delay if specified - if let Some(pre_delay) = processed_request.pre_delay_ms { - std::thread::sleep(std::time::Duration::from_millis(pre_delay)); - } + let sanitized_request = sanitize_request_for_output(request, self.config.include_secrets); + output::log_execution_result(result, &sanitized_request, self.log); - // Log request details if verbose - if config.verbose { - let sanitized_request = - sanitize_request_for_output(&processed_request, config.include_secrets); - output::log_request_details(&sanitized_request, log, config.pretty_json); - } - - // Execute the request. When fail_fast is enabled we force full response - // capture for every request (config.verbose || config.fail_fast) so the - // failed request always has body/headers available, even though we only - // print verbose detail for the failing request. - let result = match executor( - &processed_request, - config.verbose || config.fail_fast, - config.insecure, - ) { - Ok(mut result) => { - if !processed_request.assertions.is_empty() { - let assertion_results = - assertions::evaluate_assertions(&processed_request.assertions, &result); - let all_passed = assertion_results.iter().all(|r| r.passed); - result.success = all_passed; - result.assertion_results = assertion_results; - } - Ok((RequestProcessResult::Completed(result), processed_request)) + if self.config.verbose { + let sanitized_result = sanitize_result_for_output(result, self.config.include_secrets); + output::log_response_details(&sanitized_result, self.log, self.config.pretty_json); } - Err(e) => { - output::log_execution_error(&processed_request, &e, log, config.include_secrets); - Ok((RequestProcessResult::ExecutionError, processed_request)) + + if !request.assertions.is_empty() { + let sanitized_result = sanitize_result_for_output(result, self.config.include_secrets); + output::log_assertion_results(&sanitized_result, self.log); } - }; - // Apply post-delay if specified (even if request failed) - if let Some(post_delay) = result.as_ref().ok().and_then(|(_, req)| req.post_delay_ms) { - std::thread::sleep(std::time::Duration::from_millis(post_delay)); + let failed = !result.success; + + output::log_fail_fast_verbose( + &sanitized_request, + Some(result), + self.config.include_secrets, + self.config.pretty_json, + self.config.fail_fast, + self.config.verbose, + self.log, + ); + + if failed && self.config.fail_fast { + self.halted = true; + return false; + } + true } - result + fn execution_error( + &mut self, + _idx: usize, + _total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool { + self.counters.record_failure(); + output::log_execution_error(request, error, self.log, self.config.include_secrets); + output::log_fail_fast_verbose( + request, + None, + self.config.include_secrets, + self.config.pretty_json, + self.config.fail_fast, + self.config.verbose, + self.log, + ); + if self.config.fail_fast { + self.halted = true; + return false; + } + true + } } fn process_single_file( @@ -288,117 +286,27 @@ where log.writeln(&format!("Found {} HTTP request(s)\n", requests.len())); - let mut counters = output::RequestCounters::new(); - let mut request_contexts: Vec = Vec::new(); - let mut halted = false; - - for request in requests { - counters.increment_total(); - - // Apply delay between requests (not before first request) - if counters.total > 1 && config.delay_ms > 0 { - std::thread::sleep(std::time::Duration::from_millis(config.delay_ms)); - } - - let (result, processed_request) = - match process_single_request(request, &request_contexts, config, executor, log) { - Ok((result, req)) => (result, req), - Err(e) => { - log.writeln(&format!("{} Internal error: {}", colors::red("❌"), e)); - counters.record_failure(); - if config.fail_fast { - halted = true; - break; - } - continue; - } - }; - - match result { - RequestProcessResult::Skipped => { - add_skipped_request_context( - &mut request_contexts, - processed_request, - counters.total, - ); - counters.record_skip(); - } - RequestProcessResult::ExecutionError => { - counters.record_failure(); - - output::log_fail_fast_verbose( - &processed_request, - None, - config.include_secrets, - config.pretty_json, - config.fail_fast, - config.verbose, - log, - ); - - add_request_context_with_result( - &mut request_contexts, - processed_request, - None, - counters.total, - ); - - if config.fail_fast { - halted = true; - break; - } - } - RequestProcessResult::Completed(http_result) => { - if http_result.success { - counters.record_success(); - } else { - counters.record_failure(); - } - - let sanitized_request = - sanitize_request_for_output(&processed_request, config.include_secrets); - output::log_execution_result(&http_result, &sanitized_request, log); - - // Log verbose details - if config.verbose { - let sanitized_result = - sanitize_result_for_output(&http_result, config.include_secrets); - output::log_response_details(&sanitized_result, log, config.pretty_json); - } + // When fail_fast is enabled we force full response capture for every request + // (verbose || fail_fast) so the failed request always has body/headers + // available, even though we only print verbose detail for the failing request. + let capture = config.verbose || config.fail_fast; + let wrapped = move |request: HttpRequest, _verbose: bool, insecure: bool| { + async move { executor(&request, capture, insecure) } + }; - // Log assertion results - if !processed_request.assertions.is_empty() { - let sanitized_result = - sanitize_result_for_output(&http_result, config.include_secrets); - output::log_assertion_results(&sanitized_result, log); - } + let mut reporter = BatchReporter::new(config, log); + let result_contexts = block_on(run_requests( + &mut reporter, + requests, + config.insecure, + config.delay_ms, + &wrapped, + SyncSleep, + ))?; - let failed = !http_result.success; - - output::log_fail_fast_verbose( - &sanitized_request, - Some(&http_result), - config.include_secrets, - config.pretty_json, - config.fail_fast, - config.verbose, - log, - ); - - add_request_context_with_result( - &mut request_contexts, - processed_request, - Some(http_result), - counters.total, - ); - - if failed && config.fail_fast { - halted = true; - break; - } - } - } - } + let BatchReporter { + counters, halted, .. + } = reporter; // Suppress the per-file summary when halting due to fail-fast so the output // ends on the failed request's detail. @@ -412,7 +320,7 @@ where success_count: counters.success, failed_count: counters.failed, skipped_count: counters.skipped, - result_contexts: request_contexts, + result_contexts, }, halted, )) @@ -453,7 +361,6 @@ where success: file_results.success_count, failed: file_results.failed_count, skipped: file_results.skipped_count, - total: 0, // Not used in add_file_results }); http_file_results.push(file_results); if file_halted { diff --git a/src/core/src/processor/executor_tests.rs b/src/core/src/processor/executor_tests.rs index 05478364..69a82419 100644 --- a/src/core/src/processor/executor_tests.rs +++ b/src/core/src/processor/executor_tests.rs @@ -696,6 +696,62 @@ Content-Type: application/json let _ = fs::remove_file(&log_path); } + #[test] + fn test_skipped_request_log_redacts_url_secrets_by_default() { + use std::fs; + use std::time::{SystemTime, UNIX_EPOCH}; + + // `first` fails (HTTP 500) so `second` is skipped for an unmet dependency. + // The skipped request's URL carries a secret query param that must be redacted. + let file_content = "# @name first\nGET https://api.example.com/first\n\n###\n\n# @name second\n# @dependsOn first\nGET https://api.example.com/second?token=topsecret\n"; + let temp_file = create_temp_http_file(file_content); + let files = vec![temp_file.path().to_str().unwrap().to_string()]; + let log_base = format!( + "test_log_skip_redaction_{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + + let config = ProcessorConfig::new(&files) + .with_log_filename(Some(&log_base)) + .with_silent(true); + + let failed_first = HttpResult { + request_name: Some("first".to_string()), + status_code: 500, + success: false, + error_message: None, + duration_ms: 1, + response_headers: None, + response_body: None, + assertion_results: Vec::new(), + }; + let mock = MockHttpExecutor::new(vec![failed_first]); + let result = process_http_files(&config, &|req, v, i| mock.execute(req, v, i)); + assert!(result.is_ok()); + + let entries = fs::read_dir(".").unwrap(); + let log_files: Vec<_> = entries + .filter_map(|e| e.ok()) + .filter(|e| { + e.file_name().to_string_lossy().starts_with(&log_base) + && e.file_name().to_string_lossy().ends_with(".log") + }) + .collect(); + + assert_eq!(log_files.len(), 1, "Expected exactly one log file"); + + let log_path = log_files[0].path(); + let log_content = fs::read_to_string(&log_path).unwrap(); + assert!(log_content.contains("Skipped: dependency 'first' not met")); + assert!(log_content.contains("token=***REDACTED***")); + assert!(!log_content.contains("topsecret")); + + let _ = fs::remove_file(&log_path); + } + #[test] fn test_insecure_flag_writes_warning_to_log() { use std::fs; diff --git a/src/core/src/processor/incremental_loop.rs b/src/core/src/processor/incremental_loop.rs index 8f312226..49111fa4 100644 --- a/src/core/src/processor/incremental_loop.rs +++ b/src/core/src/processor/incremental_loop.rs @@ -34,6 +34,170 @@ pub enum RequestProcessingResult { Failed { request: HttpRequest, error: String }, } +/// Observes each step of request processing and decides whether to continue. +/// +/// `run_requests` calls these methods at each decision point in the single +/// orchestration loop. Each method returns `true` to continue or `false` to +/// halt (fail-fast). Two adapters implement it: [`CallbackReporter`] (UI/event +/// streaming) and the CLI's batch reporter (logging + aggregation). +pub(crate) trait RequestReporter { + /// Called immediately before a request is executed (after substitution). + fn request_started(&mut self, _idx: usize, _total: usize, _request: &HttpRequest) {} + fn dependency_skipped( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + dep_name: &str, + ) -> bool; + fn conditions_skipped(&mut self, idx: usize, total: usize, request: &HttpRequest) -> bool; + fn condition_error( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool; + fn substitution_error( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool; + fn executed( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + result: &HttpResult, + ) -> bool; + fn execution_error( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool; +} + +/// Adapter that turns the `FnMut(idx, total, RequestProcessingResult) -> bool` +/// callback into a [`RequestReporter`]. Reproduces the events the loop has +/// historically emitted, so UI streaming and incremental tests are unaffected. +pub(crate) struct CallbackReporter { + callback: F, +} + +impl CallbackReporter +where + F: FnMut(usize, usize, RequestProcessingResult) -> bool, +{ + pub(crate) fn new(callback: F) -> Self { + Self { callback } + } +} + +impl RequestReporter for CallbackReporter +where + F: FnMut(usize, usize, RequestProcessingResult) -> bool, +{ + fn dependency_skipped( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + dep_name: &str, + ) -> bool { + (self.callback)( + idx, + total, + RequestProcessingResult::Skipped { + request: request.clone(), + reason: format!("Dependency on '{}' not met", dep_name), + }, + ) + } + + fn conditions_skipped(&mut self, idx: usize, total: usize, request: &HttpRequest) -> bool { + (self.callback)( + idx, + total, + RequestProcessingResult::Skipped { + request: request.clone(), + reason: "Conditions not met".to_string(), + }, + ) + } + + fn condition_error( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool { + (self.callback)( + idx, + total, + RequestProcessingResult::Failed { + request: request.clone(), + error: format!("Condition evaluation error: {}", error), + }, + ) + } + + fn substitution_error( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool { + (self.callback)( + idx, + total, + RequestProcessingResult::Failed { + request: request.clone(), + error: format!("Substitution error: {}", error), + }, + ) + } + + fn executed( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + result: &HttpResult, + ) -> bool { + (self.callback)( + idx, + total, + RequestProcessingResult::Executed { + request: request.clone(), + result: result.clone(), + }, + ) + } + + fn execution_error( + &mut self, + idx: usize, + total: usize, + request: &HttpRequest, + error: &anyhow::Error, + ) -> bool { + (self.callback)( + idx, + total, + RequestProcessingResult::Failed { + request: request.clone(), + error: error.to_string(), + }, + ) + } +} + /// Abstraction over sleep mechanisms for sync and async paths. pub trait Sleep { async fn sleep(&self, duration: Duration); @@ -171,7 +335,7 @@ pub async fn process_requests_incremental( requests: Vec, insecure: bool, delay_ms: u64, - mut callback: F, + callback: F, executor: &impl Fn(HttpRequest, bool, bool) -> Fut, sleep: S, ) -> Result<()> @@ -179,15 +343,40 @@ where F: FnMut(usize, usize, RequestProcessingResult) -> bool, Fut: Future>, S: Sleep, +{ + let mut reporter = CallbackReporter::new(callback); + run_requests(&mut reporter, requests, insecure, delay_ms, executor, sleep).await?; + Ok(()) +} + +/// The single request-processing orchestration: dependency checking, condition +/// evaluation, variable/function substitution, pre/post delays, execution and +/// assertions. Outcomes are reported through `reporter`, which also controls +/// fail-fast (returning `false` halts the loop). Returns the accumulated request +/// contexts so callers can aggregate per-file results. +/// +/// The executor is called with an owned `HttpRequest` (the loop clones it before +/// dispatching), so the original remains available for reporting and context tracking. +pub(crate) async fn run_requests( + reporter: &mut R, + requests: Vec, + insecure: bool, + delay_ms: u64, + executor: &impl Fn(HttpRequest, bool, bool) -> Fut, + sleep: S, +) -> Result> +where + R: RequestReporter, + Fut: Future>, + S: Sleep, { let total = requests.len(); + let mut request_contexts: Vec = Vec::new(); if requests.is_empty() { - return Ok(()); + return Ok(request_contexts); } - let mut request_contexts: Vec = Vec::new(); - for (idx, mut request) in requests.into_iter().enumerate() { let request_count = (idx + 1) as u32; @@ -195,17 +384,10 @@ where sleep.sleep(Duration::from_millis(delay_ms)).await; } - if let Some(dep_name) = request.depends_on.as_ref() + if let Some(dep_name) = request.depends_on.clone() && !conditions::check_dependency(&Some(dep_name.clone()), &request_contexts) { - let should_continue = callback( - idx, - total, - RequestProcessingResult::Skipped { - request: request.clone(), - reason: format!("Dependency on '{}' not met", dep_name), - }, - ); + let should_continue = reporter.dependency_skipped(idx, total, &request, &dep_name); add_request_context(&mut request_contexts, request, None, request_count); if !should_continue { break; @@ -217,14 +399,7 @@ where match conditions::evaluate_conditions(&request.conditions, &request_contexts) { Ok(true) => {} Ok(false) => { - let should_continue = callback( - idx, - total, - RequestProcessingResult::Skipped { - request: request.clone(), - reason: "Conditions not met".to_string(), - }, - ); + let should_continue = reporter.conditions_skipped(idx, total, &request); add_request_context(&mut request_contexts, request, None, request_count); if !should_continue { break; @@ -232,14 +407,7 @@ where continue; } Err(error) => { - let should_continue = callback( - idx, - total, - RequestProcessingResult::Failed { - request: request.clone(), - error: format!("Condition evaluation error: {}", error), - }, - ); + let should_continue = reporter.condition_error(idx, total, &request, &error); add_request_context(&mut request_contexts, request, None, request_count); if !should_continue { break; @@ -251,14 +419,7 @@ where if let Err(error) = substitute_request_variables_in_request(&mut request, &request_contexts) { - let should_continue = callback( - idx, - total, - RequestProcessingResult::Failed { - request: request.clone(), - error: format!("Variable substitution error: {}", error), - }, - ); + let should_continue = reporter.substitution_error(idx, total, &request, &error); add_request_context(&mut request_contexts, request, None, request_count); if !should_continue { break; @@ -267,14 +428,7 @@ where } if let Err(error) = substitute_functions_in_request(&mut request) { - let should_continue = callback( - idx, - total, - RequestProcessingResult::Failed { - request: request.clone(), - error: format!("Function substitution error: {}", error), - }, - ); + let should_continue = reporter.substitution_error(idx, total, &request, &error); add_request_context(&mut request_contexts, request, None, request_count); if !should_continue { break; @@ -290,8 +444,10 @@ where let post_delay_ms = request.post_delay_ms; + reporter.request_started(idx, total, &request); + // Clone the request for the executor so the original remains available - // for the callback and context tracking. + // for reporting and context tracking. match executor(request.clone(), false, insecure).await { Ok(mut result) => { if !request.assertions.is_empty() { @@ -301,30 +457,14 @@ where result.success = all_passed; result.assertion_results = assertion_results; } - add_request_context( - &mut request_contexts, - request.clone(), - Some(result.clone()), - request_count, - ); - let should_continue = callback( - idx, - total, - RequestProcessingResult::Executed { request, result }, - ); + let should_continue = reporter.executed(idx, total, &request, &result); + add_request_context(&mut request_contexts, request, Some(result), request_count); if !should_continue { break; } } Err(error) => { - let should_continue = callback( - idx, - total, - RequestProcessingResult::Failed { - request: request.clone(), - error: error.to_string(), - }, - ); + let should_continue = reporter.execution_error(idx, total, &request, &error); add_request_context(&mut request_contexts, request, None, request_count); if !should_continue { break; @@ -339,7 +479,7 @@ where } } - Ok(()) + Ok(request_contexts) } /// Block on a future using a no-op waker. diff --git a/src/core/src/processor/output.rs b/src/core/src/processor/output.rs index d9c169b1..711c98f8 100644 --- a/src/core/src/processor/output.rs +++ b/src/core/src/processor/output.rs @@ -1,17 +1,13 @@ use super::formatter::{format_json_if_valid, format_request_name}; use crate::colors; -use crate::conditions; -use crate::conditions::ConditionEvaluationResult; use crate::logging::Log; use crate::redaction::{sanitize_request_for_output, sanitize_result_for_output}; -use crate::types::{AssertionType, Condition, HttpRequest, HttpResult}; -use anyhow::Result; +use crate::types::{AssertionType, HttpRequest, HttpResult}; pub(super) struct RequestCounters { pub success: u32, pub failed: u32, pub skipped: u32, - pub total: u32, } impl RequestCounters { @@ -20,14 +16,9 @@ impl RequestCounters { success: 0, failed: 0, skipped: 0, - total: 0, } } - pub fn increment_total(&mut self) { - self.total += 1; - } - pub fn record_success(&mut self) { self.success += 1; } @@ -248,90 +239,6 @@ pub fn log_execution_error( )); } -pub fn log_condition_evaluation_verbose( - processed_request: &HttpRequest, - request_contexts: &[crate::types::RequestContext], - log: &mut Log, -) -> Result { - let (conditions_met, evaluation_results) = - conditions::evaluate_conditions_verbose(&processed_request.conditions, request_contexts)?; - - log.writeln(&format!("\n{} Condition Evaluation:", colors::blue("🔍"))); - - for (condition, eval_result) in processed_request - .conditions - .iter() - .zip(evaluation_results.iter()) - { - log_single_condition_result(condition, eval_result, log); - } - - if !conditions_met { - let name_str = format_request_name(&processed_request.name); - log.writeln(&format!( - "\n{} {} {} {} - Skipped: conditions not met\n", - colors::yellow("⏭️"), - name_str, - processed_request.method, - processed_request.url - )); - } else { - log.writeln(""); - } - - Ok(conditions_met) -} - -fn log_single_condition_result( - condition: &Condition, - eval_result: &ConditionEvaluationResult, - log: &mut Log, -) { - let directive = if eval_result.negated { - "@if-not" - } else { - "@if" - }; - let request_ref = if condition.request_name.is_empty() { - "" - } else { - condition.request_name.as_str() - }; - - let (color_fn, status_icon): (fn(&str) -> String, &str) = if eval_result.condition_met { - (colors::green, "✅") - } else { - (colors::red, "❌") - }; - - log.writeln(&format!( - "{} {} {}: {}.response.{}", - color_fn(""), - status_icon, - directive, - request_ref, - eval_result.condition_type - )); - - let value_color = if eval_result.condition_met { - colors::green - } else { - colors::yellow - }; - - log.writeln(&format!( - "{} Expected: {} \"{}\"", - value_color(""), - if eval_result.negated { "!=" } else { "==" }, - eval_result.expected_value - )); - log.writeln(&format!( - "{} Actual: \"{}\"", - value_color(""), - eval_result.actual_value.as_deref().unwrap_or("") - )); -} - pub fn log_conditions_not_met(processed_request: &HttpRequest, log: &mut Log) { let name_str = format_request_name(&processed_request.name); log.writeln(&format!(