From 9149f0060873abcad94ee7ddeec0969bc254b716 Mon Sep 17 00:00:00 2001 From: Mitchell Wright Date: Tue, 13 Jan 2026 07:46:47 +0000 Subject: [PATCH 1/3] Added tests for viewing service --- tests/service/mod.rs | 3 +- tests/service/view_service_tests.rs | 71 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/service/view_service_tests.rs diff --git a/tests/service/mod.rs b/tests/service/mod.rs index f298669..95bb5a0 100644 --- a/tests/service/mod.rs +++ b/tests/service/mod.rs @@ -1,4 +1,5 @@ mod create_new_service_tests; mod delete_service_tests; mod edit_service_tests; -mod list_services_tests; \ No newline at end of file +mod list_services_tests; +mod view_service_tests; \ No newline at end of file diff --git a/tests/service/view_service_tests.rs b/tests/service/view_service_tests.rs new file mode 100644 index 0000000..2837c4f --- /dev/null +++ b/tests/service/view_service_tests.rs @@ -0,0 +1,71 @@ + +#[cfg(test)] +mod view_service_tests { + use crate::common::builders::{EndpointBuilder, EnvironmentBuilder, HtrsConfigBuilder, ServiceBuilder}; + use crate::common::test_helpers::{clear_config, setup}; + use assert_cmd::Command; + use predicates::boolean::PredicateBooleanExt; + use predicates::str::contains; + use std::error::Error; + + #[test] + fn given_view_service_command_with_unknown_service_then_should_error() -> Result<(), Box> { + let path = setup(None); + + Command::cargo_bin("htrs")? + .env("HTRS_CONFIG_PATH", &path) + .arg("view") + .arg("service") + .arg("unknown_service") + .assert() + .failure() + .stdout("No service could be found with name or alias `unknown_service`\n"); + + clear_config(&path); + Ok(()) + } + + #[test] + fn given_view_service_command_with_known_service_then_should_succeed() -> Result<(), Box> { + let config = HtrsConfigBuilder::new() + .with_service( + ServiceBuilder::new() + .with_name("foo_name") + .with_alias("foo_alias") + .with_environment( + EnvironmentBuilder::new() + .with_name("foo_environment") + .with_alias("foo_env_alias") + .with_host("foo.com") + ) + .with_endpoint( + EndpointBuilder::new() + .with_name("foo_endpoint") + .with_path("/my/{path_param}/path") + .with_query_param("required_param", true) + .with_query_param("optional_param", false) + ) + ) + .build(); + let path = setup(Some(config)); + + Command::cargo_bin("htrs")? + .env("HTRS_CONFIG_PATH", &path) + .arg("view") + .arg("service") + .arg("foo_name") + .assert() + .success() + .stdout( + contains("Name: foo_name") + .and(contains("Alias: foo_alias")) + .and(contains(" - foo_environment (foo_env_alias) ~ foo.com")) + .and(contains("foo_endpoint ~ /my/{path_param}/path")) + .and(contains(" - *required_param")) + .and(contains(" - optional_param")) + ); + + clear_config(&path); + Ok(()) + } +} From 65ce77584b0a195dd8feaeb7fa06341d220409fb Mon Sep 17 00:00:00 2001 From: Mitchell Wright Date: Tue, 13 Jan 2026 07:46:56 +0000 Subject: [PATCH 2/3] Added logic --- src/commands/view_command.rs | 8 +- .../view_command/view_service_command.rs | 80 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/commands/view_command/view_service_command.rs diff --git a/src/commands/view_command.rs b/src/commands/view_command.rs index ee68329..fdd5911 100644 --- a/src/commands/view_command.rs +++ b/src/commands/view_command.rs @@ -1,13 +1,16 @@ use crate::commands::view_command::view_preset_command::ViewPresetCommand; -use crate::commands::view_command::ViewCommand::Preset; +use crate::commands::view_command::view_service_command::ViewServiceCommand; +use crate::commands::view_command::ViewCommand::{Preset, Service}; use crate::config::current_config::HtrsConfig; use crate::outcomes::{HtrsAction, HtrsError}; use clap::{ArgMatches, Command}; mod view_preset_command; +mod view_service_command; pub enum ViewCommand { Preset(ViewPresetCommand), + Service(ViewServiceCommand), } impl ViewCommand { @@ -16,11 +19,13 @@ impl ViewCommand { .about("View an item") .arg_required_else_help(true) .subcommand(ViewPresetCommand::get_command()) + .subcommand(ViewServiceCommand::get_command()) } pub fn bind_from_matches(args: &ArgMatches) -> ViewCommand { match args.subcommand() { Some(("preset", preset_matches)) => Preset(ViewPresetCommand::bind_from_matches(preset_matches)), + Some(("service", service_matches)) => Service(ViewServiceCommand::bind_from_matches(service_matches)), _ => unreachable!(), } } @@ -28,6 +33,7 @@ impl ViewCommand { pub fn execute(&self, config: &HtrsConfig) -> Result { match self { Preset(command) => command.execute(config), + Service(command) => command.execute(config), } } } diff --git a/src/commands/view_command/view_service_command.rs b/src/commands/view_command/view_service_command.rs new file mode 100644 index 0000000..8b3d283 --- /dev/null +++ b/src/commands/view_command/view_service_command.rs @@ -0,0 +1,80 @@ +use crate::commands::bindings::MatchBinding; +use crate::config::current_config::{Endpoint, Environment, HtrsConfig}; +use crate::outcomes::HtrsAction::PrintDialogue; +use crate::outcomes::{HtrsAction, HtrsError}; +use clap::{Arg, ArgMatches, Command}; + +pub struct ViewServiceCommand { + pub name: String, +} + +impl ViewServiceCommand { + pub fn get_command() -> Command { + Command::new("service") + .arg_required_else_help(true) + .arg( + Arg::new("name") + .required(true) + .help("Name or alias of the service to view") + ) + } + + pub fn bind_from_matches(args: &ArgMatches) -> Self { + Self { + name: args.bind_field("name"), + } + } + + pub fn execute(&self, config: &HtrsConfig) -> Result { + let Some(service) = config.get_service(&self.name) else { + return Err(HtrsError::new(format!("No service could be found with name or alias `{}`", self.name).as_str())); + }; + + let mut text = String::new(); + text.push_str(format!("Name: {}\n", service.name).as_str()); + if let Some(alias) = &service.alias { + text.push_str(format!(" Alias: {}\n", alias).as_str()); + } + text.push_str("Environments:\n"); + let environment_text = service.environments.iter() + .map(|e| Self::get_environment_str(e)) + .collect::>() + .join(""); + text.push_str(match environment_text.is_empty() { + true => " (no environments)\n", + false => environment_text.as_str() + }); + + text.push_str("Endpoints:\n"); + let endpoint_text = service.endpoints.iter() + .map(|e| Self::get_endpoint_string(e)) + .collect::>() + .join(""); + text.push_str(match endpoint_text.is_empty() { + true => " (no endpoints)\n", + false => endpoint_text.as_str(), + }); + + Ok(PrintDialogue(text)) + } + + fn get_environment_str(environment: &Environment) -> String { + match environment.alias { + Some(ref alias) => format!(" - {} ({}) ~ {}\n", environment.name, alias, environment.host), + None => format!(" - {} ~ {}\n", environment.name, environment.host) + } + } + + fn get_endpoint_string(endpoint: &Endpoint) -> String { + let mut text = String::new(); + text.push_str(format!(" - {} ~ {}\n", endpoint.name, endpoint.path_template).as_str()); + for param in &endpoint.query_parameters { + match param.required { + true => text.push_str(format!(" - *{}\n", param.name).as_str()), + false => text.push_str(format!(" - {}\n", param.name).as_str()), + }; + } + + text + } +} From 3eb8a80e11e208e58bf338c16a57fef8c8069055 Mon Sep 17 00:00:00 2001 From: Mitchell Wright Date: Tue, 13 Jan 2026 07:47:55 +0000 Subject: [PATCH 3/3] Removed redundant closure --- src/commands/view_command/view_service_command.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/view_command/view_service_command.rs b/src/commands/view_command/view_service_command.rs index 8b3d283..ad4fe86 100644 --- a/src/commands/view_command/view_service_command.rs +++ b/src/commands/view_command/view_service_command.rs @@ -37,7 +37,7 @@ impl ViewServiceCommand { } text.push_str("Environments:\n"); let environment_text = service.environments.iter() - .map(|e| Self::get_environment_str(e)) + .map(Self::get_environment_str) .collect::>() .join(""); text.push_str(match environment_text.is_empty() { @@ -47,7 +47,7 @@ impl ViewServiceCommand { text.push_str("Endpoints:\n"); let endpoint_text = service.endpoints.iter() - .map(|e| Self::get_endpoint_string(e)) + .map(Self::get_endpoint_string) .collect::>() .join(""); text.push_str(match endpoint_text.is_empty() {