@@ -145,6 +145,15 @@ class OneShotSynchronizerFactory : public IFDv2SynchronizerFactory {
145145 std::unique_ptr<IFDv2Synchronizer> source_;
146146};
147147
148+ class FDv1FallbackOneShotFactory : public OneShotSynchronizerFactory {
149+ public:
150+ explicit FDv1FallbackOneShotFactory (
151+ std::unique_ptr<IFDv2Synchronizer> source)
152+ : OneShotSynchronizerFactory(std::move(source)) {}
153+
154+ bool IsFDv1Fallback () const override { return true ; }
155+ };
156+
148157// Returns each pre-supplied source in order on successive Build() calls.
149158// Returns nullptr once the supply is exhausted. Used in tests that exercise
150159// wrap-around or recovery, where the same factory is built more than once.
@@ -1090,6 +1099,129 @@ TEST(FDv2DataSystemTest, SingleSynchronizerHasNoFallbackArmed) {
10901099 status_manager.Status ().State ());
10911100}
10921101
1102+ // ============================================================================
1103+ // FDv1 fallback directive
1104+ // ============================================================================
1105+
1106+ TEST (FDv2DataSystemTest, SynchronizerFdv1FlagSwitchesToFdv1Adapter) {
1107+ auto logger = MakeNullLogger ();
1108+ boost::asio::io_context ioc;
1109+ data_components::DataSourceStatusManager status_manager;
1110+
1111+ // FDv2 synchronizer emits a ChangeSet with the directive, then closes.
1112+ auto fdv2_sync =
1113+ std::make_unique<MockSynchronizer>(std::vector<FDv2SourceResult>{[]() {
1114+ FDv2SourceResult r{FDv2SourceResult::ChangeSet{
1115+ data_model::ChangeSet<ChangeSetData>{
1116+ data_model::ChangeSetType::kNone ,
1117+ {},
1118+ data_model::Selector{}}}};
1119+ r.fdv1_fallback = true ;
1120+ return r;
1121+ }()});
1122+ auto fdv2_factory =
1123+ std::make_unique<OneShotSynchronizerFactory>(std::move (fdv2_sync));
1124+
1125+ // FDv1 adapter returns Shutdown when reached, ending orchestration.
1126+ auto fdv1_sync =
1127+ std::make_unique<MockSynchronizer>(std::vector<FDv2SourceResult>{});
1128+ auto fdv1_factory =
1129+ std::make_unique<FDv1FallbackOneShotFactory>(std::move (fdv1_sync));
1130+ auto * fdv1_factory_ptr = fdv1_factory.get ();
1131+
1132+ std::vector<std::unique_ptr<IFDv2SynchronizerFactory>> synchronizers;
1133+ synchronizers.push_back (std::move (fdv2_factory));
1134+ synchronizers.push_back (std::move (fdv1_factory));
1135+
1136+ FDv2DataSystem ds ({}, std::move (synchronizers),
1137+ /* fallback_condition_factory=*/ nullptr ,
1138+ /* recovery_condition_factory=*/ nullptr ,
1139+ ioc.get_executor (), &status_manager, logger);
1140+ ds.Initialize ();
1141+ ioc.run ();
1142+
1143+ EXPECT_EQ (1 , fdv1_factory_ptr->build_count_ );
1144+ }
1145+
1146+ TEST (FDv2DataSystemTest, SynchronizerFdv1FlagWithoutAdapterTransitionsOff) {
1147+ auto logger = MakeNullLogger ();
1148+ boost::asio::io_context ioc;
1149+ data_components::DataSourceStatusManager status_manager;
1150+
1151+ auto fdv2_sync =
1152+ std::make_unique<MockSynchronizer>(std::vector<FDv2SourceResult>{[]() {
1153+ FDv2SourceResult r{
1154+ FDv2SourceResult::Interrupted{FDv2SourceResult::ErrorInfo{
1155+ FDv2SourceResult::ErrorInfo::ErrorKind::kErrorResponse ,
1156+ /* status_code=*/ 418 , " directive" ,
1157+ std::chrono::system_clock::now ()}}};
1158+ r.fdv1_fallback = true ;
1159+ return r;
1160+ }()});
1161+ auto fdv2_factory =
1162+ std::make_unique<OneShotSynchronizerFactory>(std::move (fdv2_sync));
1163+
1164+ std::vector<std::unique_ptr<IFDv2SynchronizerFactory>> synchronizers;
1165+ synchronizers.push_back (std::move (fdv2_factory));
1166+
1167+ FDv2DataSystem ds ({}, std::move (synchronizers),
1168+ /* fallback_condition_factory=*/ nullptr ,
1169+ /* recovery_condition_factory=*/ nullptr ,
1170+ ioc.get_executor (), &status_manager, logger);
1171+ ds.Initialize ();
1172+ ioc.run ();
1173+
1174+ EXPECT_EQ (DataSourceStatus::DataSourceState::kOff ,
1175+ status_manager.Status ().State ());
1176+ }
1177+
1178+ TEST (FDv2DataSystemTest, InitializerFdv1FlagSwitchesToFdv1Adapter) {
1179+ auto logger = MakeNullLogger ();
1180+ boost::asio::io_context ioc;
1181+ data_components::DataSourceStatusManager status_manager;
1182+
1183+ // Initializer returns Interrupted with the directive set.
1184+ FDv2SourceResult init_result{
1185+ FDv2SourceResult::Interrupted{FDv2SourceResult::ErrorInfo{
1186+ FDv2SourceResult::ErrorInfo::ErrorKind::kErrorResponse ,
1187+ /* status_code=*/ 418 , " directive" ,
1188+ std::chrono::system_clock::now ()}}};
1189+ init_result.fdv1_fallback = true ;
1190+ auto initializer =
1191+ std::make_unique<MockInitializer>(std::move (init_result));
1192+
1193+ std::vector<std::unique_ptr<IFDv2InitializerFactory>> initializers;
1194+ initializers.push_back (
1195+ std::make_unique<OneShotInitializerFactory>(std::move (initializer)));
1196+
1197+ auto fdv2_sync =
1198+ std::make_unique<MockSynchronizer>(std::vector<FDv2SourceResult>{});
1199+ auto fdv2_factory =
1200+ std::make_unique<OneShotSynchronizerFactory>(std::move (fdv2_sync));
1201+ auto * fdv2_factory_ptr = fdv2_factory.get ();
1202+
1203+ auto fdv1_sync =
1204+ std::make_unique<MockSynchronizer>(std::vector<FDv2SourceResult>{});
1205+ auto fdv1_factory =
1206+ std::make_unique<FDv1FallbackOneShotFactory>(std::move (fdv1_sync));
1207+ auto * fdv1_factory_ptr = fdv1_factory.get ();
1208+
1209+ std::vector<std::unique_ptr<IFDv2SynchronizerFactory>> synchronizers;
1210+ synchronizers.push_back (std::move (fdv2_factory));
1211+ synchronizers.push_back (std::move (fdv1_factory));
1212+
1213+ FDv2DataSystem ds (std::move (initializers), std::move (synchronizers),
1214+ /* fallback_condition_factory=*/ nullptr ,
1215+ /* recovery_condition_factory=*/ nullptr ,
1216+ ioc.get_executor (), &status_manager, logger);
1217+ ds.Initialize ();
1218+ ioc.run ();
1219+
1220+ // FDv2 synchronizer was skipped; FDv1 adapter was built and ran.
1221+ EXPECT_EQ (0 , fdv2_factory_ptr->build_count_ );
1222+ EXPECT_EQ (1 , fdv1_factory_ptr->build_count_ );
1223+ }
1224+
10931225// ============================================================================
10941226// Destruction protocol: in-flight orchestration
10951227// ============================================================================
0 commit comments