blob: e26da42b0fdaf6c719ed8f2ed910a1093b5033d0 [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 <memory>
#include <string>
#include <system_error>
#include <vector>
#include "ClientManagerMock.h"
#include "NameServerResolverMock.h"
#include "Protocol.h"
#include "SimpleConsumerImpl.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "rocketmq/ErrorCode.h"
#include "rocketmq/FilterExpression.h"
#include "rocketmq/Message.h"
ROCKETMQ_NAMESPACE_BEGIN
namespace {
/// Test subclass that exposes protected members for unit testing.
class TestableSimpleConsumerImpl : public SimpleConsumerImpl {
public:
explicit TestableSimpleConsumerImpl(const std::string& group) : ClientImpl(group), SimpleConsumerImpl(group) {}
using SimpleConsumerImpl::topicsOfInterest;
};
} // namespace
TEST(SimpleConsumerImplTest, constructorWithGroupTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
EXPECT_EQ(consumer->config().subscriber.group.name(), "test-group");
}
TEST(SimpleConsumerImplTest, subscribeAddsTopicTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
consumer->subscribe("test-topic", FilterExpression("*"));
std::vector<std::string> topics;
consumer->topicsOfInterest(topics);
ASSERT_EQ(1u, topics.size());
EXPECT_EQ("test-topic", topics[0]);
}
TEST(SimpleConsumerImplTest, unsubscribeRemovesTopicTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
consumer->subscribe("test-topic", FilterExpression("*"));
std::vector<std::string> topics;
consumer->topicsOfInterest(topics);
ASSERT_EQ(1u, topics.size());
consumer->unsubscribe("test-topic");
topics.clear();
consumer->topicsOfInterest(topics);
EXPECT_TRUE(topics.empty());
}
TEST(SimpleConsumerImplTest, subscribeMultipleTopicsTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
consumer->subscribe("topic-1", FilterExpression("*"));
consumer->subscribe("topic-2", FilterExpression("tagA"));
consumer->subscribe("topic-3", FilterExpression("tagB"));
std::vector<std::string> topics;
consumer->topicsOfInterest(topics);
EXPECT_EQ(3u, topics.size());
}
TEST(SimpleConsumerImplTest, subscribeUpdatesFilterExpressionTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
consumer->subscribe("test-topic", FilterExpression("tagA"));
// Overwrite with a new filter expression
consumer->subscribe("test-topic", FilterExpression("tagB"));
std::vector<std::string> topics;
consumer->topicsOfInterest(topics);
// Should still be a single topic (no duplicate)
ASSERT_EQ(1u, topics.size());
EXPECT_EQ("test-topic", topics[0]);
}
TEST(SimpleConsumerImplTest, prepareHeartbeatDataSetsSimpleConsumerTypeTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
rmq::HeartbeatRequest request;
consumer->prepareHeartbeatData(request);
EXPECT_EQ(rmq::ClientType::SIMPLE_CONSUMER, request.client_type());
}
TEST(SimpleConsumerImplTest, buildClientSettingsSetsSimpleConsumerTypeTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
auto resolver = std::make_shared<testing::NiceMock<NameServerResolverMock>>();
ON_CALL(*resolver, resolve()).WillByDefault(testing::Return(std::string("ipv4:10.0.0.1:8080")));
consumer->withNameServerResolver(resolver);
consumer->subscribe("settings-topic", FilterExpression("tagX"));
rmq::Settings settings;
consumer->buildClientSettings(settings);
EXPECT_EQ(rmq::ClientType::SIMPLE_CONSUMER, settings.client_type());
}
TEST(SimpleConsumerImplTest, unsubscribeNonexistentTopicDoesNotCrashTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
// Should not crash or throw
consumer->unsubscribe("nonexistent-topic");
std::vector<std::string> topics;
consumer->topicsOfInterest(topics);
EXPECT_TRUE(topics.empty());
}
TEST(SimpleConsumerImplTest, topicsOfInterestNoDuplicatesTest) {
auto consumer = std::make_shared<TestableSimpleConsumerImpl>("test-group");
consumer->subscribe("dup-topic", FilterExpression("*"));
// Pre-populate the vector — topicsOfInterest should not add a duplicate
std::vector<std::string> topics;
topics.push_back("dup-topic");
consumer->topicsOfInterest(topics);
EXPECT_EQ(1u, topics.size());
}
TEST(SimpleConsumerImplTest, ackWhenNotRunningFailsTest) {
auto consumer = std::make_shared<SimpleConsumerImpl>("test-group");
// State is CREATED by default (not STARTED) — the consumer was never started.
// The manager is not configured, so ack cannot proceed.
// Verify the initial state is indeed CREATED.
EXPECT_FALSE(consumer->active());
}
TEST(SimpleConsumerImplTest, ackWithMockedManagerPropagatesErrorCodeTest) {
auto consumer = std::make_shared<SimpleConsumerImpl>("test-group");
consumer->state(State::STARTED);
auto client_manager = std::make_shared<testing::NiceMock<ClientManagerMock>>();
consumer->clientManager(client_manager);
// Mock ack to invoke callback with an error
ON_CALL(*client_manager, ack)
.WillByDefault(testing::Invoke(
[](const std::string&, const Metadata&, const AckMessageRequest&, std::chrono::milliseconds,
const std::function<void(const std::error_code&)>& cb) {
cb(ErrorCode::NotFound);
}));
MessageConstPtr msg = Message::newBuilder().withTopic("ack-topic").withBody("body").build();
std::error_code ec;
consumer->ack(*msg, ec);
EXPECT_EQ(ErrorCode::NotFound, ec);
consumer->state(State::STOPPED);
}
TEST(SimpleConsumerImplTest, ackWithMockedManagerSuccessTest) {
auto consumer = std::make_shared<SimpleConsumerImpl>("test-group");
consumer->state(State::STARTED);
auto client_manager = std::make_shared<testing::NiceMock<ClientManagerMock>>();
consumer->clientManager(client_manager);
// Mock ack to invoke callback with success
ON_CALL(*client_manager, ack)
.WillByDefault(testing::Invoke(
[](const std::string&, const Metadata&, const AckMessageRequest&, std::chrono::milliseconds,
const std::function<void(const std::error_code&)>& cb) {
cb(std::error_code());
}));
MessageConstPtr msg = Message::newBuilder().withTopic("ack-topic").withBody("body").build();
std::error_code ec;
consumer->ack(*msg, ec);
EXPECT_FALSE(ec);
consumer->state(State::STOPPED);
}
ROCKETMQ_NAMESPACE_END