blob: 405c59c47f96d960ceb44a4ed8d59d0ed1b95059 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <cstdint>
#include <string>
#include <gtest/gtest.h>
#include "lib.rs.h"
#include "tests/e2e/test_helpers.hpp"
class LowLevelE2E_Stream : public E2ETestFixture {};
TEST_F(LowLevelE2E_Stream, CreateStreamAfterLogin) {
RecordProperty("description", "Creates a stream successfully after authenticating.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
}
TEST_F(LowLevelE2E_Stream, CreateDuplicateStreamThrows) {
RecordProperty("description", "Rejects creating the same stream twice.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_THROW(client->create_stream(stream_name), std::exception);
}
TEST_F(LowLevelE2E_Stream, CreateStreamBeforeLoginThrows) {
RecordProperty("description", "Throws when stream creation is attempted before authentication.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedOutClient();
ASSERT_THROW(client->create_stream(stream_name), std::exception);
ASSERT_NO_THROW(client->connect());
ASSERT_THROW(client->create_stream(stream_name), std::exception);
ASSERT_NO_THROW(client->login_user("iggy", "iggy"));
ASSERT_NO_THROW(client->disconnect());
ASSERT_THROW(client->create_stream(stream_name), std::exception);
}
TEST_F(LowLevelE2E_Stream, CreateStreamValidatesNameConstraintsAndUniqueness) {
RecordProperty("description",
"Validates stream name length constraints and accepts the maximum allowed name length.");
iggy::ffi::Client *client = GetLoggedInClient();
const std::string illegal_stream_names[] = {
"",
std::string(256, 'b'),
};
for (const auto &stream_name : illegal_stream_names) {
SCOPED_TRACE(stream_name);
ASSERT_THROW(client->create_stream(stream_name), std::exception);
}
const std::string max_length_name(255, 'a');
ASSERT_NO_THROW(client->create_stream(max_length_name));
TrackStream(max_length_name);
}
TEST_F(LowLevelE2E_Stream, CreateStreamWithEmojiName) {
RecordProperty("description", "Creates a stream with a UTF-8 emoji name.");
const std::string stream_name = "🚀🚀🚀🚀Apache Iggy🚀🚀🚀🚀";
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW({
const auto stream_details = client->get_stream(make_string_identifier(stream_name));
const std::string returned_stream_name = static_cast<std::string>(stream_details.name);
EXPECT_EQ(returned_stream_name, stream_name);
EXPECT_EQ(stream_details.topics_count, 0u);
EXPECT_EQ(stream_details.topics.size(), 0u);
});
}
TEST_F(LowLevelE2E_Stream, UpdateStreamWorksCorrectly) {
RecordProperty("description", "Updates an existing stream name while preserving the stream identity.");
const std::string stream_name = GetRandomName();
const std::string updated_stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
iggy::ffi::StreamDetails original_stream_details{};
ASSERT_NO_THROW({ original_stream_details = client->get_stream(make_string_identifier(stream_name)); });
const std::uint32_t stream_id = original_stream_details.id;
ForgetTrackedStream(stream_name);
TrackStream(stream_id);
ASSERT_NO_THROW(client->update_stream(make_string_identifier(stream_name), updated_stream_name));
ASSERT_THROW(client->get_stream(make_string_identifier(stream_name)), std::exception);
iggy::ffi::StreamDetails updated_stream_details{};
ASSERT_NO_THROW({ updated_stream_details = client->get_stream(make_numeric_identifier(stream_id)); });
EXPECT_EQ(updated_stream_details.id, original_stream_details.id);
EXPECT_EQ(updated_stream_details.created_at, original_stream_details.created_at);
EXPECT_EQ(updated_stream_details.name, updated_stream_name);
EXPECT_EQ(updated_stream_details.size_bytes, original_stream_details.size_bytes);
EXPECT_EQ(updated_stream_details.messages_count, original_stream_details.messages_count);
EXPECT_EQ(updated_stream_details.topics_count, original_stream_details.topics_count);
EXPECT_EQ(updated_stream_details.topics.size(), original_stream_details.topics.size());
}
TEST_F(LowLevelE2E_Stream, UpdateStreamWithSameNameIsIdempotent) {
RecordProperty("description",
"Calling update_stream with the current name succeeds without changing stream details.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
iggy::ffi::StreamDetails first_read{};
iggy::ffi::StreamDetails second_read{};
ASSERT_NO_THROW({ first_read = client->get_stream(make_string_identifier(stream_name)); });
ASSERT_NO_THROW(client->update_stream(make_numeric_identifier(first_read.id), stream_name));
ASSERT_NO_THROW({ second_read = client->get_stream(make_numeric_identifier(first_read.id)); });
EXPECT_EQ(second_read.id, first_read.id);
EXPECT_EQ(second_read.created_at, first_read.created_at);
EXPECT_EQ(second_read.name, first_read.name);
EXPECT_EQ(second_read.size_bytes, first_read.size_bytes);
EXPECT_EQ(second_read.messages_count, first_read.messages_count);
EXPECT_EQ(second_read.topics_count, first_read.topics_count);
EXPECT_EQ(second_read.topics.size(), first_read.topics.size());
}
TEST_F(LowLevelE2E_Stream, UpdateStreamBeforeLoginThrows) {
RecordProperty("description", "Rejects update_stream before connect, and after connect but before login.");
const std::string stream_name = GetRandomName();
const std::string updated_stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
iggy::ffi::Client *unauthenticated_client = GetLoggedOutClient();
ASSERT_THROW(unauthenticated_client->update_stream(make_string_identifier(stream_name), updated_stream_name),
std::exception);
ASSERT_NO_THROW(unauthenticated_client->connect());
ASSERT_THROW(unauthenticated_client->update_stream(make_string_identifier(stream_name), updated_stream_name),
std::exception);
ASSERT_NO_THROW(unauthenticated_client->login_user("iggy", "iggy"));
ASSERT_NO_THROW(unauthenticated_client->disconnect());
ASSERT_THROW(unauthenticated_client->update_stream(make_string_identifier(stream_name), updated_stream_name),
std::exception);
}
TEST_F(LowLevelE2E_Stream, UpdateStreamWithVariousUtf8Characters) {
RecordProperty("description", "Updates a stream name with various UTF-8 values.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
std::uint32_t stream_id = 0;
ASSERT_NO_THROW({
const auto stream_details = client->get_stream(make_string_identifier(stream_name));
stream_id = stream_details.id;
});
ForgetTrackedStream(stream_name);
TrackStream(stream_id);
const std::vector<std::string> updated_stream_names = {
"こんにちは世界", "안녕하세요세계", "你好世界", "مرحبا بالعالم", "नमस्ते दुनिया", "🚀🍕✨🎯🔥",
};
for (const auto &updated_stream_name : updated_stream_names) {
SCOPED_TRACE(updated_stream_name);
ASSERT_NO_THROW(client->update_stream(make_numeric_identifier(stream_id), updated_stream_name));
ASSERT_NO_THROW({
const auto stream_details = client->get_stream(make_numeric_identifier(stream_id));
EXPECT_EQ(stream_details.name, updated_stream_name);
});
}
}
TEST_F(LowLevelE2E_Stream, UpdateNonExistentStreamThrows) {
RecordProperty("description", "Throws when updating a stream that does not exist.");
const std::string stream_name = GetRandomName();
const std::string updated_stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_THROW(client->update_stream(make_string_identifier(stream_name), updated_stream_name), std::exception);
}
TEST_F(LowLevelE2E_Stream, UpdateStreamWithDuplicateNameThrows) {
RecordProperty("description", "Rejects renaming a stream to another stream's existing name.");
const std::string first_stream_name = GetRandomName();
const std::string second_stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(first_stream_name));
TrackStream(first_stream_name);
ASSERT_NO_THROW(client->create_stream(second_stream_name));
TrackStream(second_stream_name);
ASSERT_THROW(client->update_stream(make_string_identifier(first_stream_name), second_stream_name), std::exception);
}
TEST_F(LowLevelE2E_Stream, UpdateDeletedStreamThrows) {
RecordProperty("description", "Throws when updating a stream after it has been deleted.");
const std::string stream_name = GetRandomName();
const std::string updated_stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->delete_stream(make_string_identifier(stream_name)));
ForgetTrackedStream(stream_name);
ASSERT_THROW(client->update_stream(make_string_identifier(stream_name), updated_stream_name), std::exception);
}
TEST_F(LowLevelE2E_Stream, UpdateStreamFailedValidationDoesNotMutateStream) {
RecordProperty("description", "Keeps the stream unchanged when update_stream fails wrapper validation.");
const std::string stream_name = GetRandomName();
const std::string updated_stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
iggy::ffi::StreamDetails stream_before_failed_update{};
ASSERT_NO_THROW({ stream_before_failed_update = client->get_stream(make_string_identifier(stream_name)); });
iggy::ffi::Identifier invalid_numeric_id;
invalid_numeric_id.kind = "numeric";
invalid_numeric_id.length = 1;
invalid_numeric_id.value.push_back(1);
ASSERT_THROW(client->update_stream(std::move(invalid_numeric_id), updated_stream_name), std::exception);
iggy::ffi::StreamDetails stream_after_failed_update{};
ASSERT_NO_THROW({ stream_after_failed_update = client->get_stream(make_string_identifier(stream_name)); });
EXPECT_EQ(stream_after_failed_update.id, stream_before_failed_update.id);
EXPECT_EQ(stream_after_failed_update.created_at, stream_before_failed_update.created_at);
EXPECT_EQ(stream_after_failed_update.name, stream_before_failed_update.name);
EXPECT_EQ(stream_after_failed_update.size_bytes, stream_before_failed_update.size_bytes);
EXPECT_EQ(stream_after_failed_update.messages_count, stream_before_failed_update.messages_count);
EXPECT_EQ(stream_after_failed_update.topics_count, stream_before_failed_update.topics_count);
EXPECT_EQ(stream_after_failed_update.topics.size(), stream_before_failed_update.topics.size());
ASSERT_THROW(client->get_stream(make_string_identifier(updated_stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, UpdateStreamOnlyChangesName) {
RecordProperty(
"description",
"Changes only the stream name and leaves stream, topic, message, partition, and segment data intact.");
const std::string stream_name = GetRandomName();
const std::string updated_stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
std::uint32_t stream_id = 0;
ASSERT_NO_THROW({
const auto stream_details = client->get_stream(make_string_identifier(stream_name));
stream_id = stream_details.id;
});
ForgetTrackedStream(stream_name);
TrackStream(stream_id);
ASSERT_NO_THROW(client->create_topic(make_numeric_identifier(stream_id), topic_name, 2, "none", 0, "never_expire",
0, "server_default"));
rust::Vec<iggy::ffi::IggyMessageToSend> messages;
for (std::uint32_t i = 0; i < 3; ++i) {
auto message = iggy::ffi::make_message(to_payload("stream-update-preserve-" + std::to_string(i)),
rust::Vec<iggy::ffi::HeaderEntry>());
messages.push_back(std::move(message));
}
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(stream_id), make_numeric_identifier(0),
"partition_id", partition_id_bytes(0), std::move(messages)));
iggy::ffi::StreamDetails stream_before_update{};
iggy::ffi::StreamDetails stream_after_update{};
iggy::ffi::Stats stats_before_update{};
iggy::ffi::Stats stats_after_update{};
ASSERT_NO_THROW({
stream_before_update = client->get_stream(make_numeric_identifier(stream_id));
stats_before_update = client->get_stats();
});
ASSERT_NO_THROW(client->update_stream(make_numeric_identifier(stream_id), updated_stream_name));
ASSERT_THROW(client->get_stream(make_string_identifier(stream_name)), std::exception);
ASSERT_NO_THROW({
stream_after_update = client->get_stream(make_numeric_identifier(stream_id));
stats_after_update = client->get_stats();
});
EXPECT_EQ(stream_after_update.id, stream_before_update.id);
EXPECT_EQ(stream_after_update.created_at, stream_before_update.created_at);
EXPECT_EQ(stream_after_update.name, updated_stream_name);
EXPECT_EQ(stream_after_update.size_bytes, stream_before_update.size_bytes);
EXPECT_EQ(stream_after_update.messages_count, stream_before_update.messages_count);
EXPECT_EQ(stream_after_update.topics_count, stream_before_update.topics_count);
ASSERT_EQ(stream_before_update.topics.size(), 1u);
ASSERT_EQ(stream_after_update.topics.size(), 1u);
const auto &before_topic = stream_before_update.topics[0];
const auto &after_topic = stream_after_update.topics[0];
EXPECT_EQ(after_topic.id, before_topic.id);
EXPECT_EQ(after_topic.created_at, before_topic.created_at);
EXPECT_EQ(after_topic.name, before_topic.name);
EXPECT_EQ(after_topic.size_bytes, before_topic.size_bytes);
EXPECT_EQ(after_topic.message_expiry, before_topic.message_expiry);
EXPECT_EQ(after_topic.compression_algorithm, before_topic.compression_algorithm);
EXPECT_EQ(after_topic.max_topic_size, before_topic.max_topic_size);
EXPECT_EQ(after_topic.replication_factor, before_topic.replication_factor);
EXPECT_EQ(after_topic.messages_count, before_topic.messages_count);
EXPECT_EQ(after_topic.partitions_count, before_topic.partitions_count);
EXPECT_EQ(stats_after_update.streams_count, stats_before_update.streams_count);
EXPECT_EQ(stats_after_update.topics_count, stats_before_update.topics_count);
EXPECT_EQ(stats_after_update.partitions_count, stats_before_update.partitions_count);
EXPECT_EQ(stats_after_update.segments_count, stats_before_update.segments_count);
EXPECT_EQ(stats_after_update.messages_count, stats_before_update.messages_count);
}
TEST_F(LowLevelE2E_Stream, UpdateStreamValidatesNameBounds) {
RecordProperty("description",
"Rejects invalid stream name lengths during update and accepts the maximum allowed name length.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
std::uint32_t stream_id = 0;
ASSERT_NO_THROW({
const auto stream_details = client->get_stream(make_string_identifier(stream_name));
stream_id = stream_details.id;
});
ForgetTrackedStream(stream_name);
TrackStream(stream_id);
const std::vector<std::string> invalid_stream_names = {
"",
std::string(256, 'b'),
};
for (const auto &invalid_stream_name : invalid_stream_names) {
SCOPED_TRACE("invalid_stream_name_length=" + std::to_string(invalid_stream_name.size()));
ASSERT_THROW(client->update_stream(make_numeric_identifier(stream_id), invalid_stream_name), std::exception);
}
}
TEST_F(LowLevelE2E_Stream, StreamCreatedAndDeletedSuccessfully) {
RecordProperty("description", "Creates a stream and deletes it successfully by string identifier.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->delete_stream(make_string_identifier(stream_name)));
ForgetTrackedStream(stream_name);
}
TEST_F(LowLevelE2E_Stream, DeleteNotCreatedStreamThrows) {
RecordProperty("description", "Throws when deleting a stream that does not exist.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_THROW(client->delete_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, DeleteStreamBeforeLoginThrows) {
RecordProperty("description", "Throws when stream deletion is attempted before authentication.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedOutClient();
ASSERT_THROW(client->delete_stream(make_string_identifier(stream_name)), std::exception);
ASSERT_NO_THROW(client->connect());
ASSERT_THROW(client->delete_stream(make_string_identifier(stream_name)), std::exception);
ASSERT_NO_THROW(client->login_user("iggy", "iggy"));
ASSERT_NO_THROW(client->disconnect());
ASSERT_THROW(client->delete_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, DeleteStreamTwiceThrows) {
RecordProperty("description", "Throws when deleting the same stream a second time.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->delete_stream(make_string_identifier(stream_name)));
ForgetTrackedStream(stream_name);
ASSERT_THROW(client->delete_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, DeleteStreamWithInvalidIdentifierThrows) {
RecordProperty("description", "Rejects stream deletion requests that use invalid identifier formats.");
iggy::ffi::Client *client = GetLoggedInClient();
iggy::ffi::Identifier invalid_kind_id;
invalid_kind_id.kind = "invalid";
invalid_kind_id.length = 4;
invalid_kind_id.value = {1, 0, 0, 0};
ASSERT_THROW(client->delete_stream(std::move(invalid_kind_id)), std::exception);
iggy::ffi::Identifier invalid_numeric_id;
invalid_numeric_id.kind = "numeric";
invalid_numeric_id.length = 1;
invalid_numeric_id.value.push_back(1);
ASSERT_THROW(client->delete_stream(std::move(invalid_numeric_id)), std::exception);
}
TEST_F(LowLevelE2E_Stream, GetStreamDetailsWithInvalidIdentifierThrows) {
RecordProperty("description", "Rejects stream detail lookups that use invalid identifier formats.");
iggy::ffi::Client *client = GetLoggedInClient();
iggy::ffi::Identifier invalid_kind_id;
invalid_kind_id.kind = "invalid";
invalid_kind_id.length = 4;
invalid_kind_id.value = {1, 0, 0, 0};
ASSERT_THROW(client->get_stream(std::move(invalid_kind_id)), std::exception);
iggy::ffi::Identifier invalid_numeric_id;
invalid_numeric_id.kind = "numeric";
invalid_numeric_id.length = 1;
invalid_numeric_id.value.push_back(1);
ASSERT_THROW(client->get_stream(std::move(invalid_numeric_id)), std::exception);
}
TEST_F(LowLevelE2E_Stream, GetStreamByStringIdentifierReturnsStreamDetails) {
RecordProperty("description", "Returns expected stream details when looked up by string identifier.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW({
const auto stream_details = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_details.name, stream_name);
EXPECT_EQ(stream_details.topics_count, 0u);
EXPECT_EQ(stream_details.topics.size(), 0u);
EXPECT_EQ(stream_details.messages_count, 0u);
EXPECT_EQ(stream_details.size_bytes, 0u);
});
}
TEST_F(LowLevelE2E_Stream, GetNonExistentStreamDetailsThrows) {
RecordProperty("description", "Throws when requesting details for a stream that does not exist.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_THROW(client->get_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, GetStreamDetailsBeforeLoginThrows) {
RecordProperty("description", "Throws when stream details are requested before authentication.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedOutClient();
ASSERT_THROW(client->get_stream(make_string_identifier(stream_name)), std::exception);
ASSERT_NO_THROW(client->connect());
ASSERT_THROW(client->get_stream(make_string_identifier(stream_name)), std::exception);
ASSERT_NO_THROW(client->login_user("iggy", "iggy"));
ASSERT_NO_THROW(client->disconnect());
ASSERT_THROW(client->get_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, GetDeletedStreamDetailsThrows) {
RecordProperty("description", "Throws when requesting details for a stream after it has been deleted.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->get_stream(make_string_identifier(stream_name)));
ASSERT_NO_THROW(client->delete_stream(make_string_identifier(stream_name)));
ForgetTrackedStream(stream_name);
ASSERT_THROW(client->get_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, GetStreamByNumericIdentifierReturnsStreamDetails) {
RecordProperty("description", "Returns expected stream details when looked up by numeric identifier.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
std::uint32_t stream_id = 0;
ASSERT_NO_THROW({
const auto by_string = client->get_stream(make_string_identifier(stream_name));
stream_id = by_string.id;
});
ASSERT_NO_THROW({
const auto by_numeric = client->get_stream(make_numeric_identifier(stream_id));
EXPECT_EQ(by_numeric.id, stream_id);
EXPECT_EQ(by_numeric.name, stream_name);
EXPECT_EQ(by_numeric.topics_count, 0u);
EXPECT_EQ(by_numeric.topics.size(), 0u);
});
}
TEST_F(LowLevelE2E_Stream, GetStreamsReturnsEmptyAfterCleanup) {
RecordProperty("description", "Verifies get_streams returns empty vector after cleaning up all streams.");
iggy::ffi::Client *client = GetLoggedInClient();
auto streams = client->get_streams();
for (const auto &s : streams) {
client->delete_stream(make_numeric_identifier(s.id));
}
streams = client->get_streams();
ASSERT_EQ(streams.size(), 0u);
}
TEST_F(LowLevelE2E_Stream, GetStreamsReturnsStreamAfterCreation) {
RecordProperty("description", "Verifies created stream appears in get_streams result.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
client->create_stream(stream_name);
TrackStream(stream_name);
auto streams = client->get_streams();
ASSERT_GE(streams.size(), 1u);
bool found = false;
for (const auto &s : streams) {
if (std::string(s.name) == stream_name) {
found = true;
EXPECT_GT(s.created_at, static_cast<std::uint64_t>(0));
EXPECT_EQ(s.size_bytes, static_cast<std::uint64_t>(0));
EXPECT_EQ(s.messages_count, static_cast<std::uint64_t>(0));
EXPECT_EQ(s.topics_count, 0u);
break;
}
}
ASSERT_TRUE(found) << "Stream '" << stream_name << "' not found in get_streams result";
}
TEST_F(LowLevelE2E_Stream, GetStreamsFieldsVerification) {
RecordProperty("description",
"Verifies get_streams returns correct field values after creating stream with topic and messages.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
client->create_stream(stream_name);
TrackStream(stream_name);
auto stream = client->get_stream(make_string_identifier(stream_name));
const std::string topic_name = GetRandomName();
client->create_topic(make_numeric_identifier(stream.id), topic_name, 1, "none", 0, "never_expire", 0,
"server_default");
rust::Vec<iggy::ffi::IggyMessageToSend> messages;
for (std::uint32_t i = 0; i < 5; i++) {
auto msg = iggy::ffi::make_message(to_payload("field-verify-message-" + std::to_string(i)),
rust::Vec<iggy::ffi::HeaderEntry>());
messages.push_back(std::move(msg));
}
client->send_messages(make_numeric_identifier(stream.id), make_numeric_identifier(0), "partition_id",
partition_id_bytes(0), std::move(messages));
auto streams = client->get_streams();
ASSERT_GE(streams.size(), 1u);
bool found = false;
for (const auto &s : streams) {
if (std::string(s.name) == stream_name) {
found = true;
EXPECT_EQ(s.topics_count, 1u);
EXPECT_EQ(s.messages_count, 5u);
break;
}
}
ASSERT_TRUE(found) << "Stream '" << stream_name << "' not found in get_streams result";
}
TEST_F(LowLevelE2E_Stream, GetStreamsBeforeLoginThrows) {
RecordProperty("description", "Throws when get_streams is called before authentication.");
iggy::ffi::Client *client = GetLoggedOutClient();
ASSERT_THROW(client->get_streams(), std::exception);
ASSERT_NO_THROW(client->connect());
ASSERT_THROW(client->get_streams(), std::exception);
ASSERT_NO_THROW(client->login_user("iggy", "iggy"));
ASSERT_NO_THROW(client->disconnect());
ASSERT_THROW(client->get_streams(), std::exception);
}
TEST_F(LowLevelE2E_Stream, GetStreamsConsistentWithGetStream) {
RecordProperty("description", "Verifies get_streams result is consistent with get_stream for the same stream.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
client->create_stream(stream_name);
TrackStream(stream_name);
std::string list_name;
std::uint32_t list_id = 0;
std::uint32_t list_topics_count = 0;
std::uint64_t list_created_at = 0;
std::uint64_t list_size_bytes = 0;
auto streams = client->get_streams();
for (const auto &s : streams) {
if (std::string(s.name) == stream_name) {
list_name = std::string(s.name);
list_id = s.id;
list_topics_count = s.topics_count;
list_created_at = s.created_at;
list_size_bytes = s.size_bytes;
break;
}
}
ASSERT_FALSE(list_name.empty()) << "Stream '" << stream_name << "' not found in get_streams result";
auto single = client->get_stream(make_string_identifier(stream_name));
auto single_name = std::string(single.name);
auto single_topics = single.topics_count;
EXPECT_EQ(list_name, single_name);
EXPECT_EQ(list_id, single.id);
EXPECT_EQ(list_topics_count, single_topics);
EXPECT_EQ(list_created_at, single.created_at);
EXPECT_EQ(list_size_bytes, single.size_bytes);
}
TEST_F(LowLevelE2E_Stream, GetStreamsRepeatedCallsReturnSameResult) {
RecordProperty("description", "Verifies repeated get_streams calls return consistent results.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
client->create_stream(stream_name);
TrackStream(stream_name);
auto streams1 = client->get_streams();
auto streams2 = client->get_streams();
auto streams3 = client->get_streams();
ASSERT_EQ(streams1.size(), streams2.size());
ASSERT_EQ(streams2.size(), streams3.size());
auto contains_stream = [&](const rust::Vec<iggy::ffi::Stream> &vec) {
for (const auto &s : vec) {
if (std::string(s.name) == stream_name) {
return true;
}
}
return false;
};
ASSERT_TRUE(contains_stream(streams1)) << "Stream not found in first call";
ASSERT_TRUE(contains_stream(streams2)) << "Stream not found in second call";
ASSERT_TRUE(contains_stream(streams3)) << "Stream not found in third call";
}
TEST_F(LowLevelE2E_Stream, PurgeStreamOnNonExistentStreamThrows) {
RecordProperty("description", "Throws when purging a stream that does not exist.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_THROW(client->purge_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, PurgeStreamAfterStreamDeletionThrows) {
RecordProperty("description", "Throws when purging a stream after it has been deleted.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->delete_stream(make_string_identifier(stream_name)));
ForgetTrackedStream(stream_name);
ASSERT_THROW(client->purge_stream(make_string_identifier(stream_name)), std::exception);
}
TEST_F(LowLevelE2E_Stream, PurgeStreamWithInvalidIdentifierThrows) {
RecordProperty("description", "Rejects stream purge requests that use invalid identifier formats.");
iggy::ffi::Client *client = GetLoggedInClient();
iggy::ffi::Identifier invalid_kind_id;
invalid_kind_id.kind = "invalid";
invalid_kind_id.length = 4;
invalid_kind_id.value = {1, 0, 0, 0};
ASSERT_THROW(client->purge_stream(std::move(invalid_kind_id)), std::exception);
iggy::ffi::Identifier invalid_numeric_id;
invalid_numeric_id.kind = "numeric";
invalid_numeric_id.length = 1;
invalid_numeric_id.value.push_back(1);
ASSERT_THROW(client->purge_stream(std::move(invalid_numeric_id)), std::exception);
}
TEST_F(LowLevelE2E_Stream, PurgeStreamPreservesStreamMetadata) {
RecordProperty("description", "Preserves stream identity and topic metadata after purging stream messages.");
const std::string stream_name = GetRandomName();
const std::string first_topic_name = GetRandomName();
const std::string second_topic_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), first_topic_name, 2, "gzip", 1,
"duration", 1000, "1GiB"));
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), second_topic_name, 3, "none", 0,
"never_expire", 0, "server_default"));
const auto stream_before_purge = client->get_stream(make_string_identifier(stream_name));
ASSERT_EQ(stream_before_purge.topics.size(), 2u);
rust::Vec<iggy::ffi::IggyMessageToSend> first_topic_messages;
first_topic_messages.push_back(
iggy::ffi::make_message(to_payload("preserve-stream-metadata"), rust::Vec<iggy::ffi::HeaderEntry>()));
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(stream_before_purge.id),
make_string_identifier(first_topic_name), "partition_id",
partition_id_bytes(0), std::move(first_topic_messages)));
const auto stream_with_messages = client->get_stream(make_string_identifier(stream_name));
EXPECT_GT(stream_with_messages.messages_count, 0u);
EXPECT_GT(stream_with_messages.size_bytes, 0u);
struct TopicMetadata {
std::uint32_t id;
std::uint64_t created_at;
std::string name;
std::uint64_t message_expiry;
std::string compression_algorithm;
std::uint64_t max_topic_size;
std::uint8_t replication_factor;
std::uint32_t partitions_count;
};
std::unordered_map<std::string, TopicMetadata> topics_before_purge;
for (const auto &topic : stream_with_messages.topics) {
topics_before_purge[static_cast<std::string>(topic.name)] = {
topic.id,
topic.created_at,
static_cast<std::string>(topic.name),
topic.message_expiry,
static_cast<std::string>(topic.compression_algorithm),
topic.max_topic_size,
topic.replication_factor,
topic.partitions_count};
}
ASSERT_NO_THROW(client->purge_stream(make_string_identifier(stream_name)));
const auto stream_after_purge = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_after_purge.id, stream_with_messages.id);
EXPECT_EQ(stream_after_purge.created_at, stream_with_messages.created_at);
EXPECT_EQ(stream_after_purge.name, stream_with_messages.name);
EXPECT_EQ(stream_after_purge.topics_count, stream_with_messages.topics_count);
ASSERT_EQ(stream_after_purge.topics.size(), stream_with_messages.topics.size());
for (const auto &topic : stream_after_purge.topics) {
const std::string topic_name = static_cast<std::string>(topic.name);
const auto metadata_it = topics_before_purge.find(topic_name);
ASSERT_NE(metadata_it, topics_before_purge.end());
const auto &metadata = metadata_it->second;
EXPECT_EQ(topic.id, metadata.id);
EXPECT_EQ(topic.created_at, metadata.created_at);
EXPECT_EQ(topic.name, metadata.name);
EXPECT_EQ(topic.message_expiry, metadata.message_expiry);
EXPECT_EQ(topic.compression_algorithm, metadata.compression_algorithm);
EXPECT_EQ(topic.max_topic_size, metadata.max_topic_size);
EXPECT_EQ(topic.replication_factor, metadata.replication_factor);
EXPECT_EQ(topic.partitions_count, metadata.partitions_count);
}
}
TEST_F(LowLevelE2E_Stream, PurgeStreamRemovesMessagesAndPreservesTopics) {
RecordProperty("description", "Purges all stream messages while keeping the stream and topics intact.");
const std::string stream_name = GetRandomName();
const std::string first_topic_name = GetRandomName();
const std::string second_topic_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), first_topic_name, 1, "none", 0,
"server_default", 0, "server_default"));
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), second_topic_name, 1, "none", 0,
"server_default", 0, "server_default"));
const auto created_stream = client->get_stream(make_string_identifier(stream_name));
ASSERT_EQ(created_stream.topics.size(), 2u);
std::uint32_t first_topic_id = 0;
std::uint32_t second_topic_id = 0;
bool first_topic_found = false;
bool second_topic_found = false;
for (const auto &topic : created_stream.topics) {
const std::string topic_name = static_cast<std::string>(topic.name);
if (topic_name == first_topic_name) {
first_topic_id = topic.id;
first_topic_found = true;
} else if (topic_name == second_topic_name) {
second_topic_id = topic.id;
second_topic_found = true;
}
}
ASSERT_TRUE(first_topic_found);
ASSERT_TRUE(second_topic_found);
rust::Vec<iggy::ffi::IggyMessageToSend> first_topic_messages;
for (std::uint32_t i = 0; i < 3; ++i) {
first_topic_messages.push_back(iggy::ffi::make_message(to_payload("purge-stream-first-" + std::to_string(i)),
rust::Vec<iggy::ffi::HeaderEntry>()));
}
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(created_stream.id),
make_numeric_identifier(first_topic_id), "partition_id",
partition_id_bytes(0), std::move(first_topic_messages)));
rust::Vec<iggy::ffi::IggyMessageToSend> second_topic_messages;
for (std::uint32_t i = 0; i < 2; ++i) {
second_topic_messages.push_back(iggy::ffi::make_message(to_payload("purge-stream-second-" + std::to_string(i)),
rust::Vec<iggy::ffi::HeaderEntry>()));
}
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(created_stream.id),
make_numeric_identifier(second_topic_id), "partition_id",
partition_id_bytes(0), std::move(second_topic_messages)));
const auto stream_before_purge = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_before_purge.topics_count, 2u);
EXPECT_EQ(stream_before_purge.messages_count, 5u);
EXPECT_GT(stream_before_purge.size_bytes, 0u);
std::unordered_map<std::string, std::uint64_t> messages_before_purge;
for (const auto &topic : stream_before_purge.topics) {
messages_before_purge[static_cast<std::string>(topic.name)] = topic.messages_count;
}
EXPECT_EQ(messages_before_purge[first_topic_name], 3u);
EXPECT_EQ(messages_before_purge[second_topic_name], 2u);
const auto streams_before_purge = client->get_streams();
bool found_stream_before_purge = false;
for (const auto &stream : streams_before_purge) {
if (static_cast<std::string>(stream.name) == stream_name) {
found_stream_before_purge = true;
EXPECT_EQ(stream.topics_count, 2u);
EXPECT_EQ(stream.messages_count, 5u);
EXPECT_GT(stream.size_bytes, 0u);
break;
}
}
ASSERT_TRUE(found_stream_before_purge);
ASSERT_NO_THROW(client->purge_stream(make_string_identifier(stream_name)));
const auto stream_after_purge = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_after_purge.topics_count, 2u);
EXPECT_EQ(stream_after_purge.messages_count, 0u);
EXPECT_EQ(stream_after_purge.size_bytes, 0u);
ASSERT_EQ(stream_after_purge.topics.size(), 2u);
for (const auto &topic : stream_after_purge.topics) {
EXPECT_EQ(topic.messages_count, 0u);
EXPECT_EQ(topic.size_bytes, 0u);
}
const auto streams_after_purge = client->get_streams();
bool found_stream_after_purge = false;
for (const auto &stream : streams_after_purge) {
if (static_cast<std::string>(stream.name) == stream_name) {
found_stream_after_purge = true;
EXPECT_EQ(stream.topics_count, 2u);
EXPECT_EQ(stream.messages_count, 0u);
EXPECT_EQ(stream.size_bytes, 0u);
break;
}
}
ASSERT_TRUE(found_stream_after_purge);
}
TEST_F(LowLevelE2E_Stream, PurgeStreamAcrossMultipleTopicsAndPartitionsClearsEverything) {
RecordProperty("description", "Purges all messages across multiple topics and partitions in the stream.");
const std::string stream_name = GetRandomName();
const std::string first_topic_name = GetRandomName();
const std::string second_topic_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), first_topic_name, 2, "none", 0,
"server_default", 0, "server_default"));
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), second_topic_name, 3, "none", 0,
"server_default", 0, "server_default"));
const auto created_stream = client->get_stream(make_string_identifier(stream_name));
ASSERT_EQ(created_stream.topics.size(), 2u);
std::uint32_t first_topic_id = 0;
std::uint32_t second_topic_id = 0;
bool first_topic_found = false;
bool second_topic_found = false;
for (const auto &topic : created_stream.topics) {
const std::string topic_name = static_cast<std::string>(topic.name);
if (topic_name == first_topic_name) {
first_topic_id = topic.id;
first_topic_found = true;
} else if (topic_name == second_topic_name) {
second_topic_id = topic.id;
second_topic_found = true;
}
}
ASSERT_TRUE(first_topic_found);
ASSERT_TRUE(second_topic_found);
for (std::uint32_t partition_id = 0; partition_id < 2; ++partition_id) {
rust::Vec<iggy::ffi::IggyMessageToSend> messages;
for (std::uint32_t i = 0; i < 2; ++i) {
messages.push_back(iggy::ffi::make_message(
to_payload("purge-stream-topic-a-" + std::to_string(partition_id) + "-" + std::to_string(i)),
rust::Vec<iggy::ffi::HeaderEntry>()));
}
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(created_stream.id),
make_numeric_identifier(first_topic_id), "partition_id",
partition_id_bytes(partition_id), std::move(messages)));
}
for (std::uint32_t partition_id = 0; partition_id < 3; ++partition_id) {
rust::Vec<iggy::ffi::IggyMessageToSend> messages;
for (std::uint32_t i = 0; i < 2; ++i) {
messages.push_back(iggy::ffi::make_message(
to_payload("purge-stream-topic-b-" + std::to_string(partition_id) + "-" + std::to_string(i)),
rust::Vec<iggy::ffi::HeaderEntry>()));
}
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(created_stream.id),
make_numeric_identifier(second_topic_id), "partition_id",
partition_id_bytes(partition_id), std::move(messages)));
}
const auto stream_before_purge = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_before_purge.messages_count, 10u);
ASSERT_NO_THROW(client->purge_stream(make_string_identifier(stream_name)));
const auto stream_after_purge = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_after_purge.topics_count, 2u);
EXPECT_EQ(stream_after_purge.messages_count, 0u);
EXPECT_EQ(stream_after_purge.size_bytes, 0u);
for (const auto &topic : stream_after_purge.topics) {
EXPECT_EQ(topic.messages_count, 0u);
EXPECT_EQ(topic.size_bytes, 0u);
}
}
TEST_F(LowLevelE2E_Stream, PurgeStreamThenSendMessagesAgainSucceeds) {
RecordProperty("description", "Allows sending fresh messages to a topic after purging its parent stream.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), topic_name, 1, "none", 0,
"server_default", 0, "server_default"));
const auto created_stream = client->get_stream(make_string_identifier(stream_name));
ASSERT_EQ(created_stream.topics.size(), 1u);
const std::uint32_t topic_id = created_stream.topics.front().id;
rust::Vec<iggy::ffi::IggyMessageToSend> first_batch;
first_batch.push_back(iggy::ffi::make_message(to_payload("before-purge"), rust::Vec<iggy::ffi::HeaderEntry>()));
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(created_stream.id), make_numeric_identifier(topic_id),
"partition_id", partition_id_bytes(0), std::move(first_batch)));
ASSERT_NO_THROW(client->purge_stream(make_string_identifier(stream_name)));
rust::Vec<iggy::ffi::IggyMessageToSend> second_batch;
second_batch.push_back(iggy::ffi::make_message(to_payload("after-purge-0"), rust::Vec<iggy::ffi::HeaderEntry>()));
second_batch.push_back(iggy::ffi::make_message(to_payload("after-purge-1"), rust::Vec<iggy::ffi::HeaderEntry>()));
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(created_stream.id), make_numeric_identifier(topic_id),
"partition_id", partition_id_bytes(0), std::move(second_batch)));
const auto stream_after_resend = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_after_resend.topics_count, 1u);
EXPECT_EQ(stream_after_resend.messages_count, 2u);
EXPECT_GT(stream_after_resend.size_bytes, 0u);
ASSERT_EQ(stream_after_resend.topics.size(), 1u);
EXPECT_EQ(stream_after_resend.topics.front().messages_count, 2u);
}
TEST_F(LowLevelE2E_Stream, PurgeStreamTwiceKeepsStreamEmptyAndTopicsIntact) {
RecordProperty("description", "Allows purging the same stream twice and keeps the stream empty after both calls.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client->create_topic(make_string_identifier(stream_name), topic_name, 1, "none", 0,
"server_default", 0, "server_default"));
const auto created_stream = client->get_stream(make_string_identifier(stream_name));
ASSERT_EQ(created_stream.topics.size(), 1u);
const std::uint32_t topic_id = created_stream.topics.front().id;
rust::Vec<iggy::ffi::IggyMessageToSend> messages;
for (std::uint32_t i = 0; i < 3; ++i) {
messages.push_back(iggy::ffi::make_message(to_payload("purge-stream-twice-" + std::to_string(i)),
rust::Vec<iggy::ffi::HeaderEntry>()));
}
ASSERT_NO_THROW(client->send_messages(make_numeric_identifier(created_stream.id), make_numeric_identifier(topic_id),
"partition_id", partition_id_bytes(0), std::move(messages)));
ASSERT_NO_THROW(client->purge_stream(make_string_identifier(stream_name)));
const auto stream_after_first_purge = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_after_first_purge.topics_count, 1u);
EXPECT_EQ(stream_after_first_purge.messages_count, 0u);
EXPECT_EQ(stream_after_first_purge.size_bytes, 0u);
ASSERT_EQ(stream_after_first_purge.topics.size(), 1u);
EXPECT_EQ(stream_after_first_purge.topics.front().messages_count, 0u);
EXPECT_EQ(stream_after_first_purge.topics.front().size_bytes, 0u);
ASSERT_NO_THROW(client->purge_stream(make_string_identifier(stream_name)));
const auto stream_after_second_purge = client->get_stream(make_string_identifier(stream_name));
EXPECT_EQ(stream_after_second_purge.topics_count, 1u);
EXPECT_EQ(stream_after_second_purge.messages_count, 0u);
EXPECT_EQ(stream_after_second_purge.size_bytes, 0u);
ASSERT_EQ(stream_after_second_purge.topics.size(), 1u);
EXPECT_EQ(stream_after_second_purge.topics.front().messages_count, 0u);
EXPECT_EQ(stream_after_second_purge.topics.front().size_bytes, 0u);
}
TEST_F(LowLevelE2E_Stream, PurgeStreamBeforeLoginThrows) {
RecordProperty("description", "Throws when stream purge is attempted before authentication.");
const std::string stream_name = GetRandomName();
iggy::ffi::Client *client = GetLoggedInClient();
ASSERT_NO_THROW(client->create_stream(stream_name));
TrackStream(stream_name);
iggy::ffi::Client *unauthenticated_client = GetLoggedOutClient();
ASSERT_THROW(unauthenticated_client->purge_stream(make_string_identifier(stream_name)), std::exception);
ASSERT_NO_THROW(unauthenticated_client->connect());
ASSERT_THROW(unauthenticated_client->purge_stream(make_string_identifier(stream_name)), std::exception);
ASSERT_NO_THROW(unauthenticated_client->login_user("iggy", "iggy"));
ASSERT_NO_THROW(unauthenticated_client->disconnect());
ASSERT_THROW(unauthenticated_client->purge_stream(make_string_identifier(stream_name)), std::exception);
}