@@ -3235,6 +3235,95 @@ def g(v):
32353235 assert result == expected
32363236
32373237
3238+ def test_downstream_placeholder_handles_upstream_post_expansion (dag_maker , session ):
3239+ """
3240+ Test dynamic task mapping behavior when an upstream placeholder task
3241+ (map_index = -1) has been replaced by the first expanded task
3242+ (map_index = 0).
3243+
3244+ This verifies that trigger rule evaluation correctly resolves relevant
3245+ upstream map indexes both when referencing the original placeholder
3246+ and when referencing the first expanded task instance.
3247+ """
3248+
3249+ with dag_maker (session = session ) as dag :
3250+
3251+ @task
3252+ def get_mapping_source ():
3253+ return ["one" , "two" , "three" ]
3254+
3255+ @task
3256+ def mapped_task (x ):
3257+ output = f"{ x } "
3258+ return output
3259+
3260+ @task_group (prefix_group_id = False )
3261+ def the_task_group (x ):
3262+ start = MockOperator (task_id = "start" )
3263+ upstream = mapped_task (x )
3264+
3265+ # Plain downstream inside task group (no mapping source).
3266+ downstream = MockOperator (task_id = "downstream" )
3267+
3268+ start >> upstream >> downstream
3269+
3270+ mapping_source = get_mapping_source ()
3271+ mapped_tg = the_task_group .expand (x = mapping_source )
3272+
3273+ mapping_source >> mapped_tg
3274+
3275+ # Create DAG run and execute prerequisites.
3276+ dr = dag_maker .create_dagrun ()
3277+
3278+ dag_maker .run_ti ("get_mapping_source" , map_index = - 1 , dag_run = dr , session = session )
3279+
3280+ # Force expansion of the upstream mapped task.
3281+ upstream_task = dag .get_task ("mapped_task" )
3282+ _ , max_index = TaskMap .expand_mapped_task (
3283+ upstream_task ,
3284+ dr .run_id ,
3285+ session = session ,
3286+ )
3287+ expanded_ti_count = max_index + 1
3288+
3289+ downstream_task = dag .get_task ("downstream" )
3290+
3291+ # Grab the downstream placeholder TI.
3292+ downstream_ti = dr .get_task_instance (task_id = "downstream" , map_index = - 1 , session = session )
3293+ downstream_ti .refresh_from_task (downstream_task )
3294+
3295+ result = downstream_ti .get_relevant_upstream_map_indexes (
3296+ upstream = upstream_task ,
3297+ ti_count = expanded_ti_count ,
3298+ session = session ,
3299+ )
3300+
3301+ assert result == 0
3302+
3303+ # Now do the same for downstream expanded (map_index = 0) to ensure existing behavior is not broken.
3304+ # Force expansion of the downstream mapped task.
3305+ _ , max_index = TaskMap .expand_mapped_task (
3306+ downstream_task ,
3307+ dr .run_id ,
3308+ session = session ,
3309+ )
3310+ expanded_ti_count = max_index + 1
3311+
3312+ # Grab the first expanded downstream task. Behavior is the same for all cases where map_inddex >= 0.
3313+ downstream_ti = dr .get_task_instance (task_id = "downstream" , map_index = 0 , session = session )
3314+ downstream_ti .refresh_from_task (downstream_task )
3315+
3316+ result = downstream_ti .get_relevant_upstream_map_indexes (
3317+ upstream = upstream_task ,
3318+ ti_count = expanded_ti_count ,
3319+ session = session ,
3320+ )
3321+
3322+ # Verify behavior remains unchanged once the downstream task itself
3323+ # has expanded (map_index >= 0).
3324+ assert result == 0
3325+
3326+
32383327def test_find_relevant_relatives_with_non_mapped_task_as_tuple (dag_maker , session ):
32393328 """Test that specifying a non-mapped task as a tuple doesn't raise NotMapped exception."""
32403329 # t1 -> t2 (non-mapped) -> t3
0 commit comments