This is an automated email from the ASF dual-hosted git repository.
hubcio pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new 0d4fa738a feat(cli): display topic partitions and options in topic get
(#4558)
0d4fa738a is described below
commit 0d4fa738a2e7b08c92738569ae64f56180afecca
Author: SteveParadox <[email protected]>
AuthorDate: Wed Oct 21 08:48:46 2026 +0100
feat(cli): display topic partitions and options in topic get (#4558)
---
core/cli/src/commands/binary_topics/get_topic.rs | 298 +++++++++++++++---
.../tests/cli/topic/test_topic_get_command.rs | 347 +++++++++++++++++++--
2 files changed, 586 insertions(+), 59 deletions(-)
diff --git a/core/cli/src/commands/binary_topics/get_topic.rs
b/core/cli/src/commands/binary_topics/get_topic.rs
index 5eb5df546..fe2d73bf9 100644
--- a/core/cli/src/commands/binary_topics/get_topic.rs
+++ b/core/cli/src/commands/binary_topics/get_topic.rs
@@ -18,10 +18,11 @@
use crate::commands::cli_command::{CliCommand, PRINT_TARGET};
use anyhow::Context;
use async_trait::async_trait;
-use comfy_table::Table;
+use comfy_table::{Table, presets::ASCII_NO_BORDERS};
use iggy_common::Client;
use iggy_common::Identifier;
use iggy_common::IggyExpiry;
+use iggy_common::TopicDetails;
use tracing::{Level, event};
pub struct GetTopicCmd {
@@ -64,44 +65,271 @@ impl CliCommand for GetTopicCmd {
}
let topic = topic.unwrap();
- let mut table = Table::new();
+ let table = topic_table(&topic);
- table.set_header(vec!["Property", "Value"]);
- table.add_row(vec!["Topic id", format!("{}", topic.id).as_str()]);
- table.add_row(vec![
+ event!(target: PRINT_TARGET, Level::INFO,"{table}");
+
+ Ok(())
+ }
+}
+
+fn topic_table(topic: &TopicDetails) -> Table {
+ let mut table = Table::new();
+
+ table.set_header(vec!["Property", "Value"]);
+ table.add_row(vec!["Topic id", format!("{}", topic.id).as_str()]);
+ table.add_row(vec![
+ "Created",
+ topic.created_at.to_utc_string("%Y-%m-%d %H:%M:%S").as_str(),
+ ]);
+ table.add_row(vec!["Topic name", topic.name.as_str()]);
+ table.add_row(vec!["Topic size", format!("{}", topic.size).as_str()]);
+ table.add_row(vec![
+ "Compression",
+ topic.compression_algorithm.to_string().as_str(),
+ ]);
+ table.add_row(vec![
+ "Message expiry",
+ match topic.message_expiry {
+ IggyExpiry::NeverExpire => String::from("unlimited"),
+ IggyExpiry::ServerDefault => String::from("server_default"),
+ IggyExpiry::ExpireDuration(value) => format!("{value}"),
+ }
+ .as_str(),
+ ]);
+ table.add_row(vec![
+ "Max topic size",
+ format!("{}", topic.max_topic_size).as_str(),
+ ]);
+ table.add_row(vec![
+ "Topic message count",
+ format!("{}", topic.messages_count).as_str(),
+ ]);
+ table.add_row(vec![
+ "Partitions count",
+ format!("{}", topic.partitions_count).as_str(),
+ ]);
+
+ if !topic.partitions.is_empty() {
+ let mut partitions_table = Table::new();
+ partitions_table.load_style(ASCII_NO_BORDERS);
+ partitions_table.set_header(vec![
+ "Partition id",
"Created",
- topic.created_at.to_utc_string("%Y-%m-%d %H:%M:%S").as_str(),
- ]);
- table.add_row(vec!["Topic name", topic.name.as_str()]);
- table.add_row(vec!["Topic size", format!("{}", topic.size).as_str()]);
- table.add_row(vec![
- "Compression",
- topic.compression_algorithm.to_string().as_str(),
- ]);
- table.add_row(vec![
- "Message expiry",
- match topic.message_expiry {
- IggyExpiry::NeverExpire => String::from("unlimited"),
- IggyExpiry::ServerDefault => String::from("server_default"),
- IggyExpiry::ExpireDuration(value) => format!("{value}"),
- }
- .as_str(),
- ]);
- table.add_row(vec![
- "Max topic size",
- format!("{}", topic.max_topic_size).as_str(),
- ]);
- table.add_row(vec![
- "Topic message count",
- format!("{}", topic.messages_count).as_str(),
- ]);
- table.add_row(vec![
- "Partitions count",
- format!("{}", topic.partitions_count).as_str(),
+ "Segments count",
+ "Current offset",
+ "Size",
+ "Messages count",
]);
- event!(target: PRINT_TARGET, Level::INFO,"{table}");
+ for partition in &topic.partitions {
+ partitions_table.add_row(vec![
+ partition.id.to_string(),
+ partition.created_at.to_utc_string("%Y-%m-%d %H:%M:%S"),
+ partition.segments_count.to_string(),
+ partition.current_offset.to_string(),
+ partition.size.to_string(),
+ partition.messages_count.to_string(),
+ ]);
+ }
+ table.add_row(vec!["Partitions",
partitions_table.to_string().as_str()]);
+ }
- Ok(())
+ if !topic.options.is_empty() {
+ let mut options_table = Table::new();
+ options_table.load_style(ASCII_NO_BORDERS);
+ options_table.set_header(vec!["Option", "Value", "Source"]);
+ for (key, option) in &topic.options {
+ options_table.add_row(vec![
+ String::from_utf8_lossy(key.as_bytes()).into_owned(),
+ option.value.to_string_value(),
+ if option.explicit {
+ "explicit"
+ } else {
+ "default"
+ }
+ .to_owned(),
+ ]);
+ }
+ table.add_row(vec!["Options", options_table.to_string().as_str()]);
+ }
+
+ table
+}
+
+#[cfg(test)]
+mod tests {
+ use super::topic_table;
+ use iggy_common::{
+ CompressionAlgorithm, HeaderKey, HeaderValue, IggyByteSize,
IggyExpiry, IggyTimestamp,
+ MaxTopicSize, OptionValue, Partition, PartitionContext,
ResourceOptions, TopicDetails,
+ };
+
+ fn topic() -> TopicDetails {
+ TopicDetails {
+ id: 7,
+ created_at: IggyTimestamp::from(0_u64),
+ name: "test-topic".to_owned(),
+ size: IggyByteSize::from(0_u64),
+ message_expiry: IggyExpiry::NeverExpire,
+ compression_algorithm: CompressionAlgorithm::None,
+ max_topic_size: MaxTopicSize::Unlimited,
+ messages_count: 0,
+ partitions_count: 0,
+ partitions: Vec::new(),
+ options: ResourceOptions::default(),
+ }
+ }
+
+ #[test]
+ fn empty_collections_keep_topic_properties_without_nested_tables() {
+ let output = topic_table(&topic()).to_string();
+ assert!(output.contains("Topic name | test-topic"));
+ assert!(output.contains("Partitions count | 0"));
+ assert!(!output.contains("Partition id"));
+ assert!(!output.contains("Segments count"));
+ assert!(!output.contains("Options"));
+ }
+
+ #[test]
+ fn partition_rows_keep_distinct_fields_and_large_offsets() {
+ let mut topic = topic();
+ topic.partitions_count = 2;
+ topic.partitions = vec![
+ Partition {
+ id: 9,
+ created_at: IggyTimestamp::from(0_u64),
+ segments_count: 3,
+ current_offset: u64::MAX,
+ size: IggyByteSize::from(1230_u64),
+ messages_count: 5,
+ context: PartitionContext::default(),
+ },
+ Partition {
+ id: 2,
+ created_at: IggyTimestamp::from(1_000_000_u64),
+ segments_count: 7,
+ current_offset: 11,
+ size: IggyByteSize::from(0_u64),
+ messages_count: 13,
+ context: PartitionContext::default(),
+ },
+ ];
+ let output = topic_table(&topic).to_string();
+ let rows = output
+ .lines()
+ .map(|line| line.split_whitespace().collect::<Vec<_>>().join(" "))
+ .collect::<Vec<_>>();
+ assert!(
+ rows.contains(&format!(
+ "| | 9 | 1970-01-01 00:00:00 | 3 | {} | 1.23 KB | 5 |",
+ u64::MAX
+ )),
+ "{output}"
+ );
+ assert!(rows.contains(&"| | 2 | 1970-01-01 00:00:01 | 7 | 11 | 0 B |
13 |".to_owned()));
+ }
+
+ #[test]
+ fn options_keep_typed_values_provenance_and_unknown_keys() {
+ let mut topic = topic();
+ let long_value = "long value / café: ".repeat(10);
+ topic.options = ResourceOptions::from([
+ (
+ HeaderKey::try_from("future_option").unwrap(),
+
OptionValue::explicit(HeaderValue::try_from(long_value.as_str()).unwrap()),
+ ),
+ (
+ HeaderKey::try_from("preallocate_segments").unwrap(),
+ OptionValue::derived(HeaderValue::from(false)),
+ ),
+ (
+ HeaderKey::try_from("messages_required_to_save").unwrap(),
+ OptionValue::explicit(HeaderValue::from(17_u32)),
+ ),
+ ]);
+ let output = topic_table(&topic).to_string();
+ let rows = output
+ .lines()
+ .map(|line| line.split_whitespace().collect::<Vec<_>>().join(" "))
+ .collect::<Vec<_>>();
+ assert!(
+ rows.contains(&"| Options | Option | Value | Source |".to_owned()),
+ "{output}"
+ );
+ assert!(rows.contains(&format!(
+ "| | future_option | {} | explicit |",
+ long_value.trim()
+ )));
+ assert!(rows.contains(&"| | preallocate_segments | false | default
|".to_owned()));
+ assert!(rows.contains(&"| | messages_required_to_save | 17 | explicit
|".to_owned()));
+ let option_lines = output
+ .lines()
+ .filter(|line| {
+ line.contains("future_option")
+ || line.contains("preallocate_segments")
+ || line.contains("messages_required_to_save")
+ })
+ .collect::<Vec<_>>();
+ assert_eq!(option_lines.len(), 3);
+ assert!(output.contains(&long_value));
+ assert!(option_lines.iter().all(|line| {
+ line.chars().filter(|&character| character == '|').count() == 5
+ && line.chars().count() == option_lines[0].chars().count()
+ }));
+ }
+
+ #[test]
+ fn resolved_options_keep_full_width_values_and_sources() {
+ let mut fixture = topic();
+ fixture.options = ResourceOptions::from([
+ (
+ HeaderKey::try_from("max_topic_size").unwrap(),
+ OptionValue::derived(HeaderValue::from(u64::MAX)),
+ ),
+ (
+
HeaderKey::try_from("size_of_messages_required_to_save").unwrap(),
+ OptionValue::derived(HeaderValue::from(1_048_576_u64)),
+ ),
+ (
+ HeaderKey::try_from("messages_required_to_save").unwrap(),
+ OptionValue::derived(HeaderValue::from(1024_u32)),
+ ),
+ (
+ HeaderKey::try_from("preallocate_segments").unwrap(),
+ OptionValue::explicit(HeaderValue::from(false)),
+ ),
+ (
+ HeaderKey::try_from("durability").unwrap(),
+
OptionValue::explicit(HeaderValue::try_from("persisted").unwrap()),
+ ),
+ (
+ HeaderKey::try_from("consumer_offset_durability").unwrap(),
+
OptionValue::explicit(HeaderValue::try_from("replicated").unwrap()),
+ ),
+ ]);
+ let output = topic_table(&fixture).to_string();
+ for expected in [
+ "Option | Value",
+ "preallocate_segments | false |
explicit",
+ "messages_required_to_save | 1024 |
default",
+ "| durability | persisted |
explicit",
+ "consumer_offset_durability | replicated |
explicit",
+ ] {
+ assert!(output.contains(expected), "missing {expected}: {output}");
+ }
+ let rows = output
+ .lines()
+ .map(|line| line.split_whitespace().collect::<Vec<_>>().join(" "))
+ .collect::<Vec<_>>();
+ for expected in [
+ "| | max_topic_size | 18446744073709551615 | default |",
+ "| | size_of_messages_required_to_save | 1048576 | default |",
+ ] {
+ assert!(
+ rows.iter().any(|row| row == expected),
+ "missing {expected}: {output}"
+ );
+ }
}
}
diff --git a/core/integration/tests/cli/topic/test_topic_get_command.rs
b/core/integration/tests/cli/topic/test_topic_get_command.rs
index 5715e9211..8cf572a2e 100644
--- a/core/integration/tests/cli/topic/test_topic_get_command.rs
+++ b/core/integration/tests/cli/topic/test_topic_get_command.rs
@@ -17,13 +17,18 @@
use crate::cli::common::{
CLAP_INDENT, IggyCmdCommand, IggyCmdTest, IggyCmdTestCase, TestHelpCmd,
TestStreamId,
- TestTopicId, USAGE_PREFIX,
+ TestTopicId, USAGE_PREFIX, clear_session_entry,
};
+use assert_cmd::Command;
use assert_cmd::assert::Assert;
use async_trait::async_trait;
use iggy::prelude::Client;
+use iggy::prelude::Durability;
use iggy::prelude::IggyExpiry;
-use iggy::prelude::TopicCreateOptions;
+use iggy::prelude::defaults::{DEFAULT_ROOT_PASSWORD, DEFAULT_ROOT_USERNAME};
+use iggy::prelude::{IggyMessage, Partitioning, StreamClient, TopicClient,
TopicCreateOptions};
+use integration::harness::{TestHarnessBuilder, TestServerConfig};
+use predicates::prelude::PredicateBooleanExt;
use predicates::str::{contains, starts_with};
use serial_test::parallel;
@@ -34,27 +39,46 @@ struct TestTopicGetCmd {
topic_name: String,
using_stream_id: TestStreamId,
using_topic_id: TestTopicId,
+ partitions_count: u32,
+ durability: Durability,
+ consumer_offset_durability: Durability,
+ partition_ids: Vec<u32>,
+ populate_partitions: bool,
}
impl TestTopicGetCmd {
fn new(
- stream_id: u32,
stream_name: String,
- topic_id: u32,
topic_name: String,
using_stream_id: TestStreamId,
using_topic_id: TestTopicId,
) -> Self {
Self {
- stream_id,
+ stream_id: 0,
stream_name,
- topic_id,
+ topic_id: 0,
topic_name,
using_stream_id,
using_topic_id,
+ partitions_count: 1,
+ durability: Durability::Replicated,
+ consumer_offset_durability: Durability::Replicated,
+ partition_ids: Vec::new(),
+ populate_partitions: false,
}
}
+ fn with_topic_options(mut self, partitions_count: u32, durability:
Durability) -> Self {
+ self.partitions_count = partitions_count;
+ self.durability = durability;
+ self.consumer_offset_durability = match durability {
+ Durability::Persisted => Durability::Replicated,
+ Durability::Replicated => Durability::Persisted,
+ };
+ self.populate_partitions = true;
+ self
+ }
+
fn to_args(&self) -> Vec<String> {
let mut command = match self.using_stream_id {
TestStreamId::Numeric => vec![format!("{}", self.stream_id)],
@@ -73,21 +97,52 @@ impl TestTopicGetCmd {
#[async_trait]
impl IggyCmdTestCase for TestTopicGetCmd {
async fn prepare_server_state(&mut self, client: &dyn Client) {
- let stream = client.create_stream(&self.stream_name).await;
- assert!(stream.is_ok());
+ let stream = client
+ .create_stream(&self.stream_name)
+ .await
+ .expect("stream should be created");
+ self.stream_id = stream.id;
let topic = client
.create_topic(
&self.stream_name.clone().try_into().unwrap(),
&self.topic_name,
&TopicCreateOptions {
- partitions_count: Some(1),
+ partitions_count: Some(self.partitions_count),
message_expiry: Some(IggyExpiry::NeverExpire),
+ durability: self.durability,
+ consumer_offset_durability:
self.consumer_offset_durability,
+ preallocate_segments: Some(false),
..TopicCreateOptions::default()
},
)
- .await;
- assert!(topic.is_ok());
+ .await
+ .expect("topic should be created");
+ self.topic_id = topic.id;
+
+ if self.populate_partitions {
+ for (index, partition) in topic.partitions.iter().enumerate() {
+ let mut messages = (0..=index)
+ .map(|_| "topic get test
payload".parse::<IggyMessage>().unwrap())
+ .collect::<Vec<_>>();
+ client
+ .send_messages(
+ &self.stream_id.try_into().unwrap(),
+ &self.topic_id.try_into().unwrap(),
+ &Partitioning::partition_id(partition.id),
+ &mut messages,
+ )
+ .await
+ .expect("messages should be sent");
+ }
+ }
+
+ self.partition_ids = topic
+ .partitions
+ .iter()
+ .map(|partition| partition.id)
+ .collect();
+ assert_eq!(self.partition_ids.len(), self.partitions_count as usize);
}
fn get_command(&self) -> IggyCmdCommand {
@@ -112,19 +167,84 @@ impl IggyCmdTestCase for TestTopicGetCmd {
let start_message =
format!("Executing get topic with ID: {topic_id} from stream with
ID: {stream_id}");
- command_state
+ let expected_messages_count = if self.populate_partitions {
+ (1..=u64::from(self.partitions_count)).sum::<u64>()
+ } else {
+ 0
+ };
+ let assertion = command_state
.success()
.stdout(starts_with(start_message))
- .stdout(contains("Topic id | 0"))
+ .stdout(contains(format!("Topic id | {}",
self.topic_id)))
.stdout(contains(format!(
"Topic name | {}",
self.topic_name
)))
- .stdout(contains("Topic size | 0"))
+ .stdout(contains("Topic size | "))
.stdout(contains("Message expiry | unlimited"))
.stdout(contains("Max topic size | unlimited"))
- .stdout(contains("Topic message count | 0"))
- .stdout(contains("Partitions count | 1"));
+ .stdout(contains(format!(
+ "Topic message count | {}",
+ expected_messages_count
+ )))
+ .stdout(contains(format!(
+ "Partitions count | {}",
+ self.partitions_count
+ )));
+
+ let mut assertion = assertion
+ .stdout(contains("Partition id | Created"))
+ .stdout(contains("Segments count | Current offset | Size"))
+ .stdout(contains("Messages count"))
+ .stdout(contains("Option | Value"))
+ .stdout(contains("Source"))
+ .stdout(contains(
+ "preallocate_segments | false |
explicit",
+ ))
+ .stdout(contains(
+ "messages_required_to_save | 1024 |
default",
+ ));
+ assertion = match self.durability {
+ Durability::Persisted => assertion.stdout(contains(
+ "| durability | persisted |
explicit",
+ )),
+ Durability::Replicated => assertion.stdout(contains(
+ "| durability | replicated |
explicit",
+ )),
+ };
+ assertion = match self.consumer_offset_durability {
+ Durability::Persisted => assertion.stdout(contains(
+ "consumer_offset_durability | persisted |
explicit",
+ )),
+ Durability::Replicated => assertion.stdout(contains(
+ "consumer_offset_durability | replicated |
explicit",
+ )),
+ };
+ if self.populate_partitions {
+ assertion = assertion.stdout(contains("Topic size | 0
B").not());
+ } else {
+ assertion = assertion.stdout(contains("Topic size | 0
B"));
+ }
+ let stdout = std::str::from_utf8(&assertion.get_output().stdout)
+ .expect("CLI output should be UTF-8");
+ let rows = stdout
+ .lines()
+ .map(|line| line.split_whitespace().collect::<Vec<_>>().join(" "))
+ .collect::<Vec<_>>();
+ for (index, partition_id) in self.partition_ids.iter().enumerate() {
+ let prefix = format!("| | {partition_id} |");
+ let row = rows
+ .iter()
+ .find(|line| line.starts_with(&prefix))
+ .expect("partition should have a row in CLI output");
+ if self.populate_partitions {
+ assert!(row.contains(&format!("| 1 | {index} |")), "{row}");
+ assert!(row.contains(" B |") && !row.contains("| 0 B |"),
"{row}");
+ assert!(row.ends_with(&format!("| {} |", index + 1)), "{row}");
+ } else {
+ assert!(row.contains("| 1 | 0 | 0 B | 0 |"), "{row}");
+ }
+ }
}
async fn verify_server_state(&self, client: &dyn Client) {
@@ -151,9 +271,7 @@ pub async fn should_be_successful() {
iggy_cmd_test.setup().await;
iggy_cmd_test
.execute_test(TestTopicGetCmd::new(
- 0,
String::from("main"),
- 0,
String::from("sync"),
TestStreamId::Named,
TestTopicId::Named,
@@ -161,9 +279,7 @@ pub async fn should_be_successful() {
.await;
iggy_cmd_test
.execute_test(TestTopicGetCmd::new(
- 1,
String::from("customer"),
- 3,
String::from("probe"),
TestStreamId::Named,
TestTopicId::Named,
@@ -171,9 +287,7 @@ pub async fn should_be_successful() {
.await;
iggy_cmd_test
.execute_test(TestTopicGetCmd::new(
- 1,
String::from("development"),
- 2,
String::from("testing"),
TestStreamId::Named,
TestTopicId::Named,
@@ -181,9 +295,7 @@ pub async fn should_be_successful() {
.await;
iggy_cmd_test
.execute_test(TestTopicGetCmd::new(
- 6,
String::from("other"),
- 1,
String::from("topic"),
TestStreamId::Named,
TestTopicId::Named,
@@ -191,6 +303,86 @@ pub async fn should_be_successful() {
.await;
}
+#[tokio::test]
+#[parallel]
+pub async fn should_show_multiple_partitions_and_persisted_durability() {
+ let mut iggy_cmd_test = IggyCmdTest::default();
+ iggy_cmd_test.setup().await;
+ iggy_cmd_test
+ .execute_test(
+ TestTopicGetCmd::new(
+ String::from("durable-stream"),
+ String::from("multi-partition-topic"),
+ TestStreamId::Named,
+ TestTopicId::Named,
+ )
+ .with_topic_options(3, Durability::Persisted),
+ )
+ .await;
+}
+
+#[tokio::test]
+#[parallel]
+pub async fn should_show_multiple_partitions_using_numeric_identifiers() {
+ let mut iggy_cmd_test = IggyCmdTest::default();
+ iggy_cmd_test.setup().await;
+ iggy_cmd_test
+ .execute_test(
+ TestTopicGetCmd::new(
+ String::from("numeric-stream"),
+ String::from("numeric-topic"),
+ TestStreamId::Numeric,
+ TestTopicId::Numeric,
+ )
+ .with_topic_options(3, Durability::Replicated),
+ )
+ .await;
+}
+
+struct TestMissingTopicGetCmd;
+
+#[async_trait]
+impl IggyCmdTestCase for TestMissingTopicGetCmd {
+ async fn prepare_server_state(&mut self, client: &dyn Client) {
+ client.create_stream("missing-topic-stream").await.unwrap();
+ }
+
+ fn get_command(&self) -> IggyCmdCommand {
+ IggyCmdCommand::new()
+ .args(vec![
+ "topic",
+ "get",
+ "missing-topic-stream",
+ "missing-topic",
+ ])
+ .with_env_credentials()
+ }
+
+ fn verify_command(&self, command_state: Assert) {
+ command_state
+ .success()
+ .stdout(contains(
+ "Topic with ID: missing-topic in stream missing-topic-stream
was not found",
+ ))
+ .stdout(predicates::str::contains("Partitions").not());
+ }
+
+ async fn verify_server_state(&self, client: &dyn Client) {
+ client
+ .delete_stream(&"missing-topic-stream".try_into().unwrap())
+ .await
+ .unwrap();
+ }
+}
+
+#[tokio::test]
+#[parallel]
+pub async fn should_report_missing_topic() {
+ let mut iggy_cmd_test = IggyCmdTest::default();
+ iggy_cmd_test.setup().await;
+ iggy_cmd_test.execute_test(TestMissingTopicGetCmd).await;
+}
+
#[tokio::test]
#[parallel]
pub async fn should_help_match() {
@@ -257,3 +449,110 @@ Options:
))
.await;
}
+
+#[tokio::test]
+#[parallel]
+#[ignore = "opt-in end-to-end test of the CLI persisted durability flag"]
+pub async fn should_show_persisted_durability_created_through_cli() {
+ let mut harness = TestHarnessBuilder::default()
+ .server(TestServerConfig::default())
+ .build()
+ .unwrap();
+ harness.start().await.expect("test server should start");
+ let client = harness.tcp_root_client().await.unwrap();
+ let stream = client.create_stream("cli-persisted-stream").await.unwrap();
+ let address = harness.server().raw_tcp_addr().unwrap();
+ let socket_address = harness.server().tcp_addr().unwrap();
+ clear_session_entry(socket_address);
+ let cli = || {
+ #[allow(deprecated)]
+ let mut command = Command::cargo_bin("iggy").unwrap();
+ command.env("IGGY_TOKEN", "").env("COLUMNS", "200").args([
+ "--tcp-server-address",
+ &address,
+ "--username",
+ DEFAULT_ROOT_USERNAME,
+ "--password",
+ DEFAULT_ROOT_PASSWORD,
+ ]);
+ command
+ };
+ cli()
+ .args([
+ "topic",
+ "create",
+ "cli-persisted-stream",
+ "cli-persisted-topic",
+ "3",
+ "none",
+ "--durability",
+ "persisted",
+ "--set",
+ "preallocate_segments=false",
+ ])
+ .assert()
+ .success();
+
+ let details = client
+ .get_topic(
+ &stream.id.try_into().unwrap(),
+ &"cli-persisted-topic".try_into().unwrap(),
+ )
+ .await
+ .unwrap()
+ .unwrap();
+ assert_eq!(details.partitions_count, 3);
+ assert_eq!(details.partitions.len(), 3);
+ let option = details
+ .options
+ .iter()
+ .find(|(key, _)| key.as_bytes() == b"durability")
+ .unwrap();
+ assert_eq!(option.1.value.to_string_value(), "persisted");
+
+ assert!(option.1.explicit);
+
+ let mut assertion = cli()
+ .args([
+ "topic",
+ "get",
+ "cli-persisted-stream",
+ "cli-persisted-topic",
+ ])
+ .assert()
+ .success()
+ .stdout(contains("Partitions count | 3"))
+ .stdout(contains("Partition id | Created"))
+ .stdout(contains("Segments count | Current offset | Size"))
+ .stdout(contains("Messages count"))
+ .stdout(contains("Source"))
+ .stdout(contains(
+ "| durability | persisted |
explicit",
+ ))
+ .stdout(contains(
+ "consumer_offset_durability | replicated |
explicit",
+ ))
+ .stdout(contains(
+ "preallocate_segments | false |
explicit",
+ ))
+ .stdout(contains(
+ "messages_required_to_save | 1024 |
default",
+ ));
+ for partition in &details.partitions {
+ assertion = assertion.stdout(contains(format!("| {:<12} |",
partition.id)));
+ }
+
+ client
+ .delete_topic(
+ &stream.id.try_into().unwrap(),
+ &details.id.try_into().unwrap(),
+ )
+ .await
+ .unwrap();
+ client
+ .delete_stream(&stream.id.try_into().unwrap())
+ .await
+ .unwrap();
+ clear_session_entry(socket_address);
+ harness.stop().await.unwrap();
+}