Skip to content

Commit 7a147d2

Browse files
kinyoklionbeekld
authored andcommitted
test: cover post-Goodbye selector preservation in FDv2DataSystem
Adds SynchronizerGoodbye_PreservesSelectorOnNextCall: drives the orchestrator through initializer-basis@v1 -> ChangeSet@v2 -> Goodbye -> Shutdown, and asserts the captured Next() selectors are v1, v2, v2 in order. The existing SynchronizerGoodbye_StaysOnSameSynchronizer test only checks that Goodbye does not rotate the synchronizer factory; it does not verify what selector the post-Goodbye Next() call receives. Without this preservation, the SDK would reconnect with stale or empty payload state on every Goodbye, forcing the server into expensive xfer-full responses instead of efficient xfer-changes. Verified load-bearing: temporarily clearing selector_ on Goodbye in fdv2_data_system.cpp makes only this test fail (the existing Goodbye test still passes).
1 parent 3a52efc commit 7a147d2

1 file changed

Lines changed: 64 additions & 0 deletions

File tree

libs/server-sdk/tests/fdv2_data_system_test.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,70 @@ TEST(FDv2DataSystemTest, SynchronizerNext_ReceivesUpdatedSelector) {
630630
EXPECT_EQ("state-2", next_calls[1].second.value->state);
631631
}
632632

633+
TEST(FDv2DataSystemTest, SynchronizerGoodbye_PreservesSelectorOnNextCall) {
634+
auto logger = MakeNullLogger();
635+
boost::asio::io_context ioc;
636+
data_components::DataSourceStatusManager status_manager;
637+
638+
// Initializer provides a basis with selector v1/state-1.
639+
auto initializer = std::make_unique<MockInitializer>(
640+
MakeFullChangeSetResult(ChangeSetData{}, MakeSelector(1, "state-1")));
641+
642+
std::vector<std::unique_ptr<IFDv2InitializerFactory>> initializers;
643+
initializers.push_back(
644+
std::make_unique<OneShotInitializerFactory>(std::move(initializer)));
645+
646+
// Synchronizer returns a partial changeset (selector advances to v2),
647+
// then a Goodbye, then exhausts via Shutdown. The orchestrator must
648+
// call Next a third time with the v2 selector — Goodbye is a transient
649+
// event the synchronizer handles internally (reconnects), and the
650+
// orchestrator must not regress the selector across it. Without this
651+
// preservation, the SDK would reconnect with stale or empty payload
652+
// state on every Goodbye, forcing the server into expensive xfer-full
653+
// responses instead of efficient xfer-changes.
654+
std::vector<MockSynchronizer::NextCall> next_calls;
655+
std::vector<FDv2SourceResult> results;
656+
results.push_back(FDv2SourceResult{FDv2SourceResult::ChangeSet{
657+
data_model::ChangeSet<ChangeSetData>{
658+
data_model::ChangeSetType::kPartial,
659+
ChangeSetData{},
660+
MakeSelector(2, "state-2"),
661+
},
662+
false,
663+
}});
664+
results.push_back(
665+
FDv2SourceResult{FDv2SourceResult::Goodbye{std::nullopt, false}});
666+
auto sync = std::make_unique<MockSynchronizer>(std::move(results), nullptr,
667+
&next_calls);
668+
669+
std::vector<std::unique_ptr<IFDv2SynchronizerFactory>> synchronizers;
670+
synchronizers.push_back(
671+
std::make_unique<OneShotSynchronizerFactory>(std::move(sync)));
672+
673+
FDv2DataSystem ds(std::move(initializers), std::move(synchronizers),
674+
ioc.get_executor(), &status_manager, logger);
675+
676+
ds.Initialize();
677+
ioc.run();
678+
679+
// Three Next calls: first with v1 from the initializer, second with v2
680+
// after the partial changeset, third with v2 still — Goodbye does not
681+
// regress the selector.
682+
ASSERT_EQ(3u, next_calls.size());
683+
684+
ASSERT_TRUE(next_calls[0].second.value.has_value());
685+
EXPECT_EQ(1, next_calls[0].second.value->version);
686+
EXPECT_EQ("state-1", next_calls[0].second.value->state);
687+
688+
ASSERT_TRUE(next_calls[1].second.value.has_value());
689+
EXPECT_EQ(2, next_calls[1].second.value->version);
690+
EXPECT_EQ("state-2", next_calls[1].second.value->state);
691+
692+
ASSERT_TRUE(next_calls[2].second.value.has_value());
693+
EXPECT_EQ(2, next_calls[2].second.value->version);
694+
EXPECT_EQ("state-2", next_calls[2].second.value->state);
695+
}
696+
633697
TEST(FDv2DataSystemTest,
634698
SynchronizerTerminalError_StatusInterruptedAndAdvance) {
635699
auto logger = MakeNullLogger();

0 commit comments

Comments
 (0)