blob: 030bed90e8df8dd09d8c18c1801ecb2212f4abb6 [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 <vector>
#include <gtest/gtest.h>
#include "iggy.hpp"
#include "tests/e2e/test_helpers.hpp"
class E2E_Partition : public E2ETestFixture {};
TEST_F(E2E_Partition, CreatePartitionsSucceeds) {
RecordProperty("description", "Creates partitions for an existing topic and verifies the resulting count.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 1));
ASSERT_NO_THROW(
client.CreatePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 43));
ASSERT_NO_THROW({
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), 1u);
EXPECT_EQ(stream_details.Topics()[0].Name(), topic_name);
EXPECT_EQ(stream_details.Topics()[0].PartitionsCount(), 44u);
});
}
TEST_F(E2E_Partition, CreatePartitionsBeforeLoginThrows) {
RecordProperty("description",
"Throws when create_partitions is called before connect, and after connect but before login.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedOutHighLevelClient();
ASSERT_THROW(
client.CreatePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_NO_THROW(client.Connect());
ASSERT_THROW(
client.CreatePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_NO_THROW(client.Login("iggy", "iggy"));
ASSERT_NO_THROW(client.Disconnect());
ASSERT_THROW(
client.CreatePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
}
TEST_F(E2E_Partition, CreatePartitionsOnNonExistentResourcesThrows) {
RecordProperty("description", "Throws when create_partitions is called for a stream or topic that does not exist.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
const std::string missing_stream_name = GetRandomName();
const std::string missing_topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 1));
ASSERT_THROW(
client.CreatePartitions(iggy::Identifier::String(missing_stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_THROW(
client.CreatePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(missing_topic_name), 1),
std::exception);
}
TEST_F(E2E_Partition, CreatePartitionsWithInvalidIdentifiersThrows) {
RecordProperty("description", "Rejects invalid stream or topic identifiers before creating partitions.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 1));
ASSERT_THROW(client.CreatePartitions(iggy::Identifier::String(""), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_THROW(client.CreatePartitions(iggy::Identifier::String(std::string(256, 'a')),
iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_THROW(client.CreatePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(""), 1),
std::exception);
ASSERT_THROW(client.CreatePartitions(iggy::Identifier::String(stream_name),
iggy::Identifier::String(std::string(256, 'a')), 1),
std::exception);
}
TEST_F(E2E_Partition, CreatePartitionsWithBoundaryPartitionsCountValues) {
RecordProperty("description",
"Accepts supported create_partitions counts and rejects values outside the allowed range.");
const std::string stream_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
struct TestCase {
std::string topic_name;
std::uint32_t partitions_count;
bool should_succeed;
std::uint32_t expected_total_partitions;
};
const std::vector<TestCase> test_cases = {
{GetRandomName(), static_cast<std::uint32_t>(-1), false, 1},
{GetRandomName(), 0, false, 1},
{GetRandomName(), 1, true, 2},
{GetRandomName(), 43, true, 44},
{GetRandomName(), 1000, true, 1001},
{GetRandomName(), 1001, false, 1},
};
for (const auto &test_case : test_cases) {
SCOPED_TRACE(test_case.topic_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), test_case.topic_name, 1));
if (test_case.should_succeed) {
ASSERT_NO_THROW(client.CreatePartitions(iggy::Identifier::String(stream_name),
iggy::Identifier::String(test_case.topic_name),
test_case.partitions_count));
} else {
ASSERT_THROW(
client.CreatePartitions(iggy::Identifier::String(stream_name),
iggy::Identifier::String(test_case.topic_name), test_case.partitions_count),
std::exception);
}
}
ASSERT_NO_THROW({
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), test_cases.size());
for (const auto &test_case : test_cases) {
bool found = false;
for (const auto &topic : stream_details.Topics()) {
if (topic.Name() == test_case.topic_name) {
EXPECT_EQ(topic.PartitionsCount(), test_case.expected_total_partitions);
found = true;
break;
}
}
EXPECT_TRUE(found) << "Missing topic " << test_case.topic_name;
}
});
}
TEST_F(E2E_Partition, CreatePartitionsWithNumericIdentifiersSucceeds) {
RecordProperty("description",
"Creates partitions successfully when valid numeric stream and topic identifiers are used.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 1));
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), 1u);
ASSERT_NO_THROW(client.CreatePartitions(iggy::Identifier::Numeric(stream_details.Id()),
iggy::Identifier::Numeric(stream_details.Topics()[0].Id()), 43));
ASSERT_NO_THROW({
const auto updated_stream_details = client.GetStream(iggy::Identifier::Numeric(stream_details.Id()));
ASSERT_EQ(updated_stream_details.Topics().size(), 1u);
EXPECT_EQ(updated_stream_details.Topics()[0].Id(), stream_details.Topics()[0].Id());
EXPECT_EQ(updated_stream_details.Topics()[0].Name(), topic_name);
EXPECT_EQ(updated_stream_details.Topics()[0].PartitionsCount(), 44u);
});
}
TEST_F(E2E_Partition, DeletePartitionsSucceeds) {
RecordProperty("description", "Deletes partitions from an existing topic and verifies the resulting count.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 44));
ASSERT_NO_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 43));
ASSERT_NO_THROW({
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), 1u);
EXPECT_EQ(stream_details.Topics()[0].Name(), topic_name);
EXPECT_EQ(stream_details.Topics()[0].PartitionsCount(), 1u);
});
}
TEST_F(E2E_Partition, DeleteMorePartitionsThanExistingThrows) {
RecordProperty("description",
"Rejects delete_partitions counts outside the allowed range and counts greater than existing.");
const std::string stream_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
struct TestCase {
std::string topic_name;
std::uint32_t partitions_count;
bool should_succeed;
std::uint32_t initial_partitions;
std::uint32_t expected_total_partitions;
};
const std::vector<TestCase> test_cases = {
{GetRandomName(), static_cast<std::uint32_t>(-1), false, 3, 3},
{GetRandomName(), 0, false, 3, 3},
{GetRandomName(), 1, true, 3, 2},
{GetRandomName(), 4, false, 3, 3},
};
for (const auto &test_case : test_cases) {
SCOPED_TRACE(test_case.topic_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), test_case.topic_name,
test_case.initial_partitions));
if (test_case.should_succeed) {
ASSERT_NO_THROW(client.DeletePartitions(iggy::Identifier::String(stream_name),
iggy::Identifier::String(test_case.topic_name),
test_case.partitions_count));
} else {
ASSERT_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name),
iggy::Identifier::String(test_case.topic_name), test_case.partitions_count),
std::exception);
}
}
ASSERT_NO_THROW({
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), test_cases.size());
for (const auto &test_case : test_cases) {
bool found = false;
for (const auto &topic : stream_details.Topics()) {
if (topic.Name() == test_case.topic_name) {
EXPECT_EQ(topic.PartitionsCount(), test_case.expected_total_partitions);
found = true;
break;
}
}
EXPECT_TRUE(found) << "Missing topic " << test_case.topic_name;
}
});
}
TEST_F(E2E_Partition, DeletePartitionsBeforeCreatingAdditionalPartitionsSucceeds) {
RecordProperty("description",
"Deletes partitions from the initial topic allocation without calling create_partitions first.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 3));
ASSERT_NO_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1));
ASSERT_NO_THROW({
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), 1u);
EXPECT_EQ(stream_details.Topics()[0].PartitionsCount(), 2u);
});
}
TEST_F(E2E_Partition, DeletePartitionsFromTopicWithZeroPartitionsThrows) {
RecordProperty("description",
"Throws when delete_partitions is called with count 1 for a topic that currently has 0 partitions.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 0));
ASSERT_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_NO_THROW({
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), 1u);
EXPECT_EQ(stream_details.Topics()[0].Name(), topic_name);
EXPECT_EQ(stream_details.Topics()[0].PartitionsCount(), 0u);
});
}
TEST_F(E2E_Partition, DeletePartitionsBeforeLoginThrows) {
RecordProperty("description",
"Throws when delete_partitions is called before connect, and after connect but before login.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedOutHighLevelClient();
ASSERT_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_NO_THROW(client.Connect());
ASSERT_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_NO_THROW(client.Login("iggy", "iggy"));
ASSERT_NO_THROW(client.Disconnect());
ASSERT_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
}
TEST_F(E2E_Partition, DeletePartitionsOnNonExistentResourcesThrows) {
RecordProperty("description", "Throws when delete_partitions is called for a stream or topic that does not exist.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
const std::string missing_stream_name = GetRandomName();
const std::string missing_topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 3));
ASSERT_THROW(
client.DeletePartitions(iggy::Identifier::String(missing_stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(missing_topic_name), 1),
std::exception);
}
TEST_F(E2E_Partition, DeletePartitionsWithInvalidIdentifiersThrows) {
RecordProperty("description", "Rejects invalid stream or topic identifiers before deleting partitions.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 3));
ASSERT_THROW(client.DeletePartitions(iggy::Identifier::String(""), iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_THROW(client.DeletePartitions(iggy::Identifier::String(std::string(256, 'a')),
iggy::Identifier::String(topic_name), 1),
std::exception);
ASSERT_THROW(client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(""), 1),
std::exception);
ASSERT_THROW(client.DeletePartitions(iggy::Identifier::String(stream_name),
iggy::Identifier::String(std::string(256, 'a')), 1),
std::exception);
}
TEST_F(E2E_Partition, DeletePartitionsTwiceForSameTopicSucceeds) {
RecordProperty("description", "Allows delete_partitions to be called twice for the same stream and topic.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 45));
ASSERT_NO_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 20));
ASSERT_NO_THROW(
client.DeletePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 20));
ASSERT_NO_THROW({
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), 1u);
EXPECT_EQ(stream_details.Topics()[0].Name(), topic_name);
EXPECT_EQ(stream_details.Topics()[0].PartitionsCount(), 5u);
});
}
TEST_F(E2E_Partition, DeletePartitionsAfterStreamDeletionThrows) {
RecordProperty("description", "Throws when delete_partitions is called after the stream has been deleted.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 3));
const auto stream_details = client.GetStream(iggy::Identifier::String(stream_name));
ASSERT_EQ(stream_details.Topics().size(), 1u);
ASSERT_NO_THROW(client.DeleteStream(iggy::Identifier::Numeric(stream_details.Id())));
ForgetTrackedStream(stream_name);
ASSERT_THROW(client.DeletePartitions(iggy::Identifier::Numeric(stream_details.Id()),
iggy::Identifier::Numeric(stream_details.Topics()[0].Id()), 1),
std::exception);
}
TEST_F(E2E_Partition, CreatePartitionsAfterTopicDeletionThrows) {
RecordProperty("description", "Throws when creating partitions after the topic has been deleted.");
const std::string stream_name = GetRandomName();
const std::string topic_name = GetRandomName();
auto client = GetLoggedInHighLevelClient();
ASSERT_NO_THROW(client.CreateStream(stream_name));
TrackStream(stream_name);
ASSERT_NO_THROW(client.CreateTopic(iggy::Identifier::String(stream_name), topic_name, 1));
ASSERT_NO_THROW(client.DeleteTopic(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name)));
ASSERT_THROW(
client.CreatePartitions(iggy::Identifier::String(stream_name), iggy::Identifier::String(topic_name), 1),
std::exception);
}