@@ -520,6 +520,10 @@ func TestDataFlowReconciler_Reconcile_CreateDeployment(t *testing.T) {
520520 err = fakeClient .Get (ctx , deploymentName , & deployment )
521521 assert .NoError (t , err , "Deployment should be created" )
522522 assert .Equal (t , "dataflow-test-dataflow" , deployment .Name )
523+ assert .Contains (t , deployment .Spec .Template .Annotations , specHashAnnotation ,
524+ "Deployment pod template should have spec-hash annotation for ConfigMap change detection" )
525+ assert .NotEmpty (t , deployment .Spec .Template .Annotations [specHashAnnotation ],
526+ "spec-hash annotation should be non-empty" )
523527 require .Len (t , deployment .Spec .Template .Spec .Containers , 1 )
524528 assert .Equal (t , version .DefaultProcessorImage (), deployment .Spec .Template .Spec .Containers [0 ].Image , "default processor image should match controller" )
525529 var hasLogLevel bool
@@ -1139,6 +1143,88 @@ func TestCreateOrUpdateDeployment_UpdateWhenSpecChanged(t *testing.T) {
11391143 "Deployment NodeSelector should reflect updated DataFlow spec" )
11401144}
11411145
1146+ // TestCreateOrUpdateDeployment_UpdateWhenSpecContentChanged verifies that when DataFlow spec content
1147+ // changes (e.g. Kafka brokers, SecretRef), the spec-hash annotation changes and Deployment is updated,
1148+ // triggering a pod restart for the new config.
1149+ func TestCreateOrUpdateDeployment_UpdateWhenSpecContentChanged (t * testing.T ) {
1150+ scheme := runtime .NewScheme ()
1151+ require .NoError (t , dataflowv1 .AddToScheme (scheme ))
1152+ require .NoError (t , clientgoscheme .AddToScheme (scheme ))
1153+
1154+ fakeRecorder := record .NewFakeRecorder (10 )
1155+ fakeClient := fake .NewClientBuilder ().WithScheme (scheme ).Build ()
1156+ reconciler := NewDataFlowReconciler (fakeClient , scheme , fakeRecorder )
1157+
1158+ ctx := context .Background ()
1159+ dataflow := & dataflowv1.DataFlow {
1160+ TypeMeta : metav1.TypeMeta {
1161+ APIVersion : "dataflow.dataflow.io/v1" ,
1162+ Kind : "DataFlow" ,
1163+ },
1164+ ObjectMeta : metav1.ObjectMeta {
1165+ Name : "test-dataflow" ,
1166+ Namespace : "default" ,
1167+ },
1168+ Spec : dataflowv1.DataFlowSpec {
1169+ Source : dataflowv1.SourceSpec {
1170+ Type : "kafka" ,
1171+ Kafka : & dataflowv1.KafkaSourceSpec {
1172+ Brokers : []string {"localhost:9092" },
1173+ Topic : "test-topic" ,
1174+ ConsumerGroup : "test-group" ,
1175+ },
1176+ },
1177+ Sink : dataflowv1.SinkSpec {
1178+ Type : "kafka" ,
1179+ Kafka : & dataflowv1.KafkaSinkSpec {Brokers : []string {"localhost:9092" }, Topic : "output-topic" },
1180+ },
1181+ },
1182+ }
1183+ require .NoError (t , fakeClient .Create (ctx , dataflow ))
1184+
1185+ req := ctrl.Request {
1186+ NamespacedName : types.NamespacedName {Name : "test-dataflow" , Namespace : "default" },
1187+ }
1188+
1189+ // First reconcile — Deployment created
1190+ _ , _ = reconciler .Reconcile (ctx , req )
1191+ drainRecorderEvents (fakeRecorder )
1192+
1193+ deploymentName := types.NamespacedName {Name : "dataflow-test-dataflow" , Namespace : "default" }
1194+ var deployment appsv1.Deployment
1195+ require .NoError (t , fakeClient .Get (ctx , deploymentName , & deployment ))
1196+ hashBefore := deployment .Spec .Template .Annotations [specHashAnnotation ]
1197+ require .NotEmpty (t , hashBefore , "initial Deployment should have spec-hash annotation" )
1198+
1199+ // Change DataFlow spec content (Kafka brokers) — same ConfigMap name, but content changes
1200+ require .NoError (t , fakeClient .Get (ctx , req .NamespacedName , dataflow ))
1201+ dataflow .Spec .Source .Kafka .Brokers = []string {"kafka-1:9092" , "kafka-2:9092" }
1202+ require .NoError (t , fakeClient .Update (ctx , dataflow ))
1203+
1204+ // Second reconcile — Deployment should be updated (spec-hash changed)
1205+ _ , _ = reconciler .Reconcile (ctx , req )
1206+
1207+ var deploymentUpdatedCount int
1208+ for {
1209+ select {
1210+ case e := <- fakeRecorder .Events :
1211+ if strings .Contains (e , "DeploymentUpdated" ) {
1212+ deploymentUpdatedCount ++
1213+ }
1214+ default :
1215+ goto done
1216+ }
1217+ }
1218+ done:
1219+ assert .Equal (t , 1 , deploymentUpdatedCount ,
1220+ "expected DeploymentUpdated event when spec content (Kafka brokers) changed" )
1221+
1222+ require .NoError (t , fakeClient .Get (ctx , deploymentName , & deployment ))
1223+ hashAfter := deployment .Spec .Template .Annotations [specHashAnnotation ]
1224+ assert .NotEqual (t , hashBefore , hashAfter ,
1225+ "spec-hash should change when Kafka brokers change, triggering pod restart" )
1226+ }
1227+
11421228// TestCreateOrUpdateDeployment_RetryOnConflict verifies that when Deployment Update returns 409 Conflict,
11431229// the controller retries and succeeds on the next attempt (no extra rollout: spec comparison skips redundant Update).
11441230func TestCreateOrUpdateDeployment_RetryOnConflict (t * testing.T ) {
0 commit comments