|
18 | 18 | */ |
19 | 19 | #include <gtest/gtest.h> |
20 | 20 | #include <pulsar/Client.h> |
| 21 | +#include <pulsar/DeadLetterPolicyBuilder.h> |
21 | 22 | #include <pulsar/MessageIdBuilder.h> |
22 | 23 |
|
23 | 24 | #include <ctime> |
24 | 25 | #include <random> |
| 26 | +#include <sstream> |
25 | 27 |
|
26 | 28 | #include "PulsarFriend.h" |
27 | 29 | #include "WaitUtils.h" |
28 | 30 | #include "lib/ChunkMessageIdImpl.h" |
| 31 | +#include "lib/ConsumerImpl.h" |
29 | 32 | #include "lib/LogUtils.h" |
30 | 33 |
|
31 | 34 | DECLARE_LOG_OBJECT() |
@@ -454,6 +457,153 @@ TEST_P(MessageChunkingTest, testResendChunkWithAckHoleMessages) { |
454 | 457 | consumer.close(); |
455 | 458 | } |
456 | 459 |
|
| 460 | +// Aligned with Go TestChunkAckAndNAck and Java testNegativeAckChunkedMessage |
| 461 | +TEST_P(MessageChunkingTest, testNegativeAckChunkedMessage) { |
| 462 | + if (toString(GetParam()) != "None") { |
| 463 | + return; |
| 464 | + } |
| 465 | + const std::string topic = |
| 466 | + "MessageChunkingTest-testNegativeAckChunkedMessage-" + std::to_string(time(nullptr)); |
| 467 | + |
| 468 | + Consumer consumer; |
| 469 | + ConsumerConfiguration consumerConf; |
| 470 | + consumerConf.setConsumerType(ConsumerShared); |
| 471 | + consumerConf.setNegativeAckRedeliveryDelayMs(1000); |
| 472 | + createConsumer(topic, consumer, consumerConf); |
| 473 | + |
| 474 | + Producer producer; |
| 475 | + createProducer(topic, producer); |
| 476 | + |
| 477 | + // Send a chunked message |
| 478 | + MessageId sendMsgId; |
| 479 | + ASSERT_EQ(ResultOk, producer.send(MessageBuilder().setContent(largeMessage).build(), sendMsgId)); |
| 480 | + |
| 481 | + // Receive and nack |
| 482 | + Message msg; |
| 483 | + ASSERT_EQ(ResultOk, consumer.receive(msg, 5000)); |
| 484 | + ASSERT_EQ(msg.getDataAsString(), largeMessage); |
| 485 | + consumer.negativeAcknowledge(msg); |
| 486 | + |
| 487 | + // The message should be redelivered after nack delay |
| 488 | + Message redeliveredMsg; |
| 489 | + ASSERT_EQ(ResultOk, consumer.receive(redeliveredMsg, 5000)); |
| 490 | + ASSERT_EQ(redeliveredMsg.getDataAsString(), largeMessage); |
| 491 | + consumer.acknowledge(redeliveredMsg); |
| 492 | + |
| 493 | + // Verify no more messages |
| 494 | + Message noMsg; |
| 495 | + ASSERT_NE(ResultOk, consumer.receive(noMsg, 2000)); |
| 496 | + |
| 497 | + producer.close(); |
| 498 | + consumer.close(); |
| 499 | +} |
| 500 | + |
| 501 | +// Aligned with Java testLargeMessageAckTimeOut |
| 502 | +TEST_P(MessageChunkingTest, testAckTimeoutChunkedMessage) { |
| 503 | + if (toString(GetParam()) != "None") { |
| 504 | + return; |
| 505 | + } |
| 506 | + const std::string topic = |
| 507 | + "MessageChunkingTest-testAckTimeoutChunkedMessage-" + std::to_string(time(nullptr)); |
| 508 | + |
| 509 | + Consumer consumer; |
| 510 | + ConsumerConfiguration consumerConf; |
| 511 | + consumerConf.setConsumerType(ConsumerShared); |
| 512 | + // Set ack timeout to 2 seconds |
| 513 | + PulsarFriend::setConsumerUnAckMessagesTimeoutMs(consumerConf, 2000); |
| 514 | + createConsumer(topic, consumer, consumerConf); |
| 515 | + |
| 516 | + Producer producer; |
| 517 | + createProducer(topic, producer); |
| 518 | + |
| 519 | + // Send a chunked message |
| 520 | + MessageId sendMsgId; |
| 521 | + ASSERT_EQ(ResultOk, producer.send(MessageBuilder().setContent(largeMessage).build(), sendMsgId)); |
| 522 | + |
| 523 | + // Receive but do NOT acknowledge - let ack timeout trigger redelivery |
| 524 | + Message msg; |
| 525 | + ASSERT_EQ(ResultOk, consumer.receive(msg, 5000)); |
| 526 | + ASSERT_EQ(msg.getDataAsString(), largeMessage); |
| 527 | + |
| 528 | + // Wait for ack timeout to trigger redelivery |
| 529 | + // The message should be redelivered after ack timeout (2s) |
| 530 | + Message redeliveredMsg; |
| 531 | + ASSERT_EQ(ResultOk, consumer.receive(redeliveredMsg, 5000)); |
| 532 | + ASSERT_EQ(redeliveredMsg.getDataAsString(), largeMessage); |
| 533 | + consumer.acknowledge(redeliveredMsg); |
| 534 | + |
| 535 | + // Verify no more messages |
| 536 | + Message noMsg; |
| 537 | + ASSERT_NE(ResultOk, consumer.receive(noMsg, 2000)); |
| 538 | + |
| 539 | + producer.close(); |
| 540 | + consumer.close(); |
| 541 | +} |
| 542 | + |
| 543 | +TEST_P(MessageChunkingTest, testChunkedMessageDLQ) { |
| 544 | + if (toString(GetParam()) != "None") { |
| 545 | + return; |
| 546 | + } |
| 547 | + const std::string topic = "persistent://public/default/MessageChunkingTest-testChunkedMessageDLQ-" + |
| 548 | + std::to_string(time(nullptr)); |
| 549 | + const std::string subName = "my-sub"; |
| 550 | + const std::string dlqTopic = topic + "-" + subName + "-DLQ"; |
| 551 | + |
| 552 | + Client client(lookupUrl); |
| 553 | + |
| 554 | + auto dlqPolicy = |
| 555 | + DeadLetterPolicyBuilder().maxRedeliverCount(2).initialSubscriptionName("dlq-init-sub").build(); |
| 556 | + |
| 557 | + Consumer consumer; |
| 558 | + ConsumerConfiguration consumerConf; |
| 559 | + consumerConf.setConsumerType(ConsumerShared); |
| 560 | + consumerConf.setNegativeAckRedeliveryDelayMs(100); |
| 561 | + consumerConf.setDeadLetterPolicy(dlqPolicy); |
| 562 | + ASSERT_EQ(ResultOk, client.subscribe(topic, subName, consumerConf, consumer)); |
| 563 | + |
| 564 | + // Subscribe to DLQ topic to verify messages arrive there |
| 565 | + Consumer dlqConsumer; |
| 566 | + ConsumerConfiguration dlqConsumerConf; |
| 567 | + dlqConsumerConf.setConsumerType(ConsumerShared); |
| 568 | + ASSERT_EQ(ResultOk, client.subscribe(dlqTopic, "dlq-sub", dlqConsumerConf, dlqConsumer)); |
| 569 | + |
| 570 | + Producer producer; |
| 571 | + createProducer(topic, producer); |
| 572 | + |
| 573 | + // Send a chunked message |
| 574 | + MessageId sendMsgId; |
| 575 | + ASSERT_EQ(ResultOk, producer.send(MessageBuilder().setContent(largeMessage).build(), sendMsgId)); |
| 576 | + |
| 577 | + // Nack the message maxRedeliverCount + 1 times to trigger DLQ |
| 578 | + Message msg; |
| 579 | + for (int i = 0; i < dlqPolicy.getMaxRedeliverCount() + 1; i++) { |
| 580 | + ASSERT_EQ(ResultOk, consumer.receive(msg, 5000)); |
| 581 | + ASSERT_EQ(msg.getDataAsString(), largeMessage); |
| 582 | + consumer.negativeAcknowledge(msg); |
| 583 | + } |
| 584 | + |
| 585 | + // Verify the message arrives in DLQ with correct content |
| 586 | + Message dlqMsg; |
| 587 | + ASSERT_EQ(ResultOk, dlqConsumer.receive(dlqMsg, 10000)); |
| 588 | + ASSERT_EQ(dlqMsg.getDataAsString(), largeMessage); |
| 589 | + std::stringstream expectedOriginMsgId; |
| 590 | + expectedOriginMsgId << sendMsgId; |
| 591 | + ASSERT_EQ(dlqMsg.getProperty(PROPERTY_ORIGIN_MESSAGE_ID), expectedOriginMsgId.str()); |
| 592 | + ASSERT_EQ(dlqMsg.getProperty(SYSTEM_PROPERTY_REAL_TOPIC), topic); |
| 593 | + |
| 594 | + // Verify no more messages in DLQ |
| 595 | + Message noMsg; |
| 596 | + ASSERT_NE(ResultOk, dlqConsumer.receive(noMsg, 2000)); |
| 597 | + |
| 598 | + // Verify original consumer has no more messages (message was acked after DLQ send) |
| 599 | + ASSERT_NE(ResultOk, consumer.receive(noMsg, 2000)); |
| 600 | + |
| 601 | + producer.close(); |
| 602 | + consumer.close(); |
| 603 | + dlqConsumer.close(); |
| 604 | + client.close(); |
| 605 | +} |
| 606 | + |
457 | 607 | // The CI env is Ubuntu 16.04, the gtest-dev version is 1.8.0 that doesn't have INSTANTIATE_TEST_SUITE_P |
458 | 608 | INSTANTIATE_TEST_CASE_P(Pulsar, MessageChunkingTest, |
459 | 609 | ::testing::Values(CompressionNone, CompressionLZ4, CompressionZLib, CompressionZSTD, |
|
0 commit comments